Skip to content

Move contract setup out of the management commands into b2b.contracts (C3 2/3) - #3964

Merged
blarghmatey merged 4 commits into
b2b-c3-clone-statusfrom
b2b-c3-contract-services
Sep 25, 2026
Merged

blarghmatey merged 4 commits into
b2b-c3-clone-statusfrom
b2b-c3-contract-services

Conversation

@blarghmatey

Copy link
Copy Markdown
Member

What are the relevant tickets?

Capability C3 of the B2B onboarding RFC, https://github.com/mitodl/hq/discussions/12784. Design and decisions: https://github.com/mitodl/hq/discussions/12784#discussioncomment-18437846.

Stack (2 of 3). Based on #3963. 3/3 adds the staff contract API on top of this.

Description (What does it do?)

The staff contract API needs the create, courseware add/remove and code-count logic the management commands carry inline. It now lives in b2b/contracts.py, and the commands call it:

  • create_contract: the contract under its organization, plus a default variant set
  • ensure_default_variant: used by check_contract_variant --fix-default
  • add_courseware_to_contract: a program, course or existing run. no_reruns=True by default, so repeating the call doesn't mint another run
  • remove_courseware_from_contract / remove_run_from_contract
  • expected_enrollment_code_count

Behaviour that changes:

  • b2b_contract create gives the contract a default variant set. Without one, get_all_variant_runs returns nothing, which is why check_contract_variant --fix-default was a required follow-up. The command also prints the new contract's ID.
  • Programs added with b2b_courseware add use the organization's courseware prefix, as courses already did. add_program_courses never passed a prefix, so program runs always got UAI_. Its other callers keep that default.
  • b2b_courseware remove only touches runs that are in the contract. Naming a run outside it used to close the run and deactivate its products.
  • b2b_codes validate counts expected codes per active product rather than per linked run, matching what ensure_enrollment_codes_exist creates. A removed run kept linked by its enrollments has an inactive product, so the per-run count never matched and validate re-ran code generation every time.

Also corrects docs/source/b2b/commands.md, docs/source/b2b/orgs_contracts.md and the comment in create_contract_run, which said saving a contract generates enrollment codes. ContractPage.save only sets the title. Bringing a contract's codes in line takes an explicit call: b2b_courseware add --make-codes, b2b_codes validate, or create_contract_run(queue_codes=True).

How can this be tested?

Run in the web container: pytest b2b, 619 passed and 1 skipped. makemigrations --check reports no changes. pre-commit passes.

b2b/contracts_test.py covers the new functions. The existing b2b_courseware add and remove tests (b2b/management/tests/b2b_courseware_test.py, b2b/commands_test.py) pass unchanged. No test invokes b2b_contract or check_contract_variant.

Not run by hand. To check the commands locally:

  1. b2b_contract create <org_key> "Test" code --max-learners 2 prints the new ID, and check_contract_variant <id> finds a default variant
  2. b2b_courseware add <id> <course readable_id> --make-codes
  3. b2b_codes validate --contract <id> reports matching found and expected counts

🤖 Generated with Claude Code

https://claude.ai/code/session_01NUA1EjC95LZV4J5swhZgxy

@github-actions

Copy link
Copy Markdown

OpenAPI Changes

Show/hide changes
## Changes for v0.yaml:
No changes detected

## Changes for v1.yaml:
No changes detected

## Changes for v2.yaml:
No changes detected

Unexpected changes? Ensure your branch is up-to-date with main (consider rebasing).

Comment thread b2b/management/commands/b2b_courseware.py
@blarghmatey
blarghmatey added this pull request to stack #3966 September 15, 2026 14:44
@blarghmatey
blarghmatey requested a balanced review from Copilot September 15, 2026 14:44

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Forced moves retain old contract membership, and the direct removal helper can destructively modify unrelated runs.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

Extracts B2B contract provisioning into reusable services for management commands and the upcoming staff API.

Changes:

  • Adds reusable contract, courseware, variant, removal, and code-count helpers.
  • Updates management commands to use the shared logic.
  • Corrects enrollment-code documentation and adds service tests.
