Subscription — Khái niệm cốt lõi
Subscription vs Enrollment: template vs runtime
Đây là phân biệt quan trọng nhất:
| Subscription | Enrollment | |
|---|---|---|
| Là gì | Template/sản phẩm: giá, lịch, điều kiện | Một lần ghi danh thực tế của attendee |
| Tính chất | Cấu hình tĩnh (admin tạo) | Runtime, có vòng đời/trạng thái |
| Ví dụ | "Gói Toán học kỳ 1" | "Bé An ghi danh gói Toán học kỳ 1" |
⚠️ Subscription là template configuration; state runtime không chuyển sang nó. State enrollment/booking chỉ sửa qua các service confirmation hiện có — UI setup không trực tiếp sửa Enrollment/Invoice/Booking.
Vòng trạng thái Enrollment
Enrollment đi qua: Submitted → Approved/Confirmed. Billing chỉ chạy cho enrollment active, approved, khớp billing schedule + term context.
BillingSchedule: nguồn canonical cho lựa chọn kỳ
Có hai mức lịch, đừng nhầm:
| Lịch | Nghĩa |
|---|---|
| SubscriptionTermBillingSchedule | Lịch "khả dụng" của subscription term (tất cả kỳ có thể book). |
| EnrollmentBillingSchedule | Lịch đã book cho một enrollment cụ thể (chỉ các kỳ thực sự đặt). |
Manage Add-ons / Manage Discounts chỉ hiện các kỳ thuộc EnrollmentBillingSchedule (đã book), không hiện kỳ chưa book. Submit kỳ chưa book → bị từ chối.
Khi booking, UI lấy period từ API:
GET /api/ConsumerEnrollment/Subscriptions/{id}/BillingSchedulesMỗi row: scheduleId ổn định, context subscription/term, BookingStart, BookingEnd. BookingStart/BookingEnd ưu tiên hơn billing period start/end khi hiển thị & submit.
SubscriptionTerm: vòng đời link/archive theo StatusId
SubscriptionTerm (bản ghi link subscription ↔ Term) có field StatusId tách biệt với IsActive. Đừng nhầm hai khái niệm:
| Trạng thái | IsActive | StatusId | Ý nghĩa |
|---|---|---|---|
| Approved | true | EntityStatus.Approved (500) | Linked term active, dùng bình thường. |
| Archived | true | EntityStatus.Archived (600) | Đã unlink nhưng còn booking usage; giữ active, không xóa. |
| Soft deleted | false | (bất kỳ) | Xóa mềm theo protocol Aimy; không phải archive, không restore từ UI. |
⚠️
IsActive = falseluôn là soft delete. Archive không dùngIsActive = false(và không dùng kiểu "zero-column" encode qua schedule). Archived term vẫnIsActive = true, chỉ khácStatusId = 600.
Quy tắc hiển thị theo từng surface (chỉ tính term IsActive = true):
| Surface | Approved (500) | Archived (600) | Inactive (IsActive=false) |
|---|---|---|---|
| Booking UI (chọn kỳ mới) | ✅ hiện | ❌ ẩn | ❌ ẩn |
| Subscription Manager | ✅ hiện | ✅ hiện | ❌ ẩn |
| Subscription Billing Manager | ✅ hiện | ✅ hiện | ❌ ẩn |
- Billing chỉ tính schedule khi
schedule.IsActive = truevà parent termIsActive = truevớiStatusId ∈ {500, 600}, vàSubscription.IsActive = true. - Bất biến:
INV-SUB-36,INV-SUB-37,INV-SUB-BM-01/02.
Unlink/re-link booked term: archive thay vì xóa
- Unlink term có booking usage → archive (
IsActive = true,StatusId = 600); không đụngSubscriptionTermBillingSchedule(giữ cho Manager & Billing). - Unlink term không có booking usage → soft delete như cũ (
IsActive = false). - Re-link term đã archived (cùng subscription + term,
IsActive=true,StatusId=600) → restore: setStatusId = 500, giữ nguyênProductIds+ schedules, bỏ qua popup chọn product, không tạo bản ghi trùng. - Link khi không có archived row active → tạo linked term mới + mở popup chọn product như cũ.
- Term
IsActive = falsekhông bao giờ được restore qua UI link flow.
Invoice: vòng trạng thái tài chính
Billing Details hiển thị theo invoice (không phải EnrollmentInvoiceLog — log nội bộ bị ẩn):
| Trạng thái | Khi nào |
|---|---|
Initialised (0) | Sau khi Skip; trạng thái nền. |
Generated (75) | Invoice vừa sinh khi vào workflow detail page. |
Approved | Sau khi Approve. |
Hành động ↔ invoice (không tác động EnrollmentInvoiceLog):
Add-on & Discount theo kỳ
- Áp theo từng kỳ (period) của enrollment, không phải toàn enrollment.
- Đại diện bằng cặp (Add-on/Discount, EnrollmentSchedule period).
- Period rows lấy từ EnrollmentSchedule đã book, không sinh từ price type của add-on.
- Bulk select-all chỉ xuất hiện khi nhóm có > 3 kỳ.
- Lưu = "desired state": check = áp, uncheck = gỡ; trùng cặp trong request → từ chối.
Skip: reset một kỳ
Skip là hành động cấp invoice: hủy scheduler đang chạy (nếu có) + đưa invoice về Initialised (0) — vẫn reset status dù không có scheduler. Archive booking line liên quan, rồi regenerate invoice hoặc issue credit note theo trạng thái invoice trước đó — giữ nhất quán tài chính.
BookingAndBilling vs BillingOnly
| BookingAndBilling | BillingOnly (TypeId=1) | |
|---|---|---|
| Program section | Hiện, validation active | Ẩn, không chặn save/publish |
| Cần SubscriptionProduct để publish | Có | Không |
| Link term cần product planned check | Có | Không |
Loại được chọn trước khi tạo; sau khi subscription tồn tại thì không đổi được.
Yêu cầu booking từ Product (requirements)
Yêu cầu attendee khi booking được resolve từ Product trong Program của subscription, theo term của schedule được chọn (SubscriptionTerm.ProductIds).
| Dimension | Đánh giá |
|---|---|
| Age | Trong khoảng tuổi Product yêu cầu. |
| School Year | Khớp năm học Product yêu cầu. |
| Level | So Attendee.Level (nguồn chính thức) với MinLevel/MaxLevel của Product. |
| Height / Weight | Trong khoảng đo Product yêu cầu. |
| Swimming Competency | So bằng: Attendee.SwimmingCompetency == giá trị Product (không phải range). |
Quy tắc:
- Dimension không có bound (Product không cấu hình) hoặc
<Reference>_Enabled = false→ coi như chưa cấu hình, không chặn booking. - Nhiều Product cùng hiệu lực yêu cầu Swimming khác nhau → dimension coi như không cấu hình.
- Attendee phải thỏa tất cả dimension enforce; staff tắt toggle (Admin Mode, transient) → bỏ qua dimension đó; customer không bypass được.
- Không đạt → chặn chọn schedule ở Step 1 và reject submit ở Step 2 (backend revalidate).
Ví dụ thực tế: Bé An (SwimmingCompetency = "Non Swimmer") book gói bơi yêu cầu Competent. → Chặn chọn/submit khi thiếu năng lực. → Chọn schedule bị chặn (dimension Swimming Competency), submit bị reject; staff tắt toggle tạm thời được.
Tiếp theo: Kiến trúc & dữ liệu.