Preview Billing Diff: Code flow
Trang này dùng để trace nhanh Edit Booking qua code. Mục tiêu là biết ai gọi gì, đọc/ghi gì và kết quả đi đâu.
Eligibility có hai lớp: UI chỉ là early gate; backend mới quyết định cuối cùng. Bảng dưới đây cho biết cần kiểm tra gì và trace ở đâu. Điều kiện đầy đủ nằm ở trang Edit Booking.
1. Luồng một dòng
Cách đọc diagram
| Nhãn | Đây là gì? |
|---|---|
| 🖥️ UI | Màn hình hoặc hành động của admin. |
| 🛠️ Function | Hàm client/service/builder/mapper xử lý dữ liệu. |
| 🚪 API | Endpoint/controller nhận request và kiểm tra boundary. |
| 🛡️ Policy | Rule kiểm tra eligibility, scope và quyền truy cập. |
| 🧮 Calculation | Hàm tính invoice/credit projection trong memory. |
| 📦 Result | Dữ liệu trả về hoặc state được giữ lại; không phải hàm. |
| 📖 DB READ | Đọc dữ liệu hiện tại; không thay đổi DB. |
| ✏️ DB WRITE | Ghi dữ liệu thật; chỉ xảy ra khi Confirm hợp lệ. |
| 🎫 Token | Mã kiểm tra Preview còn khớp khi Confirm; không phải quyền truy cập. |
Eligibility check
| Check | Điều kiện cần đạt | Trace ở đâu? |
|---|---|---|
| Flow / UI gate | Flag bật; Admin Edit booking hiện hữu; không phải New Booking, Attendance hoặc lifecycle action. | billingDifferencePreviewReadiness, useConfirm |
| Canonical order | Submit thành công; order active; có TermBookingOrderIds; status Submitted/Submitted_Change; tối đa 500 IDs/request. | submitBookingOrders, SubmitBookingOrders, PreviewBillingDifference |
| Booking gốc | TermBookingId trỏ tới booking active; status Attendance_Approved, Quote hoặc Approved. | PreviewBillingDifferenceContextPolicy.Resolve |
| Billing context & access | Có BillingId, Account, BU, attendee, program set; các order cùng billing/account/BU; đúng permission/scope. | Controller access check + context policy |
| Exact billing data | Có invoice/credit note baseline, BU invoice settings, default tax rate và anchor cho line/extra. | BookingConfirmationPlanBuilder, BillingService |
Không đạt một check → UI bypass hoặc API trả structured error; không dùng amount đoán. Direct-confirm chỉ là nhánh explicit mà policy cho phép.
Ví dụ thực tế: Admin đổi một session. →
SubmitBookingOrderslưu bản edit và trả IDs. → Preview đọc bản đã lưu. → Confirm chỉ ghi khi token còn khớp.
2. Bản đồ code
Không dùng cột Step: thứ tự đã thể hiện trong diagram; bảng này chỉ trả lời điểm code thuộc loại nào và làm gì. Path được rút gọn ở giữa; repo là Tuke/ hoặc Tux/.
| Loại | File / đoạn | Hàm hoặc endpoint | Vai trò / dữ liệu chính |
|---|---|---|---|
| 🖥️ UI | Tuke/.../attendee-selection-view/view-action.tsx851-1041, 1173-1323 | billingDifferencePreviewReadiness | Kiểm tra flag/quyền/edit; Submit trước; giữ IDs, options, result và token. |
| 🖥️ UI | Tuke/.../booking-manager/.../useConfirm.tsx68-150, 305-405 | useConfirm | Áp dụng cùng gate cho Booking Manager; stale thì Preview lại. |
| 🛠️ Client function | Tuke/app/services/booking.ts615-680, 1126-1145 | submitBookingOrderspreviewBillingDifferenceByOrdersconfirmBookingOrders | Tạo payload và chuyển tiếp IDs, billing options, PreviewSnapshotToken. |
| 🚪 API/controller | Tux/.../ConsumerBookingController.cs6610-6834 | SubmitBookingOrdersPreviewBillingDifferenceConfirmBookingOrders | Kiểm tra body, canonical IDs, count tối đa 500, scope và permission; không tính invoice. |
| 🛡️ Policy | Tux/.../PreviewBillingDifferenceContextPolicy.cs48-154 | Context policy | Kiểm tra order/original booking status, billing/account/BU context, mixed scope và access. |
| 🛠️ Preview service | Tux/.../BillingService.cs4010-4506 | PreviewBookingActionDifferenceAsync | Điều phối read-only plan, baseline, calculation, token và clear state; không persist. |
| 🛠️ Plan builder | Tux/.../BookingConfirmationPlanBuilder.cs43-145 | BuildAsync | Dựng BookingConfirmationPlan dùng chung cho Preview và Confirm. |
| 🛠️ Mapper | Tux/.../BookingConfirmationPlanPreviewMapper.cs16-151 | Preview mapper | Đổi plan thành lines, extras, discounts và excluded booking IDs; không tính tiền. |
| 🛠️ Baseline / input | Tux/.../BillingService.cs1749-2016, 2293-3212 | BuildPreviewBillingDifferenceBaselineAsyncBuildPreviewBillingGenerationInputAsync | Gom transaction hiện tại, overlay booking, discount/promo, tax/settings, outstanding logs và work items. |
| 🧮 Calculation | Tux/.../BillingGenerationCalculationCore.cs112-1695 | Calculate + split/frequency/line builders | Tạo invoice/credit-note projection, lines và logs trong memory; không SaveChanges. |
| 📦 Preview mapping | Tux/.../BillingService.cs3344-3485, 3859-3867 | ApplyBillingGenerationPreviewCalculatePreviewDifference | Đổi calculation result thành transactions, breakdown và authoritative Difference. |
| 🎫 Token | Tux/.../PreviewBillingSnapshotToken.cs32-147 | Create / Matches | Hash canonical input + result thành pbd-v1.<hash> để phát hiện state/options đổi. |
| ✏️ Confirm service | Tux/.../BookingService.cs1305-1421 | ConfirmBookingAsync | Recompute, kiểm tra token, rồi persist booking/billing trong transaction. |
3. Bốn checkpoint cần trace
| Checkpoint | Đọc code đầu tiên | Input → output | DB boundary |
|---|---|---|---|
| 1. Submit | UI → client → SubmitBookingOrders | Form edit → pending order + TermBookingOrderIds | ✏️ Ghi TermBookingOrder*; notification được mute khi cần. |
| 2. Preview | PreviewBillingDifference → policy → BillingService | IDs/options → plan → projection + PreviewSnapshotToken | 📖 Chỉ đọc order, booking, billing, invoice, discount và settings. |
| 3. Review | Preview modal / Booking Manager | Admin xem Billing Difference, giữ result + token | Không gọi persistence hoặc side effect. |
| 4. Confirm | ConfirmBookingOrders → ConfirmBookingAsync | Cùng IDs/options/token → kết quả confirm | Kiểm tra token trước; khớp mới ✏️ ghi trong transaction. |
Preview không dùng payload billing do browser tự tính. Backend đọc dữ liệu đã Submit và là nguồn quyết định amount cuối.
3.1. BillingGenerationCalculation tính như thế nào?
Ở lớp calculation, backend đọc dữ liệu đã Submit, dựng BillingGenerationCalculationInput trong memory và gọi chung BillingGenerationCalculationCore.Calculate() với real confirm.
| Lớp | Dữ liệu / quy tắc | Kết quả |
|---|---|---|
| Baseline | Active Invoice/CreditNote hiện tại; Generated chưa thanh toán là ScheduledToDelete, invoice đã thanh toán được giữ lại. | OriginalTransactions, ScheduledToDelete |
| Input | TermBookingOrderIds canonical, booking rows không đổi + overlay đang edit, discount/promo, tax, invoice settings và frequency override. | BillingGenerationCalculationInput |
| Work item | Fresh lines/extras cần phát sinh; invoice/credit-note logs còn outstanding cần đảo hoặc tái lập; các trạng thái không thể reconstruct bị reject. | Danh sách work item dùng chung với Confirm |
| Calculation core | Split, frequency, grouping, discount, promo, tax và tổng tiền; không dùng preview-only amount engine. | Invoice/CreditNote projection + finance logs |
| Preview mapping | Áp dụng date override như Confirm, loại bỏ transaction $0, tạo identifier opaque ổn định và breakdown. | NewTransactions, Summary, AttendeeBreakdowns |
Các bước chính bên trong core:
- Clone input: mỗi work item được xử lý từ bản sao lookup/data để không sửa input của caller.
- Split invoice: theo
Course,Attendee,Attendee_CoursehoặcAccount. - Group theo kỳ: Booking thường nhóm theo
Weekly,MonthlyhoặcOne-off; confirmation type/payment option có thể override frequency qua helper dùng chung. - Tạo financial lines: gom booking line, extra và outstanding log theo attendee/course/discount/item split rule. Fresh line dùng discount hiện tại; log cũ tái lập số đã billing, không áp discount lần nữa.
- Áp promo và tax: promo allocation trở thành invoice line âm riêng; line amount dùng quantity, unit price, effective discount, tax rate và
InvoiceLineAmountTypeId. - Sinh projection: tạo
Invoice/CreditNotecùng các invoice/credit-note logs trong memory. Với Edit Booking, phần giảm ròng có thể tạoCreditNote; no-op không tạo transaction$0giả.
Minh hoạ 1: từ booking line đến chứng từ giả lập
| Projection | Core tính | Chưa thực hiện trong Preview |
|---|---|---|
Invoice | Subtotal, discount, tax, total, invoice lines và invoice logs. | Chưa cấp transaction number thật; chưa persist. |
CreditNote | Subtotal, tax, total, credit-note lines và credit-note logs. | Chưa ghi credit note thật; Confirm mới persist. |
BillingGenerationCalculationResult trả về NewInvoices, NewCreditNotes, logs mới và logs hết hiệu lực. TotalOriginalBookingValue chỉ là baseline thông tin và cũng phải đi qua shared core, không dùng legacy calculation engine.
Chi tiết trace chính: BuildPreviewBillingGenerationInputAsync → Calculate → GenerateInvoicesBySplitRule → CreateFTLines → ApplyBillingGenerationPreview.
Summary.Difference là giá trị backend xác định:
Difference = invoice mới - credit note mới - invoice cũ scheduled-deleteTương ứng với tên field trong response:
Difference
= TotalNewAmount
- TotalCreditNoteAmount
- TotalScheduledToDeleteAmountCredit note và scheduled-delete được biểu diễn bằng số dương rồi trừ một lần.
Ví dụ thực tế: Invoice mới
$150, credit note$25, invoice cũ chưa thanh toán scheduled-delete$80. →Difference = 150 - 25 - 80 = +$45→ thu thêm$45.
| Tình huống | Phép tính | Kết quả đọc cho admin |
|---|---|---|
| Thay đổi làm tăng tiền | 150 - 25 - 80 | +$45, cần thu thêm |
| Giảm tiền sau Edit Booking | 0 - 30 - 0 | -$30, phát sinh credit/reduction |
| Không có work cần persist | 0 - 0 - 0 | $0, không tạo transaction giả |
Boundary: Calculation core chỉ tạo projection trong memory. Preview map kết quả thành response, không gọi SaveChanges, billing persistence, scheduler, accounting sync hoặc notification. Confirm chỉ persist cùng prepared result sau khi PreviewSnapshotToken còn hợp lệ.
4. Confirm sau Preview
- UI lấy lại cùng
TermBookingOrderIdsvà billing options đã Preview. - API kiểm tra body, permission và order scope như request bình thường.
- Service dựng/tính lại prepared result rồi gọi
PreviewBillingSnapshotToken.Matches. - Token thiếu hoặc mismatch →
PREVIEW_BILLING_DIFF_STALE, không ghi và không auto-confirm. - Token khớp → dùng result đã kiểm tra để persist trong transaction; history, notification, scheduler và external event chạy sau commit.
Direct-confirm chỉ được phép cho flow không có preview token và error code được policy cho phép như INELIGIBLE_ORDER hoặc NO_ORIGINAL_TRANSACTION. Đây không phải fallback chung cho lỗi Preview.
5. Đọc/ghi dữ liệu ở đâu?
| Giai đoạn | 📖 Đọc | ✏️ Ghi | Kết quả |
|---|---|---|---|
| Submit | Form edit, options | TermBookingOrder*, dữ liệu submit liên quan | Canonical TermBookingOrderIds |
| Preview | TermBookingOrder*, booking hiện tại, Billing, invoice/credit note, discount, attendance context, BU settings | Không ghi DB; projection chỉ ở memory | Billing Difference + PreviewSnapshotToken |
| Confirm | Cùng order IDs/options, current state và token | Booking, billing, invoice/credit note, attendance theo flow | Dữ liệu thật sau transaction commit |
| Sau commit | — | History, notification, scheduler, external event | Side effects của Confirm |
6. Debug nhanh và test checkpoint
Debug nhanh
| Câu hỏi | Mở trước | Tìm |
|---|---|---|
| Request có vào Preview không? | view-action.tsx, booking.ts | billingDifferencePreviewReadiness, previewBillingDifferenceByOrders |
| Đang dùng IDs nào? | view-action.tsx, controller | TermBookingOrderIds, resolve current edit IDs |
| Vì sao bị reject? | Controller, PreviewBillingDifferenceContextPolicy.cs | Structured error và context check |
| Amount tính ở đâu? | BillingService.cs, BillingGenerationCalculationCore.cs | BuildPreviewBillingGenerationInputAsync, Calculate |
| Vì sao Confirm stale? | BookingService.cs, PreviewBillingSnapshotToken.cs | Matches, PREVIEW_BILLING_DIFF_STALE |
| Preview có ghi DB không? | BillingService.cs, boundary tests | SaveChanges, transaction rollback và read-only assertions |
Test checkpoint
| Test | Bảo vệ điều gì? |
|---|---|
Tux/src/Tux.Tests/Tux.Service.Tests/Billing/PreviewBillingDifferenceRegressionTests.cs:17-40 | Runtime entry point của Preview. |
Tux/src/Tux.Tests/Tux.Service.Tests/Billing/BillingCalculationCoreBoundaryTests.cs:156-184 | Calculation core không persistence. |
Tuke/app/[curl]/program/components/preview-billing-difference-modal-policy.test.mjs | Submitted IDs, retry, stale và bypass policy của modal. |