flows/status-matrix/02-happy-path.md

02 · Happy Path Transitions

Các transition thuận: PENDING → CONFIRMED → ARRIVED → IN_PROGRESS → COMPLETED. Không bao gồm cancel / no-show (xem 03-cancel.md, 04-no-show.md).

Endpoint chung: POST /bookings/:id/status/:status (roles: OWNER/STAFF/ADMIN). Flow: updateStatus() → isValidTransition OR isAdminTransition → guard → prisma.update + outbox enqueue → audit log → notification.


T1 · PENDING → CONFIRMED

Performer matrix

Performer Allowed? Path Ghi chú
CUSTOMER chỉ cancel own không có endpoint confirm cho customer
STAFF state machine không check deposit
OWNER / ADMIN state machine không check deposit
SYSTEM (PaymentAuthorized listener) isAdminTransition branch skip cancellation window guard

Guards

# Guard Code? Docs? Status
C1 isValidTransition(PENDING, CONFIRMED) [x]
C2 Deposit required — nếu depositEnabled + Payment không ở AUTHORIZED/CAPTURED → reject BOOKING_DEPOSIT_REQUIRED booking-status-flow.md §3 [!] Gap P0-2

Events + side effects

  • BookingConfirmed emit (payload: bookingId, confirmedAt, confirmedBy).
  • Notification: BOOKING_CONFIRMED template gửi customer.
  • Payment: KHÔNG capture (giữ hold, capture ở ARRIVED).
  • Loyalty: no-op.

Real-world

Case Hiện tại Gap
Customer trả deposit xong → webhook → listener auto-confirm ✅ hoạt động
Staff nhấn "Confirm" thủ công khi depositEnabled=false ✅ OK
Staff nhấn "Confirm" khi depositEnabled=true nhưng Payment INITIATED [!] Hệ thống cho qua, booking CONFIRMED không deposit P0-2
Staff confirm khi Payment FAILED (chưa kịp listener cancel) [!] Race — staff thắng → booking CONFIRMED, sau đó OnPaymentSettledNegative skip (status != PENDING) P1-5

Implementation

  • State machine transition
  • Event emit BookingConfirmed
  • Audit log
  • Notification
  • [!] Guard C2 (deposit required) — chưa có

T2 · CONFIRMED → ARRIVED

Performer matrix

Performer Allowed?
CUSTOMER
STAFF / OWNER / ADMIN
SYSTEM — (không có event auto-arrive)

Guards

# Guard Code? Docs? Status
A1 isValidTransition(CONFIRMED, ARRIVED) [x]
A2 Check-in window — không cho check-in sớm quá X phút trước startTime [ ] chưa có yêu cầu, nhưng salon thật có thể muốn

Events + side effects

  • BookingArrived emit.
  • Payment: onBookingArrived → capture MANUAL+AUTHORIZED payment (primary capture trigger).
  • Notification: none mặc định (docs mô tả optional).
  • Stats: none.

Real-world

Case Hiện tại Gap
Khách check-in đúng giờ ✅ Payment capture tự động
Khách đến sớm 1 tiếng ✅ cho mark Arrived sớm (no guard) A2 nếu cần
Khách late nhưng đã đến trước khi startTime + grace ✅ mark Arrived bình thường
Khách không đến, staff quên mark NoShow Booking stuck CONFIRMED, Payment hold hết 7 ngày → EXPIRED Xem 07-edge-cases.md
depositEnabled=false → không có Payment ✅ capture loop findByBookingId trả [], no-op

Implementation

  • State machine
  • BookingArrived event
  • Payment capture MANUAL+AUTHORIZED
  • Idempotent (duplicate event → capture command idempotent key)
  • UI "Check-in" button chỉ visible từ X phút trước startTime (nice-to-have)

T3 · CONFIRMED → IN_PROGRESS (skip ARRIVED)

Path ngắn khi staff/owner bấm "Start" luôn (khách đến, staff cầm máy bấm start thay vì 2 bước).

Performer matrix

Performer Allowed?
STAFF / OWNER / ADMIN

Guards

# Guard Code? Docs? Status
S1 isValidTransition(CONFIRMED, IN_PROGRESS) [x]
S2 Resource không conflict IN_PROGRESS khác (docs §3) [ ] chưa có

