Subscription — Kiến trúc & dữ liệu
Phác họa các "vật thể" dữ liệu chính + quan hệ giữa chúng — góc nhìn khái niệm, không phải schema chính xác.
Sơ đồ quan hệ (mức khái niệm)
Hai mức BillingSchedule
Manage Add-ons/Discounts chỉ dùng
EnrollmentBillingSchedule(đã book). Invariant:INV-SUB-ADDON-SCHEDULE-001,INV-SUB-DISCOUNT-SCHEDULE-001.
Enrichment flow và ownership boundary
- Setup chỉ expose option có Product active/approved, TermProduct đúng Term/Program scope, rate và accounting item hợp lệ.
- Preview, submit và confirmed generation dùng cùng occurrence resolver; client chỉ gửi identity (
subscriptionProductId,weeklyPatternhoặcTermProductId + Datekhi quản lý). TermBookingLinelà owner của phí Enrichment; không ghi phí đó vàoEnrollmentExtra,SubscriptionTermBillingSchedulehoặc Subscription Billing invoice.TermBookingId + TermProductId + Datelà semantic key; replay không tạo duplicate active line.
Ví dụ thực tế: Một product Enrichment bị bỏ RateStandardItemId sau khi Setup. → Preview/submit không tính amount phỏng đoán. → Hệ thống fail closed trước khi tạo Enrollment, Booking hoặc Invoice.
Ranh giới sở hữu (ownership)
Quan trọng để không phá vỡ nhất quán:
| Khối | Ai được sửa |
|---|---|
| Subscription (template, price, product, content) | UI Setup |
| Enrollment / TermBookingOrder / TermBooking / Billing / Invoice / CreditNote (runtime) | Chỉ các service booking/enrollment/confirmation hiện có |
UI Setup / popup add-on KHÔNG trực tiếp approve/skip/schedule/regenerate invoice. Mọi recalculation billing đi qua service backend sở hữu hành vi đó.
Các API chính (đường đọc/ghi)
| Mục đích | API |
|---|---|
| Lịch thanh toán cho booking | GET /api/ConsumerEnrollment/Subscriptions/{id}/BillingSchedules |
| Metadata subscription (giá, product, linked term) | GET /api/ConsumerEnrollment/Subscriptions/{id} |
| Submit enrollment | POST .../ConsumerEnrollment/Submit |
| Options cho Subscription Manager filter | GET /api/Subscription (admin, gồm cả unpublished) |
| Options cho Billing Manager filter | GET /Subscription/WithTerms (không dùng /Subscription/List) |
| Options booking (khách) | GET /api/ConsumerEnrollment/Subscriptions |
| Enrichment pricing preview | POST /api/ConsumerEnrollment/EnrichmentPricingPreview (authenticated, read-only) |
| Manage Enrichments | GET/POST /api/SubscriptionManager/Enrollment/Enrichments/Manage (admin scope) |
Manager filter dùng
GET /api/Subscription(admin, thấy cả gói chưa publish); Billing Manager dùng/Subscription/WithTerms(không đọcPeriods); booking khách dùngConsumerEnrollment(chỉ gói bookable). Ba đường không lẫn nhau.
Đơn vị & quy ước dữ liệu đáng nhớ
| Field | Quy ước |
|---|---|
Subscription.CutOffMinute | Lưu phút; UI hiển thị ngày nguyên (1 ngày = 1440). Decimal bị từ chối. |
Subscription.TypeId | null/0 = BookingAndBilling; 1 = BillingOnly. |
| Booking Start/End Selection | 0 = Staff Only (mặc định); khác = Staff & Customer. |
| Billing Type | Khóa cứng Flat Rate (Dynamic chưa sẵn sàng). |
Term & booking liên quan
- Term = kỳ học. Subscription link tới term qua
SubscriptionTerm(fieldLinked). - Booking "normal" (
TermBookingOrder/TermBooking) là luồng khác subscription nhưng giao thoa khi validate trùng lịch — xem domain Booking và Case thường gặp.
Public-safe vs private
API hiển thị cho khách (Course List, Step 1) chỉ lộ public-safe: title, price info, weekday availability, mô tả, site map metadata, closure-day. Không lộ booking/enrollment/account/ attendee/invoice/payment private. Mọi đường hiển thị read-only (không mutate state).
Các capability runtime đã có
| Capability | Boundary chính |
|---|---|
| Extend | Đọc SubscriptionTermBillingSchedule cùng Term, ghi period mới trên Enrollment; không đụng period/invoice cũ. |
| Rollover | Job Individual/Batch chuyển enrollment sang linked target Term; idempotent theo Subscription + action + target. |
| Duplicate | Clone selected config sang Site trong cùng BU; bản mới Unpublished/Unbookable, không có customer/finance state. |
| Flyer | Subscription.FlyerBlob giữ ordered manifest; admin đọc đầy đủ, public chỉ đọc URL/path qua visibility guard. |
| Auto billing | Job hiện có tự chạy theo earliest unbilled invoice date và reschedule; Manual/Auto dùng chung generation core. |
| Invoice split | InvoiceSplitRuleId nhóm schedule chưa invoiced; invoice line vẫn giữ attendee ownership. |
Ví dụ thực tế: Một request Setup vừa đổi
Namevừa đổiPricetrên gói Published. → Không lưu nửa vời. → Service thấy field locked và reject toàn request; đổi Name riêng vẫn được.
Tiếp theo: Luồng xử lý trong code.