Customer Portal & My Home — Khái niệm cốt lõi
App mode
app_type xác định context host, quyết định bootstrap gọi API nào (INV-CPBOOT-001).
| Mode | app_type | Bootstrap |
|---|---|---|
| Admin Mode | aoadmin, onsite | Gọi staff context: GET /api/Manage/CurrentUser, rồi GET /api/Employee/LinkOrg nếu response có employeeId và user không phải admin. Không fetch caller account (GET /api/ConsumerAccount không _a). |
| Customer mode | Customer Portal, aimyme, còn lại | Gọi caller account GET /api/ConsumerAccount (không _a) + GET /api/ConsumerManage/CurrentUser. Không gọi staff endpoints. |
_a= account selector trênConsumerAccountcall: admin vẫn gọi được account customer đã chọn ở Admin Mode; rule chỉ cấm fetch admin's own account lúc bootstrap.- Identity + timezone hydrate từ
CurrentUser(mọi mode) — gating staff-context không được làm vỡ timezone hydration.
Ví dụ thực tế: Page embed trong Onsite APP (
app_type = onsite). → Staff vẫn cần staff context. → Bootstrap gọiManage/CurrentUser+Employee/LinkOrg, populatecurrentUserTopi/currentUserLinkedSites; không gọiConsumerAccountcho chính admin.
ServiceAreaId — chế độ timezone hiển thị
EnterpriseSetting.ServiceAreaId quyết định Calendar/Timetable hiển thị giờ thế nào (INV-ENTTZMODE-002). Backend trả giá trị nguồn; frontend lo phần display conversion.
| Giá trị | Mã | Cách hiển thị |
|---|---|---|
Local | single zone | Giữ nguyên wall-clock source; không convert theo user/browser timezone; Syncfusion ScheduleComponent không reinterpret qua user-local timezone. |
MultiZone | 1 | Convert về effective timezone từ User.TimezoneIdentifier; null → fallback browser-detected timezone. |
Ví dụ thực tế: Cùng event 09:00, user mở trên browser timezone khác ở enterprise
Local. → Giờ không được đổi theo browser. → Cả hai browser đều thấy 09:00 vì không có conversion nào được áp.
Timezone identifier
User.TimezoneIdentifier= Windows TZ ID của user (timezoneIdentifier). Dropdown hiển thị<IANA name> (<abbreviation>)(vdAustralia/Sydney (AEDT)).- Option từ catalog frontend
getSystemTimezoneOptions(); không dùnguseUserStore.systemTimeZoneshydrate từ API; Select hỗ trợ search/filter (showSearch). - Cập nhật qua
POST /api/User/UpdateUserTimezoneIdentifier(Windows TZ ID) hoặcupdateUserTimezone(newWindowsId)(sidebar auto-save). - Sau reload, giá trị lưu được hydrate từ
ConsumerManage/CurrentUser; không cần gọi thêmConsumerManage/GetUserTimezoneIdentifier.
Session flags (sessionStorage)
| Key | Surface | Chặn gì trong session |
|---|---|---|
my-home-tz-prompted | My Home (staff) | Prompt chọn timezone lần đầu — chỉ set khi save. |
tz-popup-dismissed | Customer Portal | Session popup timezone — set khi save hoặc dismiss. |
- Hai key độc lập theo surface; không dùng chung.
- Guest/auth-unresolved: không check, không set flag nào.
Ví dụ thực tế: User dismiss session popup trên portal. → Popup không hiện lại trong session. →
tz-popup-dismissed = '1'; staff My Home vẫn còn prompt lần đầu vìmy-home-tz-promptedchưa set.
Enrollment và trạng thái
- Enrollment = ghi danh subscription (runtime); status gồm
Submitted(Pending),Approved(Confirmed),Canceled(Cancelled) khi map sang tab My Bookings. - EnrollmentPeriod = kỳ đã book. Chỉ period active + không cancelled được đưa vào Booking Range và calendar.
- Cancel workflow để
StatusId = CancelednhưngIsActive = true— lọc bằngIsActivemột mình là không đủ, phải kết hợpStatusId != Canceled.
Ví dụ thực tế: Enrollment có period đã cancel (
StatusId = Canceled,IsActive = true). → Period cancelled không được hiện. → Booking Range và View Calendar đều loại period đó vì filterIsActive && StatusId != Canceled.
My Subscriptions — read model
- Route duy nhất:
/{curl}/enrollments(không tạo route mới); menuMy Subscriptionstrỏ tới đó. - Scope cứng theo authenticated account: client filter bị ignore/reject; không bao giờ trả enrollment của account khác.
- Row gồm: site, subscription name + enrollment number, account/customer display (nếu customer-safe), attendee, start date, optional end date, status.
- Summary:
Subscription Summaryheader dùng theme accent của BU/portal (không hard-code màu hồng screenshot); recurring price label + amount canh phải desktop, mobile vẫn đọc được. - Program/session/weekly pattern table: group session theo program; weekday theo thứ Mon–Sun, ngày chọn nổi bật.
- Các state: loading (không render row stale của account khác), empty (kèm action browse/book subscription), error (recoverable + retry).
- Read-only: load/expand không tạo hay mutate
TermBookingOrder,TermBooking,Enrollment,Billing,Invoice,Payment,CreditNote.
Customer-safe vs admin-only
| Customer-safe (được hiện/cho) | Admin/Staff-only (ẩn) |
|---|---|
| View details, refresh, cancel, terminate (khi backend policy cho phép theo row status) | Confirm button |
| Subscription name, site name, price label + amount, description / customer-facing note | Manage Billing |
| Program/session/weekly pattern, add-ons | Edit admin note |
| Booking Range + View Calendar (read-only) | Account selection / cross-account filter |
| Admin notes, payment method identifier, internal booking-order id |
- Disabled/unavailable action không được gọi mutation endpoint.
- Action cho phép phải đi qua backend policy; không hiện button chỉ để đẹp.