Subscription — Luồng xử lý trong code
Subscription trải trên cả frontend (Tuke/Topi) và backend (Tux). Trang này nêu các đường chính; tên API/spec là điểm neo để tra cứu.
Luồng booking (khách hàng submit)
Guard trùng lịch (overlap validation)
Khi submit/edit/confirm, hệ thống so các schedule rows submit với:
- Enrollment đang
Submitted/Confirmed(soEnrollmentPattern). - TermBookingOrder normal đang
Submitted/Quote/Submitted_Change. - TermBooking normal đang
Approved/Attendance_Approved.
Trùng bất kỳ → toàn request fail (atomic, không tạo state một phần). Edit loại trừ EnrollmentId hiện tại để không tự xung đột. Direct-confirm & future-confirm dùng cùng guard.
Direct-confirm & Submit & Confirm (admin)
- Chỉ admin/staff được dùng direct-confirm; khách/non-admin → từ chối.
- Dùng Subscription làm template, nhưng mutate runtime chỉ qua service confirmation hiện có (không sửa trực tiếp Enrollment/Invoice/Booking từ UI setup).
- Response không báo "confirmed success" trừ khi runtime đã hoàn tất confirmation semantics.
- Submit & Confirm: submit với direct-confirm intent; thành công → confirmed ngay, không cần vào Booking Manager. Submit thường giữ pending/submitted.
- Edit mode của Subscription dùng direct-confirm intent như flow hiện có; không gọi
PreviewBillingDifference. Billing Difference chỉ áp dụng cho eligible Admin Edit normal booking. - Confirm/Terminate/Cancel/Submit đều nhận mute notification intent (Mute Notifications); mute chỉ bỏ email, không đổi lifecycle/validation/billing.
Subscription Enrichment: preview → submit → confirmed Booking
POST /api/ConsumerEnrollment/EnrichmentPricingPreviewchỉ đọc; preview stale, lỗi hoặc không resolve được configuration thì Step 2 không được tiếp tục.- Submit không nhận count, rate, amount, invoice id hay credit-note id làm authority; server resolve lại từ TermProduct trước mutation.
- Confirmed generation giữ line normal Subscription zero-priced, tạo line Enrichment có
RateStandardItemId,Price,Quantity = 1,Amount,TotalAmountvà attendance tương ứng. - Closure day, inclusive
BookingStart/BookingEnd, Mon–Fri, dedupe và overlap phải giống nhau giữa preview, submit và generation.
Ví dụ thực tế: User đổi selection từ Mon–Fri sang Mon–Wed trong lúc preview cũ đang trả về. → Client bỏ qua response cũ theo fingerprint. → Toolbar chỉ hiển thị amount của Mon–Wed hiện tại.
Billing Details: Schedule / Approve / Skip
| Hành động | Backend làm |
|---|---|
| Schedule | Tạo một InvoiceScheduler / invoice; ScheduledOn = Invoice.Date (UTC). |
| Approve | Update invoice → Approved; tạo InvoiceScheduler với ScheduledOn = DateTime.UtcNow. |
| Skip | Deactivate scheduler đang active (nếu có); update invoice → Initialised (0). |
Trang derive intent nút từ trạng thái invoice, không từ
EnrollmentInvoiceLog.
Subscription Billing Manager
Filter Site + Subscription (bắt buộc) + Term (tùy chọn); nguồn options:
GET /Subscription/WithTerms(không/Subscription/List, không đọcPeriods). Bấm Apply để load bảng.Mỗi row = một
SubscriptionTermBillingSchedule(gồm term archivedStatusId=600): Term, Period, Invoice Date, Due Date, Stats (generated/expectedtheo enrollment active-approved), Action.View → redirect Billing Details với context site/subscription/term. Non-SuperAdmin (
BusinessUnit.TypeId ≠ 99): manager read-only, không có approve/skip/generate.SuperAdmin (
TypeId = 99): row có Calculate → drawerGET /api/subscription-billing/ScheduleSummary/{id}(+POSTkhi chưa có summary) rồi Generate Subscription Booking (POST /api/ConsumerEnrollment/BillingEnrollment/{scheduleId}); đóng drawer → refresh grid.Sinh invoice yêu cầu schedule có
SubscriptionPriceId+SubscriptionPriceLineactive (kể cảUnitPrice = 0.00); thiếu → validation fail trước khi persist, không fallback theo schedule amount.Billing completeness chỉ đếm
EnrollmentBillingScheduleactive thuộc đúng schedule đang mở; không đếm toàn bộEnrollmentPeriodcủa cùng Subscription/Term.
Manage Add-ons / Discounts
Chỉ mutate state add-on selection thuộc enrollment; billing side effect đi qua service backend (popup không trực tiếp approve/skip/schedule/regenerate invoice).
Manage Enrichments cho Subscription Booking
- Subscription Manager thêm action Manage Enrichments riêng với Manage Add-ons.
- Read trả product, occurrence date, selected state, amount và lock reason; read không mutate state.
- Write chỉ nhận Booking +
TermProductId + Date; server kiểm tra BU/site/Subscription/attendee/Term, eligibility, invoice/credit/attendance/Booking lock và lưu atomic. - Chỉ occurrence tương lai, chưa invoice/credited/attended và chưa lock được thêm/bỏ. Invoiced change cần flow adjustment của Booking/Finance; Manage Enrichments không tự ghi Invoice/CreditNote/log.
- Save retry phải idempotent; nhiều request cùng semantic key chỉ còn tối đa một active line.
Ví dụ thực tế: Staff gửi một request có một ngày hợp lệ và một ngày ngoài Term. → API reject toàn request. → Không thêm ngày hợp lệ dở dang và không thay đổi invoice/credit log.
Setup: các quy ước cần giữ khi code
| Quy ước | Ghi chú |
|---|---|
| Archive chỉ khi unpublished | Gói đã publish → server từ chối archive. |
CutOffMinute lưu phút, UI ngày | Decimal bị reject; 0/null = không có cut-off. |
Billing Type khóa Flat Rate | Request Dynamic bị reject/normalize. |
BillingOnly editable trước khi tạo | Sau khi tồn tại → read-only. |
| Add Price mới = blank | Không prefill từ price đã xem/lưu. |
| Rollover alert bị mute | Notification-only: không đổi state booking/enrollment/billing/term planner. |
Thông báo vòng đời booking
- Submitted (còn pending) →
New Subscription Booking (Client)cho khách +(Staff)cho admin/staff. - Confirmed →
Subscription Booking Confirmed (Client); Cancelled →(Client)kèm site contact phone/email. - Submit & Confirm → không queue submitted/pending; chỉ confirmed.
- Mute intent (admin action) → không queue/send email đáng lẽ gửi; lifecycle vẫn persist.
- Notification được tạo sau khi lifecycle đã persist; render chỉ đọc, không mutate state.
- Placeholder resolve từ context:
,,,,,, v.v.
Frontend (Subscription Manager)
- Filter subscription:
GET /api/Subscription(admin). Site filter giới hạn options theo site. - Đổi filter → không tự reload; bấm Apply mới load bảng theo filter hiện tại. Load đầu trang tự chạy; mutation (confirm/cancel/delete/terminate/rollover) thành công cũng refresh. Response request cũ không ghi đè data mới (chống race khi switch nhanh).
- Expanded row: hiện add-ons rồi discounts; có nút Update mở Manage Add-ons.
Nơi tìm code
Frontend ở Tuke/ (Next.js) và một phần Topi/ (admin cũ). Backend + API ở Tux/ (ConsumerEnrollment, Subscription, Invoice services).
Flow mới trong implementation
api/SubscriptionManager/*là boundary admin cho reads và lifecycle (Confirm/Cancel/Terminate/ Delete/Notes/Rollover); không còn dùng account fallback khi thiếu Business Unit scope. Các flow customer như Extend, self-service rollover và My Bookings giữ ở consumer surface.- Extend đọc schedule cùng Term, lock schedule đã book/đã qua ngày, rồi persist period mới trong transaction; Rollover dùng job Individual/Batch và natural-key guard chống duplicate.
- Setup guard kiểm tra publish state ở service layer trước mọi write locked. Frontend chỉ disable để phản hồi sớm; API vẫn là enforcement cuối.
- Flyer upload validate manifest trước save;
not sent= preserve,[]= clear, list mới = replace. Public read chỉ trả metadata render được. - Billing Auto dùng scheduled job hiện có; sau generation thành công job trỏ tới kỳ chưa billed kế tiếp. Manual generation cũng cập nhật billed status.
Ví dụ thực tế: Hai worker cùng nhận Rollover Batch. → Không tạo hai period đích. → Job claim và kết quả idempotent theo target; lần chạy lại chỉ báo skipped cho period đã xử lý.
Tiếp theo: Case thường gặp.