Preview Billing Diff: Code flow
Use this page to trace Edit Booking through the code quickly. The goal is to see who calls what, what is read/written, and where the result goes.
Eligibility has two layers: the UI is only an early gate; the backend makes the final decision. The table below shows what to check and where to trace it. See the Edit Booking page for the full conditions.
1. One-line flow
How to read the diagram
| Label | What is it? |
|---|---|
| 🖥️ UI | An admin screen or action. |
| 🛠️ Function | A client/service/builder/mapper function that processes data. |
| 🚪 API | An endpoint/controller that receives the request and checks the boundary. |
| 🛡️ Policy | A rule that checks eligibility, scope, and access. |
| 🧮 Calculation | A function that calculates the invoice/credit projection in memory. |
| 📦 Result | Returned data or retained state; not a function. |
| 📖 DB READ | Reads current data; does not change the DB. |
| ✏️ DB WRITE | Writes real data; only occurs after a valid Confirm. |
| 🎫 Token | Checks that Preview still matches at Confirm; not an access right. |
Eligibility check
| Check | Required condition | Where to trace |
|---|---|---|
| Flow / UI gate | Flag on; Admin Edit of an existing booking; not New Booking, Attendance, or a lifecycle action. | billingDifferencePreviewReadiness, useConfirm |
| Canonical order | Submit succeeded; order is active; TermBookingOrderIds exists; status is Submitted/Submitted_Change; at most 500 IDs per request. | submitBookingOrders, SubmitBookingOrders, PreviewBillingDifference |
| Original booking | TermBookingId points to an active booking whose status is Attendance_Approved, Quote, or Approved. | PreviewBillingDifferenceContextPolicy.Resolve |
| Billing context & access | Billing, account, BU, attendee, and program-set context exists; all orders share billing/account/BU; permission/scope is valid. | Controller access check + context policy |
| Exact billing data | An invoice/credit-note baseline, BU invoice settings, default tax rate, and line/extra anchors are available. | BookingConfirmationPlanBuilder, BillingService |
If a check fails → the UI bypasses Preview or the API returns a structured error; never use a guessed amount. Direct-confirm is only an explicit policy-approved branch.
Real-world example: The admin changes one session. →
SubmitBookingOrdersstores the edit and returns the IDs. → Preview reads the stored edit. → Confirm writes only while the token still matches.
2. Code map
There is no Step column: the order is already shown in the diagram. This table only answers what type each code point is and what it does. Middle path segments are shortened; the repositories are Tuke/ or Tux/.
| Type | File / lines | Function or endpoint | Role / key data |
|---|---|---|---|
| 🖥️ UI | Tuke/.../attendee-selection-view/view-action.tsx851-1041, 1173-1323 | billingDifferencePreviewReadiness | Checks flag/permission/edit; submits first; retains IDs, options, result, and token. |
| 🖥️ UI | Tuke/.../booking-manager/.../useConfirm.tsx68-150, 305-405 | useConfirm | Applies the same gate in Booking Manager; reruns Preview when stale. |
| 🛠️ Client function | Tuke/app/services/booking.ts615-680, 1126-1145 | submitBookingOrderspreviewBillingDifferenceByOrdersconfirmBookingOrders | Builds the payload and passes IDs, billing options, and PreviewSnapshotToken through. |
| 🚪 API/controller | Tux/.../ConsumerBookingController.cs6610-6834 | SubmitBookingOrdersPreviewBillingDifferenceConfirmBookingOrders | Checks body, canonical IDs, max count 500, scope, and permission; does not calculate invoices. |
| 🛡️ Policy | Tux/.../PreviewBillingDifferenceContextPolicy.cs48-154 | Context policy | Checks order/original booking status, billing/account/BU context, mixed scope, and access. |
| 🛠️ Preview service | Tux/.../BillingService.cs4010-4506 | PreviewBookingActionDifferenceAsync | Coordinates the read-only plan, baseline, calculation, token, and clear state; does not persist. |
| 🛠️ Plan builder | Tux/.../BookingConfirmationPlanBuilder.cs43-145 | BuildAsync | Builds the shared BookingConfirmationPlan for Preview and Confirm. |
| 🛠️ Mapper | Tux/.../BookingConfirmationPlanPreviewMapper.cs16-151 | Preview mapper | Converts the plan into lines, extras, discounts, and excluded booking IDs; does not calculate money. |
| 🛠️ Baseline / input | Tux/.../BillingService.cs1749-2016, 2293-3212 | BuildPreviewBillingDifferenceBaselineAsyncBuildPreviewBillingGenerationInputAsync | Collects current transactions, booking overlay, discount/promo, tax/settings, outstanding logs, and work items. |
| 🧮 Calculation | Tux/.../BillingGenerationCalculationCore.cs112-1695 | Calculate + split/frequency/line builders | Creates invoice/credit-note projections, lines, and logs in memory; no SaveChanges. |
| 📦 Preview mapping | Tux/.../BillingService.cs3344-3485, 3859-3867 | ApplyBillingGenerationPreviewCalculatePreviewDifference | Maps the calculation result to transactions, breakdowns, and the authoritative Difference. |
| 🎫 Token | Tux/.../PreviewBillingSnapshotToken.cs32-147 | Create / Matches | Hashes canonical input + result into pbd-v1.<hash> to detect changed state/options. |
| ✏️ Confirm service | Tux/.../BookingService.cs1305-1421 | ConfirmBookingAsync | Recomputes, checks the token, then persists booking/billing in a transaction. |
3. Four checkpoints to trace
| Checkpoint | Read first | Input → output | DB boundary |
|---|---|---|---|
| 1. Submit | UI → client → SubmitBookingOrders | Edit form → pending order + TermBookingOrderIds | ✏️ Writes TermBookingOrder*; notification is muted when needed. |
| 2. Preview | PreviewBillingDifference → policy → BillingService | IDs/options → plan → projection + PreviewSnapshotToken | 📖 Reads order, booking, billing, invoice, discount, and settings only. |
| 3. Review | Preview modal / Booking Manager | Admin reviews Billing Difference, retaining result + token | No persistence or side effect call. |
| 4. Confirm | ConfirmBookingOrders → ConfirmBookingAsync | Same IDs/options/token → confirm result | Checks token first; only a match ✏️ writes in a transaction. |
Preview does not use billing data calculated by the browser. The backend reads submitted data and is the source of the final amount.
3.1. How does BillingGenerationCalculation work?
At the calculation layer, the backend reads submitted data, builds BillingGenerationCalculationInput in memory, and calls the same BillingGenerationCalculationCore.Calculate() used by real Confirm.
| Layer | Data / rule | Result |
|---|---|---|
| Baseline | Current active Invoice/CreditNote; unpaid Generated invoices are ScheduledToDelete, while paid invoices are retained. | OriginalTransactions, ScheduledToDelete |
| Input | Canonical TermBookingOrderIds, unchanged booking rows + edit overlay, discount/promo, tax, invoice settings, and frequency override. | BillingGenerationCalculationInput |
| Work item | Fresh lines/extras to generate; outstanding invoice/credit-note logs to reverse or reissue; unreconstructable states are rejected. | Work-item list shared with Confirm |
| Calculation core | Split, frequency, grouping, discount, promo, tax, and totals; no preview-only amount engine. | Invoice/CreditNote projection + finance logs |
| Preview mapping | Applies the same date override as Confirm, removes $0 transactions, creates stable opaque identifiers, and builds breakdowns. | NewTransactions, Summary, AttendeeBreakdowns |
The main steps inside the core are:
- Clone input: each work item is processed from cloned lookup/data so the caller's input is not changed.
- Split invoices: by
Course,Attendee,Attendee_Course, orAccount. - Group by period: normal Booking uses
Weekly,Monthly, orOne-off; confirmation type/payment option may override frequency through the shared helper. - Build financial lines: group booking lines, extras, and outstanding logs by attendee/course/discount/item split rule. Fresh lines use current discounts; old logs reissue the already billed amount without applying the discount again.
- Apply promo and tax: promo allocation becomes a separate negative invoice line; line amount uses quantity, unit price, effective discount, tax rate, and
InvoiceLineAmountTypeId. - Create the projection: create
Invoice/CreditNoteplus invoice/credit-note logs in memory. For Edit Booking, a net reduction may create aCreditNote; a no-op creates no synthetic$0transaction.
Illustration 1: from booking lines to a projected transaction
| Projection | What the core calculates | Not performed by Preview |
|---|---|---|
Invoice | Subtotal, discount, tax, total, invoice lines, and invoice logs. | No real transaction number; no persistence. |
CreditNote | Subtotal, tax, total, credit-note lines, and credit-note logs. | No real credit note write; Confirm persists it. |
BillingGenerationCalculationResult returns NewInvoices, NewCreditNotes, new logs, and expired logs. TotalOriginalBookingValue is informational baseline data and must also use the shared core, not a legacy calculation engine.
Main trace: BuildPreviewBillingGenerationInputAsync → Calculate → GenerateInvoicesBySplitRule → CreateFTLines → ApplyBillingGenerationPreview.
Summary.Difference is calculated authoritatively by the backend:
Difference = new invoice - new credit note - old scheduled-delete invoiceThe response field names are:
Difference
= TotalNewAmount
- TotalCreditNoteAmount
- TotalScheduledToDeleteAmountCredit-note and scheduled-delete amounts are positive magnitudes and are subtracted once.
Real-world example: A new invoice is
$150, a credit note is$25, and an unpaid old invoice is scheduled for deletion at$80. →Difference = 150 - 25 - 80 = +$45→ collect$45more.
| Scenario | Calculation | What the admin should read |
|---|---|---|
| Change increases the amount | 150 - 25 - 80 | +$45, collect more |
| Reduction after Edit Booking | 0 - 30 - 0 | -$30, credit/reduction |
| No persistable work | 0 - 0 - 0 | $0, no synthetic transaction |
Boundary: The calculation core creates projections only in memory. Preview maps the result to a response; it does not call SaveChanges, billing persistence, a scheduler, accounting sync, or notifications. Confirm persists the same prepared result only after PreviewSnapshotToken remains valid.
4. Confirm after Preview
- The UI sends back the same
TermBookingOrderIdsand billing options used by Preview. - The API checks the body, permission, and order scope as in a normal request.
- The service rebuilds/recalculates the prepared result, then calls
PreviewBillingSnapshotToken.Matches. - Missing or mismatched token →
PREVIEW_BILLING_DIFF_STALE; no write and no auto-confirm. - Matching token → persist the validated result in a transaction; history, notification, scheduler, and external events run after commit.
Direct-confirm is allowed only for a flow without a preview token and an error code allowed by policy, such as INELIGIBLE_ORDER or NO_ORIGINAL_TRANSACTION. It is not a general fallback for Preview errors.
5. Where data is read/written
| Stage | 📖 Read | ✏️ Write | Result |
|---|---|---|---|
| Submit | Edit form, options | TermBookingOrder*, related submit data | Canonical TermBookingOrderIds |
| Preview | TermBookingOrder*, current booking, Billing, invoice/credit note, discount, attendance context, BU settings | No DB write; projection exists only in memory | Billing Difference + PreviewSnapshotToken |
| Confirm | Same order IDs/options, current state, and token | Booking, billing, invoice/credit note, attendance as required by the flow | Real data after transaction commit |
| After commit | — | History, notification, scheduler, external event | Confirm side effects |
6. Quick debug and test checkpoints
Quick debug
| Question | Open first | Look for |
|---|---|---|
| Does the request reach Preview? | view-action.tsx, booking.ts | billingDifferencePreviewReadiness, previewBillingDifferenceByOrders |
| Which IDs are being used? | view-action.tsx, controller | TermBookingOrderIds, current edit ID resolution |
| Why was it rejected? | Controller, PreviewBillingDifferenceContextPolicy.cs | Structured error and context checks |
| Where is the amount calculated? | BillingService.cs, BillingGenerationCalculationCore.cs | BuildPreviewBillingGenerationInputAsync, Calculate |
| Why is Confirm stale? | BookingService.cs, PreviewBillingSnapshotToken.cs | Matches, PREVIEW_BILLING_DIFF_STALE |
| Does Preview write to the DB? | BillingService.cs, boundary tests | SaveChanges, transaction rollback, and read-only assertions |
Test checkpoints
| Test | Protects |
|---|---|
Tux/src/Tux.Tests/Tux.Service.Tests/Billing/PreviewBillingDifferenceRegressionTests.cs:17-40 | Preview runtime entry point. |
Tux/src/Tux.Tests/Tux.Service.Tests/Billing/BillingCalculationCoreBoundaryTests.cs:156-184 | Calculation core has no persistence. |
Tuke/app/[curl]/program/components/preview-billing-difference-modal-policy.test.mjs | Submitted IDs, retry, stale, and modal bypass policy. |