File summaries
File Description
b2b/contracts.py Adds shared contract provisioning services.
b2b/contracts_test.py Tests the new services.
b2b/models.py Supports organization prefixes for program runs.
b2b/api.py Corrects enrollment-code documentation.
b2b/management/commands/b2b_contract.py Uses shared contract creation.
b2b/management/commands/b2b_courseware.py Uses shared add/remove services.
b2b/management/commands/b2b_codes.py Uses product-based expected code counts.
b2b/management/commands/check_contract_variant.py Uses shared default-variant creation.
docs/source/b2b/commands.md Updates command behavior documentation.
docs/source/b2b/orgs_contracts.md Clarifies code-refresh behavior.
Review details
  • Files reviewed: 10/10 changed files
  • Comments generated: 2
  • Review effort level: Balanced

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread b2b/contracts.py
Comment thread b2b/contracts.py
@blarghmatey
blarghmatey force-pushed the b2b-c3-contract-services branch from 32c3b85 to de77cc6 Compare September 15, 2026 14:53
@blarghmatey
blarghmatey force-pushed the b2b-c3-contract-services branch from d92f623 to 135ebb3 Compare September 17, 2026 14:43
Comment thread b2b/contracts.py
@blarghmatey
blarghmatey force-pushed the b2b-c3-contract-services branch from 135ebb3 to 9ebf948 Compare September 17, 2026 14:48
Comment thread b2b/models.py Outdated
@blarghmatey
blarghmatey force-pushed the b2b-c3-contract-services branch from 9ebf948 to d24d23c Compare September 17, 2026 15:23
Comment thread b2b/management/commands/b2b_courseware.py
@blarghmatey
blarghmatey force-pushed the b2b-c3-contract-services branch from fa9b12a to 1847f20 Compare September 17, 2026 18:14

@jkachel jkachel left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM - did try running the commands and (other than the argument order being wrong for b2b_courseware add; order is weird when using subparsers) everything worked as expected.

@blarghmatey
blarghmatey force-pushed the b2b-c3-contract-services branch from 1847f20 to acb8d4c Compare September 22, 2026 13:51
blarghmatey and others added 4 commits September 25, 2026 12:38
… (C3 2/3)

The staff contract API needs the same create, courseware add/remove and
code-count logic the commands carry inline, so it lives in one module both
call.

Behaviour that changes on the way:
- create_contract adds the default variant set. Command-created contracts
  had none, and get_all_variant_runs returns nothing without one, so
  check_contract_variant --fix-default was a required follow-up step.
  b2b_contract create also prints the new contract's ID.
- Programs added through b2b_courseware follow the organization's
  courseware prefix, as courses already did. add_program_courses never
  passed a prefix, so program runs always got UAI_. Other callers of
  add_program_courses keep that default.
- b2b_courseware remove only touches runs that are in the contract. Naming
  a run outside it used to close the run and deactivate its products.
- b2b_codes validate counts expected codes per active product rather than
  per linked run, matching what ensure_enrollment_codes_exist creates. A
  removed run kept linked by its enrollments has an inactive product, so
  the per-run count never matched and validate re-ran every time.

Also corrects the docs and the create_contract_run comment that said
saving a contract generates enrollment codes. ContractPage.save only sets
the title.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUA1EjC95LZV4J5swhZgxy
…ng to remove

remove_run_from_contract closes a run, deactivates its products and deletes
codes without checking that the run is in the contract. Its only caller,
remove_courseware_from_contract, filters to the contract's runs first, so
the helper is now private and that check stays in one place.

b2b_courseware remove printed nothing for a course or run with no runs in
the contract, where the old command reported how many runs it found. It
now says nothing was removed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUA1EjC95LZV4J5swhZgxy
Copilot flagged that force changed the legacy FK but only added to the M2M,
so the run stayed in its previous contract as well. jkachel, who added the
flag, says it was meant to re-point a run when something went wrong, that
run membership can be adjusted elsewhere, and that the many-to-many needs a
different mechanism anyway, so it should go.

A run already in another contract is now always left where it is and
reported as skipped. That also matches where contracts are heading: pdpinch
notes that letting public runs into contracts requires one run to appear in
several of them, which is the opposite of a move.

The remaining questions about run-to-contract membership are tracked in
witan as tk-decide-how-a-course-run-belongs-to-contracts-sha-a00239.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUA1EjC95LZV4J5swhZgxy
…ry line

add_program_courses returns a 2-tuple; the docstring said three. The
remove subcommand's refactor dropped the course-level summary message
that the pre-refactor command printed, leaving only per-run lines.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W2XzZbf9sYVFaw6PVaw7uX
@blarghmatey
blarghmatey force-pushed the b2b-c3-contract-services branch from acb8d4c to b9c83c4 Compare September 25, 2026 16:38
@blarghmatey
blarghmatey merged commit 5aeb36b into main Sep 25, 2026
15 checks passed
@blarghmatey
blarghmatey deleted the b2b-c3-contract-services branch September 25, 2026 16:59
@odlbot odlbot mentioned this pull request Sep 28, 2026
11 tasks
@odlbot odlbot mentioned this pull request Sep 28, 2026
13 tasks done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants