Skip to content

Track the contract that an enrollment belongs to - #3944

Open
jkachel wants to merge 11 commits into
mainfrom
jkachel/store-enrollment-contract
Open

jkachel wants to merge 11 commits into
mainfrom
jkachel/store-enrollment-contract

Conversation

@jkachel

@jkachel jkachel commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

What are the relevant tickets?

https://github.com/mitodl/hq/discussions/12973

Description (What does it do?)

We were able to determine if an enrollment was a B2B enrollment or not based on the run it was for - runs could either be B2B contract runs or not. #3903 changes that and allows runs to belong to multiple contracts, so the current method of determination won't work.

This PR adds a b2b_contract FK to CourseRunEnrollment and ProgramEnrollment so we can track what contract the enrollment was for. A backfill migration was also added to fill out the existing enrollments. The B2B APIs were updated to ensure they're filling the enrollment's contract field as well.

How can this be tested?

APIs that display enrollments and also selectively filter B2B enrollments should work as expected - you should either see enrollments that aren't attached to a B2B contract, or enrollments attached to the specified B2B contract.

Creating a B2B enrollment should set the b2b_contract field on the enrollment itself properly. Furthermore, it should attempt to figure out what contract to apply if one isn't specified. The B2B enrollment API - https://mitxonline.mit.edu/api/schema/swagger-ui/#/b2b/b2b_enroll_create - has been updated to handle this; it now also takes an optional contract slug to specify the contract to use.

Creating a B2B enrollment in a course run or program that is either for a contract the learner's not in or that is ambiguous should fail:

  • Specifying a contract slug that either the purchasable item isn't in or that the user isn't in should result in an error.
  • Specifying no contract slug when the overlap between user contracts and purchasable item contracts is >1 contract should also result in an error.

Additional Context

Course runs belonging to more than one contract is pretty new - but programs have had this for a while. Existing program enrollments get backfilled by comparing the program enrollments for a given user against what course runs the user has enrolled in. If the learner is in a program that is linked to a B2B contract, and the learner also has enrollments in corresponding course runs that are also in the same contract, then the learner's program enrollment is linked to the contract if they have no public course run enrollments.

Learn will need some minor updates to support the contract slug field. However, it's unlikely that users will run into the "ambiguous contract" error for now; most (normal, not engineer) people aren't in multiple contracts anyway and even then we will by and large only have one contract for each course run. But, we should update the frontend somewhat quickly because people are waiting for the ability to attach public course runs to arbitrary contracts.

Claude was used to generate some of these tests and work through a few sections of the code path; this is largely not AI-written code, though.

@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown

OpenAPI Changes

Show/hide changes
## Changes for v0.yaml:
7 changes: 0 error, 4 warning, 3 info
warning	[response-property-enum-value-added] at head/openapi/specs/v0.yaml
	in API POST /api/v0/b2b/enroll/{readable_id}/
		added the new `b2b-error-ambiguous-contract` enum value to the `result/allOf[#/components/schemas/ResultEnum]/` response property for the response status `201`
		The server may now return a value the previous contract excluded, so a client written against it may not handle the response. If the value set is meant to grow, declare it with x-extensible-enum. This compared the `allOf` branches one at a time, so another branch may still guarantee what this one dropped. Run with --flatten-allof to merge the branches and compare what they describe together.

warning	[response-property-enum-value-added] at head/openapi/specs/v0.yaml
	in API POST /api/v0/b2b/enroll/{readable_id}/
		added the new `b2b-error-ambiguous-contract` enum value to the `result/allOf[#/components/schemas/ResultEnum]/` response property for the response status `400`
		The server may now return a value the previous contract excluded, so a client written against it may not handle the response. If the value set is meant to grow, declare it with x-extensible-enum. This compared the `allOf` branches one at a time, so another branch may still guarantee what this one dropped. Run with --flatten-allof to merge the branches and compare what they describe together.

warning	[response-property-enum-value-added] at head/openapi/specs/v0.yaml
	in API POST /api/v0/b2b/enroll/{readable_id}/
		added the new `b2b-error-no-matching-contract` enum value to the `result/allOf[#/components/schemas/ResultEnum]/` response property for the response status `201`
		The server may now return a value the previous contract excluded, so a client written against it may not handle the response. If the value set is meant to grow, declare it with x-extensible-enum. This compared the `allOf` branches one at a time, so another branch may still guarantee what this one dropped. Run with --flatten-allof to merge the branches and compare what they describe together.

warning	[response-property-enum-value-added] at head/openapi/specs/v0.yaml
	in API POST /api/v0/b2b/enroll/{readable_id}/
		added the new `b2b-error-no-matching-contract` enum value to the `result/allOf[#/components/schemas/ResultEnum]/` response property for the response status `400`
		The server may now return a value the previous contract excluded, so a client written against it may not handle the response. If the value set is meant to grow, declare it with x-extensible-enum. This compared the `allOf` branches one at a time, so another branch may still guarantee what this one dropped. Run with --flatten-allof to merge the branches and compare what they describe together.

info	[new-optional-request-property] at head/openapi/specs/v0.yaml
	in API POST /api/v0/b2b/enroll/{readable_id}/
		added the new optional request property `contract_slug` (media type: application/json)

info	[new-optional-request-property] at head/openapi/specs/v0.yaml
	in API POST /api/v0/b2b/enroll/{readable_id}/
		added the new optional request property `contract_slug` (media type: application/x-www-form-urlencoded)

info	[new-optional-request-property] at head/openapi/specs/v0.yaml
	in API POST /api/v0/b2b/enroll/{readable_id}/
		added the new optional request property `contract_slug` (media type: multipart/form-data)



## Changes for v1.yaml:
7 changes: 0 error, 4 warning, 3 info
warning	[response-property-enum-value-added] at head/openapi/specs/v1.yaml
	in API POST /api/v0/b2b/enroll/{readable_id}/
		added the new `b2b-error-ambiguous-contract` enum value to the `result/allOf[#/components/schemas/ResultEnum]/` response property for the response status `201`
		The server may now return a value the previous contract excluded, so a client written against it may not handle the response. If the value set is meant to grow, declare it with x-extensible-enum. This compared the `allOf` branches one at a time, so another branch may still guarantee what this one dropped. Run with --flatten-allof to merge the branches and compare what they describe together.

warning	[response-property-enum-value-added] at head/openapi/specs/v1.yaml
	in API POST /api/v0/b2b/enroll/{readable_id}/
		added the new `b2b-error-ambiguous-contract` enum value to the `result/allOf[#/components/schemas/ResultEnum]/` response property for the response status `400`
		The server may now return a value the previous contract excluded, so a client written against it may not handle the response. If the value set is meant to grow, declare it with x-extensible-enum. This compared the `allOf` branches one at a time, so another branch may still guarantee what this one dropped. Run with --flatten-allof to merge the branches and compare what they describe together.

warning	[response-property-enum-value-added] at head/openapi/specs/v1.yaml
	in API POST /api/v0/b2b/enroll/{readable_id}/
		added the new `b2b-error-no-matching-contract` enum value to the `result/allOf[#/components/schemas/ResultEnum]/` response property for the response status `201`
		The server may now return a value the previous contract excluded, so a client written against it may not handle the response. If the value set is meant to grow, declare it with x-extensible-enum. This compared the `allOf` branches one at a time, so another branch may still guarantee what this one dropped. Run with --flatten-allof to merge the branches and compare what they describe together.

warning	[response-property-enum-value-added] at head/openapi/specs/v1.yaml
	in API POST /api/v0/b2b/enroll/{readable_id}/
		added the new `b2b-error-no-matching-contract` enum value to the `result/allOf[#/components/schemas/ResultEnum]/` response property for the response status `400`
		The server may now return a value the previous contract excluded, so a client written against it may not handle the response. If the value set is meant to grow, declare it with x-extensible-enum. This compared the `allOf` branches one at a time, so another branch may still guarantee what this one dropped. Run with --flatten-allof to merge the branches and compare what they describe together.

info	[new-optional-request-property] at head/openapi/specs/v1.yaml
	in API POST /api/v0/b2b/enroll/{readable_id}/
		added the new optional request property `contract_slug` (media type: multipart/form-data)

info	[new-optional-request-property] at head/openapi/specs/v1.yaml
	in API POST /api/v0/b2b/enroll/{readable_id}/
		added the new optional request property `contract_slug` (media type: application/json)

