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" |
⚠️ Booking từ subscription dùng Subscription làm template configuration, nhưng không chuyển quyền sở hữu state runtime sang Subscription. State enrollment/booking chỉ được 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 các trạng thái: Submitted → Approved/Confirmed. Billing chỉ chạy cho enrollment active, approved và 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). |
Quy tắc quan trọng: 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 gồm: 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: giữ
IsActive = true, setStatusId = 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
Trang Billing Details hiển thị theo invoice (không phải EnrollmentInvoiceLog — log nội bộ bị ẩn). Các trạng thái liên quan:
| 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). Skip vẫn reset status kể cả khi không có scheduler. Hệ thống archive booking line liên quan và regenerate invoice hoặc issue credit note tùy 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.
Tiếp theo: Kiến trúc & dữ liệu.