Events + side effects

  • KHÔNG emit event (code booking.service.ts:969 default branch return null).
  • Không có Payment capture ở bước này.
  • Notification: none.

Real-world

Case Hiện tại Gap
Khách đến + staff start luôn (skip Arrived) Không capture. Sẽ capture ở COMPLETED fallback [!] Cửa sổ capture dài hơn → rủi ro nếu booking bị hủy giữa chừng
Staff đang bận resource khác, bấm Start sai booking Không có guard → booking chuyển IN_PROGRESS trước S2 nên add

Implementation

  • State machine
  • BookingStarted event (docs mention, code không emit) — cần quyết định có emit không
  • S2 resource busy guard

T4 · ARRIVED → IN_PROGRESS

Performer matrix

Performer Allowed?
STAFF / OWNER / ADMIN

Guards

# Guard Code? Docs? Status
I1 isValidTransition(ARRIVED, IN_PROGRESS) [x]
I2 Resource không conflict khác [ ] như S2

Events + side effects

  • KHÔNG emit event (cùng default branch).
  • Payment đã capture ở ARRIVED bước trước.
  • Notification: none.

Implementation

  • State machine
  • BookingStarted event + guard I2

T5 · IN_PROGRESS → COMPLETED

Performer matrix

Performer Allowed?
STAFF / OWNER / ADMIN

Guards

# Guard Code? Docs? Status
M1 isValidTransition(IN_PROGRESS, COMPLETED) [x]
M2 Full payment required khi POS enabled (docs §3) ✅ (future) [ ] phụ thuộc POS feature
M3 remaining = total − captured + refunded hiển thị cảnh báo nếu > 0 ❌ FE [ ] UX

Events + side effects

  • BookingCompleted emit.
  • Payment: onBookingCompleted → capture fallback nếu MANUAL+AUTHORIZED (ví dụ skip Arrived). Idempotent (đã CAPTURED → no-op).
  • Loyalty: OnBookingCompletedListener (hiện pre-L3 — cần check L4):
    • RESERVED → CONSUMED nếu có appliedRedemptionId
    • autoStamp tạo stamp cho card VISIT_BASED qualifying
    • autoEarn cộng points cho card POINTS_BASED qualifying
  • TenantCustomer metrics: visitCount++, lastVisit, totalSpent += payableTotal.

Real-world

Case Hiện tại Gap
Service xong, khách đã trả full deposit + cash phần còn lại COMPLETED → stamps/points auto-earn OK
Service xong, khách nợ phần còn lại Không guard → COMPLETED mặc dù chưa full thu M2/M3 nếu cần
"Krev resterende" QR đã capture full COMPLETED idempotent no-op OK (Flow 18 payment-flow)
Loyalty L4 listener chưa ship [~] Code base hiện chỉ có pre-L3 listener on-booking-completed.listener.ts — cần verify RESERVED → CONSUMED flip có chạy chưa P1-6

Implementation

  • State machine
  • BookingCompleted event
  • Payment capture fallback
  • [~] Loyalty: pre-L3 listener có autoStamp/autoEarn, L4 RESERVED → CONSUMED chưa verify
  • TenantCustomer metrics update
  • M2/M3 full-payment guard (POS-gated)

Summary checklist

PENDING → CONFIRMED

  • State machine
  • Event BookingConfirmed
  • Audit + notification
  • [!] Deposit-required guard (P0-2)

CONFIRMED → ARRIVED

  • State machine
  • Event BookingArrived
  • Payment capture MANUAL+AUTHORIZED
  • Check-in window UX (nice-to-have)

CONFIRMED → IN_PROGRESS

  • State machine
  • Event emit BookingStarted (docs promise, code không emit)
  • Resource busy guard

ARRIVED → IN_PROGRESS

  • State machine
  • Event + guard (như trên)

IN_PROGRESS → COMPLETED

  • State machine
  • Event BookingCompleted
  • Payment capture fallback
  • TenantCustomer metrics
  • [~] Loyalty L4 RESERVED → CONSUMED — cần verify
  • Full-payment guard khi POS (future)

→ Chi tiết gap + plan: gaps-and-plan.md.