Customer Portal & My Home — Case thường gặp
Cách đọc mỗi case: bắt đầu từ triệu chứng/câu hỏi, sau đó đọc behavior đúng, cách kiểm tra ở UI/API/dữ liệu/code, source anchor và Ví dụ thực tế. Các case ở đây ưu tiên behavior đang chạy; nếu cần contract gốc, mở spec/code được dẫn ở cuối case.
403 khi vào Customer Portal lúc bootstrap
Chỉ app_type ∈ {aoadmin, onsite} mới gọi staff endpoints; customer mode không gọi Manage/CurrentUser / Employee/LinkOrg.
Ví dụ thực tế: User không có staff profile mở portal bị 403. → Chẩn đoán gọi sai endpoint theo mode. →
app_typecustomer mode nhưng vẫn gọiManage/CurrentUser→ bỏ call đó khỏi bootstrap; hết 403.
Giờ hiển thị lệch theo browser
ServiceAreaId = Local → không convert, giờ phải khớp wall-clock source ở mọi browser. MultiZone → resolve effective timezone từ User.TimezoneIdentifier, null → browser.
Ví dụ thực tế: Staff mở Calendar trên laptop ở timezone khác, enterprise
Local. → Giờ không được đổi theo máy. → Kiểm traEnterpriseSetting.ServiceAreaId; nếuLocal, frontend không convert, SyncfusionScheduleComponentkhông reinterpret qua user-local timezone.
Prompt/popup timezone không hiện hoặc hiện sai lúc
Gating theo thứ tự: authenticated (không guest/unresolved) → MultiZone → User.TimezoneIdentifier null → chưa dismiss trong session. Save hay dismiss đều set flag; hai surface dùng hai key riêng.
Ví dụ thực tế: User MultiZone có sẵn timezone vẫn thấy popup. → Popup không auto-trigger khi đã có timezone. →
User.TimezoneIdentifierkhông null → bỏ qua popup; user vẫn tự mở modal quaTimezoneIndicatorbadge nếu muốn.
My Subscriptions hiện dữ liệu account khác
Backend phải scope cứng theo authenticated account; client filter (nếu gửi) bị ignore/reject. Loading/error state không render row stale của account khác.
Ví dụ thực tế: Request My Subscriptions kèm account identifier của người khác. → Không được trả dữ liệu chéo account. → Backend bỏ qua client filter, scope theo account đã đăng nhập; không hiện enrollment account khác.
Section My Subscriptions thiếu/không đúng tab
Map tab ↔ status: Pending → Submitted, Confirmed → Approved, Cancelled → Canceled; Waiting/Cart → ẩn section. Đổi tab lọc trên data đã fetch, không re-fetch.
Ví dụ thực tế: Khách mở tab Confirmed nhưng không thấy section. → Xem có enrollment
Approvednào không. → Tab Confirmed render section chỉ khi cóApproved; không có → bỏ header/divider section.
Period đã cancel vẫn xuất hiện ở Booking Range
Filter booked period = IsActive = true và StatusId != Canceled; cancel workflow để IsActive = true nên lọc IsActive một mình không đủ.
Ví dụ thực tế: Enrollment có period cancel (
StatusId = Canceled,IsActive = true). → Không được hiện trong list lẫn calendar. → Booking Range và drawer View Calendar cùng loại period đó vì thiếuStatusId != Canceled.
Share link trỏ về Admin Portal hoặc lộ token
Mọi share action compose theo Customer Portal base URL, không theo browser origin (INV-CP-SHARELINK-001); không admin prefix, không bearer-token query param; URL hiển thị = URL copy.
Ví dụ thực tế: Staff bấm share từ Admin Portal, khách nhận link có token/session artifact. → Không được lộ admin context. → Compose lại theo Customer Portal origin, chỉ giữ param program/term/site/category/TPS; clipboard nhận đúng chuỗi hiển thị.
Card thiếu contact nhưng vẫn render row trống
Site card / Program Details chỉ render row khi có giá trị; thiếu email/phone/address → bỏ row, giữ thứ tự (name → phone → email → address).
Ví dụ thực tế: Site không có email. → Không để hàng trống gây rối mắt. → Booking UI Program Details chỉ hiện row mobile; landing card bỏ row email, giữ phone rồi address.
Sidebar timezone không hiện hoặc không lưu
Sidebar Select chỉ hiện khi MultiZone (enSetting.serviceAreaId === 1) và user đã login; option từ catalog frontend (getSystemTimezoneOptions()), không phụ thuộc API hydration. Save lỗi → re-enable + revert giá trị cũ.
Ví dụ thực tế: User đổi timezone trong sidebar nhưng request fail giữa chừng. → Không để hiện giá trị không lưu. →
updateUserTimezonethrow → Select re-enable, hiện lạitimezoneIdentifiercũ; không gọi lại tới khi user chọn lại.
"Không thấy Flyer trên Subscription Booking"
Step 1 chỉ render section khi public manifest hợp lệ và Subscription đang bookable. Manifest được trả dưới dạng URL/path an toàn, ảnh lỗi bị bỏ qua; đây không phải lý do chặn booking.
Ví dụ thực tế: Admin upload PDF nhưng trang booking không hiện flyer. → Kiểm tra file vượt 5 trang/ 10 MB hoặc manifest không hợp lệ. → Sửa upload; nếu chỉ một page lỗi, phần còn lại vẫn có thể render.
"Customer không thấy nút Extend"
Nút chỉ hiện khi enrollment còn schedule tương lai liền kề và Subscription cho phép chọn đầu/cuối. Nút không xuất hiện với enrollment canceled/terminated, schedule đã hết, hoặc setting không cho customer; server vẫn kiểm tra lại ownership, Term và ngày khi submit.
Ví dụ thực tế: Customer cố Extend vào tuần đã qua bằng request cũ. → Không tạo booking backdated. → UI loại schedule, backend reject thêm một lần nếu client vẫn gửi.
"Portal hiện setting của BU cũ"
Kiểm tra context-keyed loader và stale-response guard. Đổi BU phải nạp snapshot mới; response pending của BU trước đó không được commit. Sau khi save logo/banner, backend phải invalidate cascade trước khi response thành công.
Ví dụ thực tế: Chuyển BU trong lúc mạng chậm. → Không trộn branding của hai BU. → Chỉ snapshot của context cuối cùng được render.