-
Notifications
You must be signed in to change notification settings - Fork 46
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: enterprise entitlements and subsidy based fulfillment models he…
…irarchy rework
- Loading branch information
1 parent
734c9e7
commit a5fda34
Showing
12 changed files
with
670 additions
and
86 deletions.
There are no files selected for viewing
143 changes: 143 additions & 0 deletions
143
docs/decisions/0011-enterprise-subsidy-enrollments-and-entitlements
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
## Enterprise subsidy enrollments and entitlements | ||
|
||
# Status | ||
|
||
Draft | ||
|
||
# Context | ||
|
||
Enterprise course enrollment and course entitlement records can come into existence from different types of subsidies: | ||
|
||
- Subscription licenses - we’d want to track the license uuid at time of enrollment creation. | ||
|
||
- Learner credit subsidy transactions - we’d want to track the transaction uuid at time of enrollment or entitlement creation. | ||
|
||
- Coupon codes - not yet tracked in the context of this ADR. | ||
|
||
On top of architecting the tables to support sub licenses and credit transactions we want to build a system that can support other, not yet known types of subsidies as there is the chance that new ones will be added in the future. | ||
|
||
We also will very likely need to create a new EnterpriseCourseEntitlement model, which would mirror the purpose of the EnterpriseCourseEnrollment model but instead track a subsidy that is not yet connected to a course enrollment and will support upgrading to said enrollment. | ||
|
||
**Why implement table inheritance and what is `EnterpriseFulfillmentSource`?** | ||
|
||
- Having concrete table inheritance (`EnterpriseFulfillmentSource` being the parent model of all enterprise related subsidy based enrollments) means that we have a single entry point table that every kind of transaction/enrollment/entitlement/etc will extend from. | ||
|
||
- Transaction and fulfillment type are encapsulated by a singular model (or the model name). | ||
|
||
- We can more easily track the entitlement to enrollment lifecycle. | ||
|
||
- The null/not-null data integrity constraints are easy to grok and helps keep our data valid. | ||
|
||
- Table inheritance means that things are easily extendable. As we onboard new types of fulfillments, we can simply add new child models. | ||
|
||
**Why have `EnterpriseCourseEntitlement`s?** | ||
|
||
There are situations where enterprise admins give a learner a subsidy for a specific course, but that course does not yet have a valid run for the learner to enroll in. Entitlements allow us to track the subsidy for the specific user before the enrollment can be created so that when the time comes for the learner to start/create the enrollment, the entitlment can be easily converted and used. These entitlements can also be easily gathered, tracked and provided as metric data for the admins of any enterprise customer. | ||
|
||
It is also important that these models link to and mirror the state of the B2C enrollment and entitlement models, such that there is a 1:1 relationship between B2C and B2B rows for enterprise related enrollments and entitlements. Meaning that when we write an enterprise enrollment or entitlement, we should also create the B2C counterpart for that record. | ||
|
||
**Personas for whom this is relevant** | ||
|
||
Anyone who wants to enroll in Exec Ed courses through the edX Enterprise system + anyone who wants reporting on Exec Ed enrollments via edX Enterprise system, e.g. | ||
|
||
Learning and Development managers at BigTech, Inc. | ||
|
||
Libby Learner, an aspiring executive at Any Co. | ||
|
||
This will apply not only to enterprise customers with learners consuming Executive Education content (the catalyst for this change), but to all subsidy based consumptions for B2B customers. | ||
|
||
**Benefits of this rework** | ||
|
||
- It’ll make Executive Education (and lay the ground work for future external content integrations) subsidy consumption “standardized” with subsidy consumptions related to `edx.org`. | ||
|
||
- We can do this in a de-coupled way with Event-bus and/or polling. | ||
|
||
- We can easily support new subsidy types | ||
|
||
# Decisions | ||
|
||
**A rework of the enterprise subsidy enrollment models and creation of enterprise entitlements** | ||
|
||
The new enterprise entitlement table: | ||
|
||
``` | ||
EnterpriseCourseEntitlement | ||
--------------------------- | ||
- uuid, created, modified, history (boilerplate) | ||
- enterprise_customer_user_id (NOT NULL, FK to EnterpriseCustomerUser) | ||
- enterprise_course_enrollment_id (NULL, FK to EnterpriseCourseEnrollment) | ||
- converted_at (NULL DateTime). | ||
- (cached property) course_entitlement_id (query look up of related CourseEntitlement) | ||
|
||
TBD: | ||
-- A built in method of entitlement conversion to enrollment | ||
``` | ||
|
||
Reworked and added table inheritance to all subsidy based enrollment tables. As such all subsidy based fulfillment records will have access to these fields: | ||
|
||
``` | ||
EnterpriseFulfillmentSource | ||
--------------------------- | ||
- uuid, created, modified, history (boilerplate) | ||
- fulfillment_ty (NOT NULL, char selection: (`license`, `learner_credit`, `coupon_code`)) | ||
- enterprise_course_entitlement (NOT NULL, FK to EnterpriseCourseEntitlement) | ||
- enterprise_course_enrollment (NOT NULL, FK to EnterpriseCourseEnrollment) | ||
- is_revoked (Default False, Bool) | ||
``` | ||
|
||
Models inheriting `EnterpriseFulfillmentSource`: | ||
|
||
``` | ||
LicensedEnterpriseCourseEnrollment (inherited from EnterpriseFulfillmentSource) | ||
------------------------------------------------------------------------------- | ||
- license_uuid (NOT NULL, UUID field) | ||
``` | ||
|
||
``` | ||
LearnerCreditEnterpriseCourseEnrollment (inherited from EnterpriseFulfillmentSource) | ||
------------------------------------------------------------------------------------ | ||
- transaction_id (NOT NULL, UUID field) | ||
``` | ||
|
||
[NOTE] Even though these models are labeled as `...Enrollment`s, they can reference entitlements as well as enrollments. In fact, despite both `enterprise_course_entitlement` `enterprise_course_enrollment` both being nullable, there is validation on the `EnterpriseFulfillmentSource` which will guarantees one of these values must exist. | ||
|
||
To support interactions with these reworked and new models, we've buffed out the bulk enrollment (`enroll_learners_in_courses`) EnterpriseCustomerViewSet view to support subsidy enrollments. `enrollment_info` parameters supplied to the endpoint can now include transaction ID's that will detected and realized into a `LearnerCreditEnterpriseCourseEnrollment` record. | ||
|
||
**How we'd use this in code** | ||
|
||
``` | ||
# In the parent class... | ||
@classmethod | ||
def get_fulfillment_source(cls, enrollment_id, entitlement_id=None): | ||
return cls.objects.select_related( | ||
# all child tables joined here | ||
).filter( | ||
cls.enterprise_course_enrollment=enrollment_id | ||
) | ||
# do kwargs stuff here to optionally pass in a non-null | ||
# entitlement id to filter by... | ||
|
||
@property | ||
def fulfillment_status(self): | ||
if not self.enterprise_course_enrollment: | ||
return 'entitled' | ||
return 'enrolled' | ||
``` | ||
|
||
# Consequences | ||
|
||
- Table inheritance means that we’ll most likely have to do JOINs in our code and in our analytics/reporting. | ||
|
||
- There exists a subsidy based enrollment table already (`LicensedEnterpriseCourseEnrollment`), this table and it's records will need to be migrated to the table inheritance structure which would complicate our django migration hierarchy. | ||
|
||
# Further Improvements | ||
|
||
- Verify transaction ID's are real on creation through the bulk enrollment endpoint | ||
- Add a programatic way to turn entitlements into enrollments | ||
- Continue extending the `enroll_learners_in_courses` endpoint to support bulk entitlement creation of entitlements. (suggestion here is that if course run keys are supplied for enrollments, if course uuid's are supplied then we generate entitlements instead) | ||
|
||
# Alternatives Considered | ||
|
||
- `One big table`: Jam everything into one big table; almost every field is optional - might do code-level validation in the model’s save() method to ensure the presence of non-null fields depending on type of fulfillment. | ||
|
||
- `Table-hierarchy based on FK relationships`: Instead of strict inheritance, we could implement subsidy based tables that rely on foreign keys instead. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.