Subscription — Case thường gặp
"Khách book bị báo trùng lịch"
Hệ thống cố ý chặn double-booking. Submit → so schedule với enrollment/booking đang có của attendee; trùng → cả request fail, không tạo state một phần.
- Edit một enrollment: EnrollmentId hiện tại được loại trừ khỏi check (không tự xung đột).
- Direct-confirm / future-confirm: dùng cùng guard → cũng bị chặn nếu trùng.
Spec:
subscription-booking-double-booking-guard+ requirement "validates existing enrollment schedules before runtime state creation". Phía normal booking xem Booking → Case thường gặp.
Ví dụ thực tế: Trẻ đã có enrollment After School Thứ 4; phụ huynh book thêm gói trùng đúng chiều Thứ 4. → Tránh ghi nhận trẻ ở hai chỗ cùng giờ. → Cả request fail lúc submit, không tạo enrollment một phần.
"Manage Add-ons không thấy kỳ tôi muốn"
Popup chỉ hiện các kỳ thuộc EnrollmentBillingSchedule (đã book), không hiện kỳ chưa book của subscription term. Submit kỳ chưa book → bị từ chối.
- Bulk "select all" chỉ hiện khi nhóm có > 3 kỳ.
- Period rows lấy từ enrollment schedule, không từ price type của add-on.
Ví dụ thực tế: Admin thêm add-on bữa ăn cho Term 3 nhưng trẻ chưa book Term 3. → Chỉ gắn add-on vào kỳ đã thực sự book. → Term 3 không hiện trong popup; cố submit → bị từ chối.
"Skip một kỳ thì hóa đơn ra sao?"
Skip đưa invoice về Initialised (0), hủy scheduler đang active (nếu có). Tùy trạng thái invoice trước đó: regenerate invoice hoặc issue credit note để giữ nhất quán tài chính, đồng thời archive booking line liên quan.
Ví dụ thực tế: Gia đình đi nghỉ, xin bỏ kỳ tháng 7 đã xuất hóa đơn. → Không thu phí kỳ trẻ không dùng. → Skip → invoice
Initialised, credit note đảo phần đã issue, archive booking line tháng 7.
"Tạo gói BillingOnly mà không cần product"
BillingOnly (TypeId=1):
- Ẩn Program section; không chặn save/publish.
- Publish được dù không có
SubscriptionProduct. - Link term không cần product planned check.
Trong khi BookingAndBilling vẫn bắt buộc có product để publish + planned check khi link term.
Lưu ý: chọn loại trước khi tạo; sau khi subscription tồn tại thì read-only.
Ví dụ thực tế: Trung tâm thu phụ phí cơ sở vật chất hàng kỳ, không gắn buổi học cụ thể nào. → Lập gói chỉ để tính tiền, khỏi cấu hình product. → Tạo gói
BillingOnly, publish ngay dù không cóSubscriptionProduct.
"Không archive được gói"
Chỉ archive được gói unpublished (Unbookable). Gói đã publish → Current tab không cho archive, request trực tiếp bị server từ chối. Archive/restore không đổi publish status.
Ví dụ thực tế: Admin muốn dọn gói "Term 1 2025" đang mở bán. → Tránh ẩn nhầm gói khách vẫn book được. → Unpublish trước; gói còn bookable → nút archive bị chặn, server từ chối.
"Unlink term có booking xong vẫn thấy trong Billing nhưng mất ở Booking"
Đúng như thiết kế. Unlink term đang có booking usage → archive (giữ IsActive = true, set StatusId = 600), không xóa schedule.
- Booking UI: term archived không còn để khách chọn kỳ mới.
- Subscription Manager & Subscription Billing Manager: vẫn thấy term + schedule archived để xử lý booking/billing đang chạy.
- Nếu unlink term không có booking → soft delete bình thường (
IsActive = false), biến mất khỏi cả ba surface.
Term
IsActive = falselà soft delete, không phải archive — không restore được từ UI.
Ví dụ thực tế: Admin gỡ Term 2 khỏi gói nhưng Term 2 đã có 30 booking đang chạy. → Giữ booking/billing đang chạy không bị mất, ngừng bán kỳ mới. → Term bị archive: ẩn ở Booking UI, vẫn hiện ở Subscription/Billing Manager.
"Re-link lại term cũ bị hỏi chọn product / ra bản trùng"
Re-link đúng term đã archived (cùng subscription + term) sẽ restore, không tạo mới:
- Set
StatusIdtừ600→500; bỏ qua popup chọn product. - Giữ nguyên
ProductIdsvàSubscriptionTermBillingSchedulecũ — không mất sản phẩm. - Không tạo
SubscriptionTerm/schedule trùng; reload mỗi surface chỉ thấy một bộ active. - Nếu không có archived row active → mới mở popup chọn product và tạo linked term mới.
Ví dụ thực tế: Admin lỡ unlink Term 2 (đã archive) rồi muốn gắn lại. → Khôi phục đúng sản phẩm + schedule cũ, không tạo bản trùng. → Re-link đổi
StatusId600 → 500, bỏ qua popup product, giữ nguyênProductIds.
"Booking Cut-Off nhập số lẻ bị lỗi"
UI nhận ngày nguyên, lưu thành phút (CutOffMinute, 1 ngày = 1440). Nhập 1.5 → bị từ chối (không tự làm tròn). 0/null = không có cut-off → không chặn booking.
Ví dụ thực tế: Admin muốn chốt đăng ký trước 1,5 ngày nên gõ
1.5. → Cut-off chỉ tính theo ngày nguyên để tránh mốc phút mơ hồ. → Hệ thống từ chối1.5; admin nhập1hoặc2(lưuCutOffMinute= 1440/2880).
"Đổi filter Subscription Manager không thấy data mới"
Cố ý: đổi filter không tự reload bảng — bấm Apply mới load theo filter hiện tại. Load đầu trang tự chạy; mutation (confirm/cancel/terminate/…) thành công cũng refresh. Response request cũ (còn pending) không ghi đè data mới; nếu thấy data nhảy → kiểm tra logic "latest active request wins".
Ví dụ thực tế: Admin đổi Term để xem kỳ khác nhưng bảng vẫn hiện kỳ cũ. → Tránh load tốn request khi chỉ đang chỉnh filter. → Đổi xong bấm
Apply, bảng mới hiện theo Term đã chọn.
"Period booking khách chọn không khớp"
UI phải lấy period từ GET .../Subscriptions/{id}/BillingSchedules (canonical), không tự tính ở client. Mỗi row có BookingStart/BookingEnd ưu tiên hơn billing period start/end. Endpoint trả rỗng → trạng thái "unavailable" + chặn submit; lỗi → trạng thái retryable + chặn submit với selection cũ.
Ví dụ thực tế: Gói có kỳ tính phí theo tháng nhưng booking bắt đầu giữa tháng. → Cho khách chọn đúng khoảng được phép book. → UI lấy
BookingStart/BookingEndtừBillingSchedules; rỗng thì hiện "unavailable" và chặn submit.
"Rollover alert biến mất sau khi link term"
Cố ý: alert rollover bị mute cho tới khi tính năng rollover sẵn sàng. Mute là notification-only — không đổi state Subscription/Booking/Enrollment/Billing/Invoice/Term Planner/queue/worker. Link-term success vẫn hoàn tất + refresh linked-term như cũ; error handling giữ nguyên.
"Quyền chọn start/end của khách"
Phụ thuộc setting gói (Booking Start Selection, Booking End Selection):
| Setting | Khách được chọn? |
|---|---|
Staff Only (mặc định, 0) | Không |
Staff & Customer | Có |
Staff luôn chọn được cả start lẫn end.
Ví dụ thực tế: Trung tâm muốn khách tự chọn ngày bắt đầu gói để chủ động lịch. → Mở quyền chọn mốc cho phụ huynh thay vì chỉ staff. → Đặt
Booking Start Selection=Staff & Customer; khách thấy và chọn được ngày start.