info	[new-optional-request-property] at head/openapi/specs/v1.yaml
	in API POST /api/v0/b2b/enroll/{readable_id}/
		added the new optional request property `contract_slug` (media type: application/x-www-form-urlencoded)



## Changes for v2.yaml:
7 changes: 0 error, 4 warning, 3 info
warning	[response-property-enum-value-added] at head/openapi/specs/v2.yaml
	in API POST /api/v0/b2b/enroll/{readable_id}/
		added the new `b2b-error-ambiguous-contract` enum value to the `result/allOf[#/components/schemas/ResultEnum]/` response property for the response status `201`
		The server may now return a value the previous contract excluded, so a client written against it may not handle the response. If the value set is meant to grow, declare it with x-extensible-enum. This compared the `allOf` branches one at a time, so another branch may still guarantee what this one dropped. Run with --flatten-allof to merge the branches and compare what they describe together.

warning	[response-property-enum-value-added] at head/openapi/specs/v2.yaml
	in API POST /api/v0/b2b/enroll/{readable_id}/
		added the new `b2b-error-ambiguous-contract` enum value to the `result/allOf[#/components/schemas/ResultEnum]/` response property for the response status `400`
		The server may now return a value the previous contract excluded, so a client written against it may not handle the response. If the value set is meant to grow, declare it with x-extensible-enum. This compared the `allOf` branches one at a time, so another branch may still guarantee what this one dropped. Run with --flatten-allof to merge the branches and compare what they describe together.

warning	[response-property-enum-value-added] at head/openapi/specs/v2.yaml
	in API POST /api/v0/b2b/enroll/{readable_id}/
		added the new `b2b-error-no-matching-contract` enum value to the `result/allOf[#/components/schemas/ResultEnum]/` response property for the response status `201`
		The server may now return a value the previous contract excluded, so a client written against it may not handle the response. If the value set is meant to grow, declare it with x-extensible-enum. This compared the `allOf` branches one at a time, so another branch may still guarantee what this one dropped. Run with --flatten-allof to merge the branches and compare what they describe together.

warning	[response-property-enum-value-added] at head/openapi/specs/v2.yaml
	in API POST /api/v0/b2b/enroll/{readable_id}/
		added the new `b2b-error-no-matching-contract` enum value to the `result/allOf[#/components/schemas/ResultEnum]/` response property for the response status `400`
		The server may now return a value the previous contract excluded, so a client written against it may not handle the response. If the value set is meant to grow, declare it with x-extensible-enum. This compared the `allOf` branches one at a time, so another branch may still guarantee what this one dropped. Run with --flatten-allof to merge the branches and compare what they describe together.

info	[new-optional-request-property] at head/openapi/specs/v2.yaml
	in API POST /api/v0/b2b/enroll/{readable_id}/
		added the new optional request property `contract_slug` (media type: multipart/form-data)

info	[new-optional-request-property] at head/openapi/specs/v2.yaml
	in API POST /api/v0/b2b/enroll/{readable_id}/
		added the new optional request property `contract_slug` (media type: application/json)

info	[new-optional-request-property] at head/openapi/specs/v2.yaml
	in API POST /api/v0/b2b/enroll/{readable_id}/
		added the new optional request property `contract_slug` (media type: application/x-www-form-urlencoded)



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

@jkachel
jkachel force-pushed the jkachel/store-enrollment-contract branch 3 times, most recently from 4357f50 to 740af0c Compare September 21, 2026 20:07
jkachel and others added 8 commits September 23, 2026 10:20
…t, and a backfill migration

It will become harder to figure out whether or not a particular enrollment is for a B2B run or not, once we allow course runs to be in multiple contracts. So, adding a field to track that specifically (to be followed by API changes to ensure that the field is getting set properly).
no-verify commit because not done yet; need to work out changes to
`create_run_enrollments` still and some other things.
These will likely need to get renumbered again.
Rolls back the prior changes to try to link the contract in during
enroll and instead moves that elsewhere so I'm not messing with the
enrollment code. Adds a b2b_contract field to the line/basketitem so
that it can keep track of that later too. Did get purchases done (B2B
and not) and things worked as expected!
@jkachel
jkachel force-pushed the jkachel/store-enrollment-contract branch from dc685ca to a6ef61b Compare September 23, 2026 15:20
@jkachel
jkachel marked this pull request as ready for review September 23, 2026 20:33

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant