-
Notifications
You must be signed in to change notification settings - Fork 0
Program Pricing Requirement
The Program Pricing system defines how course and resource fees are managed, tracked, and versioned. The system must handle:
Flat course fees
Resource-based fees (labs, exams, materials)
Price changes over time
Full historical tracking
This ensures billing accuracy, auditability, and immutability of past charges.
Pricing is not static. Over time:
Course flat fees may increase or decrease
Lab fees may change
Resource fees may be added, removed, or updated
Overwriting pricing records leads to:
Incorrect student billing
Broken financial reports
Loss of audit trail
Solution: Preserve pricing history via versioned records.
Prices are time-based facts
Historical prices are never overwritten
Every price change creates a new record
Historical prices must always be queryable
Pricing setup is separate from enrollment records
The system supports:
Flat course fees
Lab fees
Resource/material fees
Exam fees
Any additional chargeable items
All fees follow the same versioning rules.
The ProgramPricing table was initially designed to store all pricing types and maintain history.
CREATE TABLE ProgramPricing (
ID INT PRIMARY KEY AUTO_INCREMENT,
ProgramID INT NOT NULL, -- Reference to the program the price belongs to
PricingScope VARCHAR(20) NOT NULL, -- COURSE or RESOURCE
ScopeReferenceID INT NOT NULL, -- ProgramCourse or ProgramResource
PricingType VARCHAR(20) NOT NULL, -- FLAT, LAB, EXAM, MATERIAL, etc.
Price DECIMAL(10,2) NOT NULL,
IsMandatory BOOLEAN DEFAULT TRUE,
EffectiveFrom DATE NOT NULL,
EffectiveTo DATE DEFAULT NULL,
CreatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
This version worked conceptually but introduced ambiguity and tight coupling as the system evolved.
This document describes the finalized pricing system design based strictly on the current implementation.
It explains why certain fields were removed, why new fields were introduced, and how the model logically converged into its final form.
The goal of this design is clarity, auditability, and long-term scalability — not short-term convenience.
Pricing is treated as:
A time-based financial fact attached to an entity
Not a configuration flag.
Not a business rule.
Not an enrollment decision.
Early designs assumed:
All pricing belongs to a program
Courses, labs, and resources are secondary
This assumption failed quickly.
Course fees depend on program + course
Lab fees depend on course, not program
Resource fees may belong to labs or courses
-
ProgramIDhad only one real use but introduced:Ambiguous ownership
Hard-coded hierarchy
Increasing query complexity
Remove ProgramID.
Introduce parent_id in the ProgramCourses table:
parent_idindicates the owning entity of the course pricing.For courses: parent is the program it belongs to.
For labs/resources: parent is the course it belongs to.
This allows hierarchical ownership without hardcoding assumptions.
It makes joins deterministic and enables storing all entity types in a single table.
Introduce a generic ownership reference in pricing:
ref_id → ProgramCourses.id
Pricing now attaches to the actual owning entity.
Entity | Ownership -- | -- Program tuition | PROGRAM Course tuition | COURSE Lab fee | LAB Resource fee | RESOURCEHierarchy resolves ownership — not column assumptions.
At the pricing layer:
“Mandatory” had no stable definition
-
It varied by:
intake
enrollment rules
scholarships
policy changes
Embedding it early would:
Freeze business assumptions
Force schema changes later
Mix enrollment logic into pricing
Remove is_mandatory.
Pricing answers one question only:
What was the price at a given point in time?
Mandatory vs optional belongs to enrollment rules, not pricing.
With only ref_id:
A pricing record exists
-
But its meaning is unclear:
Program?
Course?
Lab?
Resource?
This leads to:
Unsafe joins
Application-side guessing
Hidden coupling
Add explicit semantic context:
ref_type ENUM('PROGRAM','COURSE','LAB','RESOURCE')
Now each pricing record is:
Self-describing
Query-safe
Deterministic
Without ref_type, the table is ambiguous.
With ref_type, the table explains itself.
ProgramCoursePricings
---------------------
id (PK)
parent_id -- refers to parent of the record. course stores program id, lab stores course id
price_type -- TUITION_FEE, LAB_FEE, MATERIAL_FEE, EXAM_FEE, FLAT_FEE, OTHER_FEE
price
effective_date
effective_to -- NULL = current active price
ref_id -- FK → ProgramCourses.id to determine whether its lab or course or program
ref_type -- PROGRAM / COURSE / LAB / RESOURCE
createdAt
updatedAt
(ref_id, price_type, effective_date)
This enforces:
One price per entity per type per date
Clean historical timelines
Append-only pricing history
Prices are never overwritten — only superseded.
This pricing system now:
Treats pricing as immutable historical data
Attaches prices to entities, not assumptions
Preserves financial auditability
Avoids premature business rules
Scales without schema rewrites
No magic columns.
No hidden logic.
No future cleanup migrations.
Pricing is not a property.
It is a historical event attached to an entity.
This design is optimized to survive years of change, not just pass initial implementation.