Regression test cases — Cancellation, amendment and fees¶
Audience: QA. Scope: cancelling a booking or a single line, the cancellation-fee calculation, the fee override, reviving a cancelled booking, waitlist release, and the housekeeping jobs that cancel or delete.
Every case cites the microflow and node it derives from, so expected results can be re-derived
rather than trusted. Mechanism detail:
docs/deep-dives/cancellation-and-fees.md.
Priority: P1 blocks release, P2 must pass before sign-off, P3 when time allows.
Money warning. Sections D and E change what a client is charged. Treat any unexpected result there as P1 regardless of the priority column.
None of these cases have been executed. They state what the system currently does, derived from its decision logic. A pass means unchanged, not correct.
Revised 2026-08-07 against a re-generated model extract; section N is new. See
quote-to-booking.md§12.
What to assert on¶
Cancellation is not a single status change. A correct cancellation touches five things:
| Thing | Cancelled, no fee | Cancelled with costs | Hard delete |
|---|---|---|---|
Booking.WindowStatus |
CX ("Canc") |
CC ("CCost") |
row gone |
Booking.InventoryStatus |
CX |
CX |
row gone |
Booking.Cancelled |
true (only if Wish cancel succeeded) |
true |
— |
BookingLine.LIVE |
false |
true on fee-bearing lines |
— |
BookingLine.aSequence |
original + 1000 | + 1000 | — |
A fee-bearing line stays LIVE on purpose — it carries the charge. Asserting "all lines are
not LIVE" after a cancellation with fees is a wrong expectation.
A. Which outcome — delete vs cancel¶
| ID | Case | Preconditions | Steps | Expected | Pri | Source |
|---|---|---|---|---|---|---|
| A1 | Empty draft is deleted outright | WindowStatus = Draft, no TravelLocation anywhere under the booking's legs, TourPlanReference empty or ≤ 0 |
Cancel | Booking row is deleted, not marked CX. A DataManagement.ArchiveLog exists with Reason = 'No travellocations so delete' and Action = 'Delete booking' |
P1 | CancelBooking [10],[68-71] |
| A2 | Draft with a Tourplan reference is cancelled, not deleted | Same but TourPlanReference > 0 |
Cancel | Booking survives with WindowStatus = CX |
P1 | Booking.CancelBooking [68] |
| A3 | Draft with travel locations is cancelled, not deleted | WindowStatus = Draft but a TravelLocation exists |
Cancel | Booking survives, WindowStatus = CX |
P1 | CancelBooking [10] |
| A4 | Non-draft with no travel locations is cancelled | No TravelLocation, WindowStatus = P |
Cancel | Booking survives — the delete needs both conditions | P2 | CancelBooking [10] |
| A5 | Quote-only cancellation takes the simple path | Source = Quote_only |
Cancel | WindowStatus = CX, InventoryStatus = CX, Cancelled = true, CancelledByUser set. ChangeDate is set, CancellationDate is NOT — defect C10 |
P1 | CancelBooking [17],[24] |
| A6 | Confirmed booking sets CancellationDate |
WindowStatus = C, Wish-backed |
Cancel | CancellationDate populated at [35] |
P1 | CancelBooking [35] |
| A7 | CC is never demoted to CX |
Booking already WindowStatus = CC |
Cancel again | Stays CC |
P1 | CancelBooking [35] |
| A8 | Travelled booking cannot be cancelled | WindowStatus = Traveled |
Cancel | Info "Booking is in an invalid state ( Status = Trav'd) to be Cancelled in the Window."; nothing changes | P1 | Booking.IsCancellationValid [3] |
| A9 | Travelling booking cannot be cancelled | WindowStatus = Travelling |
Cancel | Same rejection | P1 | Booking.IsCancellationValid [2] |
B. Entry-point guards¶
| ID | Case | Preconditions | Steps | Expected | Pri | Source |
|---|---|---|---|---|---|---|
| B1 | Travelled blocked on the standard route | WindowStatus = Traveled |
Cancel from a booking grid | Info "You cannot cancel a booking that is already travelling or has travelled (why would you do that?) Please get your manager to speak to WW support" | P1 | Booking.IVK_CancelBooking_Confirm [2-3] |
| B2 | Ex-Window blocked | Source = Ex_window |
Cancel | Info "Booking is ex-Window and cannot be cancelled via the Window" | P1 | Booking.IVK_CancelBooking_Confirm [14] |
| B3 | Tourplan-confirmed redirected to the booking file | TourPlanFullReference populated |
Cancel from a grid | Info "As this booking has been confirmed in Tourplan, please cancel from the booking file" | P1 | Booking.IVK_CancelBooking_Confirm [12] |
| B4 | Simple confirmation page for draft-ish bookings | WindowStatus in Draft, empty, Placeholder, Quote_only |
Cancel | Page Booking.CancelBooking_confirm opens |
P2 | Booking.IVK_CancelBooking_Confirm [10] |
| B5 | Full confirmation page otherwise | WindowStatus = P or C |
Cancel | Page Booking.CancelBookingConfirmation opens |
P2 | Booking.IVK_CancelBooking_Confirm [8] |
| B6 | The two entry points disagree — travelled | WindowStatus = Traveled, user is a Manager |
Cancel via the standard route, then via the _NoRedirect route |
Standard route blocks. _NoRedirect allows the manager through. Log as defect C2 unless product confirms the divergence is intended |
P1 | Booking.IVK_CancelBooking_Confirm [2] vs ..._NoRedirect [12-15] |
| B7 | The two entry points disagree — Tourplan reference | TourPlanFullReference populated |
Cancel via _NoRedirect |
No Tourplan check is applied — cancellation proceeds where the standard route would have blocked it. Defect C2 | P1 | ..._NoRedirect [4-5] |
| B8 | Bulk cancel bypasses all wrapper guards | Debug user, a list including a travelled and an ex-Window booking | Run IVK_CancelBookings |
Only IsCancellationValid applies, so travelled bookings are rejected but ex-Window and Tourplan-confirmed bookings are not. Confirm this is acceptable for a Debug-only tool |
P2 | Booking.IVK_CancelBookings [3], IsCancellationValid |
| B9 | Orphaned booking-user-history row | BookingUserHistory whose booking has been deleted |
Cancel from booking-user history | The history row is deleted, no error | P3 | Booking.IVK_CancelBooking_Confirm_FromBookingUserHistory [6] |
| B10 | Role coverage | One user per role in Administrator, Agent, Employee, Manager, Debug, ExternalConsultant, plus one outside |
Attempt cancel | Only the six listed roles can reach IVK_CancelBooking_Confirm; CancelBooking itself is Agent, Debug, Employee, ExternalConsultant |
P2 | model role lists |
C. Cancellation side effects¶
| ID | Case | Preconditions | Steps | Expected | Pri | Source |
|---|---|---|---|---|---|---|
| C1 | Lines are marked and delisted | Booking with several live lines | Cancel | Each line: Cancel = true, LIVE = false, aSequence increased by exactly 1000, TravelDay and TravelLocation detached |
P1 | CancelBooking [31-34], Booking.CancelBL [4] |
| C2 | Tourplan flagged for update | Line was Chargeable with TourPlanStatus != Draft |
Cancel | TPUpdateRequired = true; line TourPlanStatus = Pending; booking TourplanStatus = Pending unless already CC/CX |
P1 | Booking.CancelBL [3-5] |
| C3 | Draft Tourplan status is preserved | Line TourPlanStatus = Draft |
Cancel | Stays Draft — not moved to Pending |
P2 | Booking.CancelBL [4] |
| C4 | Wish cancel failure aborts | Wish-backed booking; force CancelWishHeader to fail |
Cancel | Error "Your booking could not be cancelled die to Wish error"; a Critical Logging.SystemLog naming the Wish and Window references; booking is NOT marked cancelled |
P1 | CancelBooking [59-63] |
| C5 | Wish failure leaves a support trail | As C4 | Cancel, inspect the log | Resolution text: "The cancellation of this booking failed due to a system issue. The booking has been handed over to a consultant to complete the cancellation." | P2 | Booking.CancelBooking [61] |
| C6 | Third-party inventory released | Booking with LiveRequest lines | Cancel | DataManagement.CancelBooking_LR runs |
P2 | Booking.CancelBooking [36] |
| C7 | Itineraries removed from Wetu | Booking with PartyCostGroups that have itineraries | Cancel | Itinerary.DeleteItineraryInWetu runs per PartyCostGroup |
P2 | CancelBooking [38-39] |
| C8 | Guests cancelled | Any booking | Cancel | BookingGuestManagement.CancelBooking_Guests runs |
P2 | Booking.CancelBooking [18],[30] |
| C9 | Explorations cleared | Booking with an exploration | Cancel | Explorations.ClearExploration runs |
P3 | Booking.CancelBooking [15] |
| C10 | Open system logs auto-resolved | Booking with unresolved Logging.SystemLog rows (Status not Resolved/Audit) |
Cancel | All become Resolved, ResolvedBy = 'System', Resolution = 'Booking cancelled', DateTimeResolved set. Diagnostic trail is erased — defect C11 |
P2 | CancelBooking [46-49] |
| C11 | Confirmation message — no Tourplan reference | TourPlanReference empty or ≤ 0, ShowConfirmationAfterCancel = true |
Cancel from an overview | Info "Your booking has been successfully cancelled and the reserved inventory has been released where applicable" | P2 | Booking.CancelBooking [55] |
| C12 | Confirmation message — Tourplan reference present | TourPlanReference > 0 |
Cancel | Info "A consultant has been informed of the cancellation of this booking and will be in contact with you in the event of any financial settlement. Please be aware that we can not show this financial settlement in the Wilderness Window." | P1 | Booking.CancelBooking [52] |
| C13 | Silent cancel from bulk paths | ShowConfirmationAfterCancel = false |
Run IVK_CancelBookings |
No confirmation message at all | P3 | Booking.CancelBooking [50],[57] |
| C14 | Notification e-mail status | Booking that ends up CC |
Cancel | EmailConsultantsAgentBooking is called with BookingStatus.CX even though the booking is CC. Check the template wording is not misleading |
P2 | Booking.CancelBooking [40] |
| C15 | Repricing triggered | Booking with chargeable lines | Cancel | Pricing.SumDetailedBookingLines($Booking, false) runs; booking totals reflect fees only |
P1 | Booking.CancelBooking [41] |
| C16 | Audit trail | Any cancellation | Cancel | Booking.Event.Cancellation "Booking cancelled by user" at [8]; BHL_LogEntry_Create "Cancel Booking" with Booking.Event.Cancel at [43] |
P2 | CancelBooking [8],[43] |
| C17 | Archive log on manual cancel | Cancel from an overview grid | Cancel | ArchiveLog with process = 'IVK_CancelBooking_Overview', Reason = 'Manual by :<user>', Whom set |
P2 | Booking.IVK_CancelBooking_Overview [5] |
D. The fee engine — when a fee applies¶
Each of D1–D5 must produce no fee. Assert DetailedBookingLine.SellingPrice is unchanged.
| ID | Case | Preconditions | Steps | Expected | Pri | Source |
|---|---|---|---|---|---|---|
| D1 | Override suppresses all fees | Booking.NoCancelFees = true |
Cancel | No fee on any line, whatever the dates | P1 | Booking.CancelFees [2] |
| D2 | Non-chargeable lines are free | Line Chargeable = false |
Cancel | No fee | P1 | Booking.CancelFees [3] |
| D3 | Unconfirmed lines are free | Line BookingLineStatus = Provisional (not Confirmed) |
Cancel | No fee | P1 | Booking.CancelFees [4] |
| D4 | Lines never written to Tourplan are free | Confirmed chargeable line with no TourplanBookingServiceLine records |
Cancel | No fee, regardless of proximity to travel. High-value case — this is how provisional-only bookings cancel free | P1 | Booking.CancelFees [7] |
| D5 | Zero percentage means no fee | Payment terms configured with CancellationFee = 0 |
Cancel | HasCancelFee = false, ApplyCancelFees is not called |
P1 | Booking.CancelFees [14-16] |
| D6 | Configured payment terms win | Supplier with PaymentTerms/CancellationFee = 35 |
Cancel | Fee is 35%, not any fallback-ladder value | P1 | Booking.GetCancelFeePerc [7] |
| D7 | Empty configured fee is 0% | Payment terms found but CancellationFee empty |
Cancel | 0% → no fee | P2 | Booking.GetCancelFeePerc [7] |
| D8 | Missing Day_First charges 100% |
Confirmed chargeable line with Day_First empty |
Cancel | GetCancelFeePerc returns empty, which Booking.CancelFees [13] converts to 1 → full-value fee. Verify this is intended and not a data-quality trap |
P1 | Booking.GetCancelFeePerc [2], Booking.CancelFees [13] |
| D9 | Relaxed payment terms are honoured | Booking.RelaxedPmtTermsApply = true |
Cancel | Pricing.GetPmtTerms receives the flag and may return different terms |
P2 | Booking.GetCancelFeePerc [5] |
E. The fee ladder — fallback when no payment terms exist¶
All of section E requires a supplier with no matching PaymentTerms. Fee is a fraction:
0.25 = 25%.
| ID | Line type | Days before travel | Expected fee | Pri | Source |
|---|---|---|---|---|---|
| E1 | Accommodation | 90 | 25% | P1 | Booking.GetCancelFeePerc [11] |
| E2 | Accommodation | 61 | 25% | P1 | [11] — boundary is > 60 |
| E3 | Accommodation | 60 | 100% | P1 | [11] — exactly 60 falls to the else branch |
| E4 | Accommodation | 5 | 100% | P1 | [11] |
| E5 | Exploration | 90 / 60 | 25% / 100% | P2 | [10-11] same branch as accommodation |
| E6 | Private Activity | 90 / 60 | 25% / 100% | P2 | [10-11] |
| E7 | Flight | 10 | 0% | P1 | [14] — > 7 |
| E8 | Flight | 8 | 0% | P1 | [14] boundary |
| E9 | Flight | 7 | 25% | P1 | [14] — exactly 7 drops a band |
| E10 | Flight | 4 | 25% | P1 | [14] — > 3 |
| E11 | Flight | 3 | 50% | P1 | [14] boundary |
| E12 | Flight | 2 | 75% | P1 | [14] — > 1 |
| E13 | Flight | 1 | 100% | P1 | [14] final else |
| E14 | Flight | 0 / same day | 100% | P1 | [14] |
| E15 | Transfer, Sundry, Road Transfer, or any other type | any | 0% | P1 | [13] |
| E16 | Associated product — supplier OwnershipID in M1, J1, J2, K1, CR, DM, N1, TN |
any, even 365 days out | 100% | P1 | [8-9] returns empty → Booking.CancelFees [13] → 1 |
| E17 | Same supplier codes with payment terms configured | any | The configured percentage — the $IsAP branch is only reached when terms are missing |
P1 | [6-7] |
| E18 | Supplier with an ownership code not in either hardcoded list | 90 days, accommodation | Falls to the ordinary accommodation ladder (25%). Confirms new supplier codes silently get different treatment — defect C1 | P2 | [3],[10] |
E16 is the highest-risk row in this suite: a master-data gap on an associated-product supplier charges the client the full value of the line.
F. Applying the fee¶
| ID | Case | Preconditions | Steps | Expected | Pri | Source |
|---|---|---|---|---|---|---|
| F1 | Prices are overwritten with the fee | Line with SellingPrice = 1000, CostPrice = 600, fee 25% |
Cancel | SellingPrice = 250, SellingPrice_CancellationAdjustment = 750, CostPrice = 150, CostPrice_CancellationAdjustment = 450 |
P1 | Booking.ApplyCancelFees [6-8] |
| F2 | Original prices are preserved in the "after discounts" fields | Same, with SellingPriceAfterDiscounts = 0 beforehand |
Cancel | SellingPriceAfterDiscounts becomes the original 1000; CostPriceAfterDiscounts becomes 600 |
P1 | Booking.ApplyCancelFees [8] |
| F3 | Existing "after discounts" values are not clobbered | SellingPriceAfterDiscounts = 900 beforehand |
Cancel | Stays 900 — only a zero is overwritten |
P1 | Booking.ApplyCancelFees [8] |
| F4 | Gross profit is the post-fee margin | F1's numbers | Cancel | GrossProfit = 250 − 150 = 100 |
P1 | Booking.ApplyCancelFees [8] |
| F5 | GrossProfitPerc is the pre-fee margin |
F1's numbers | Cancel | GrossProfitPerc = (1000 − 600) / 1000 × 100 = 40, i.e. the original margin, not 100/250 = 40%... assert the actual value and log the mismatch against GrossProfit. Defect C6 |
P2 | Booking.ApplyCancelFees [8] |
| F6 | Fee percentage recorded on the detail line | fee 25% | Cancel | CancelFee_Cost = CancelFee_Sell = 25.00 (rounded to 2dp) |
P2 | Booking.ApplyCancelFees [8] |
| F7 | Fee percentage recorded on the booking line | fee 25% | Cancel | CancelFeeAdj_Cost = CancelFeeAdj_Sell = 25 — unrounded, unlike F6 |
P3 | Booking.ApplyCancelFees [17] |
| F8 | Party cost group receives the waived fraction | fee 25% | Cancel | CancelFee_PCG_BL is called with 0.75, not 0.25 |
P2 | Booking.ApplyCancelFees [16] |
| F9 | Tax recomputed inline when rates are present | TaxRate_Cost and TaxRate_Mkup both set |
Cancel | TaxOnCost, TaxOnMarkup, TaxOnSell recomputed; TaxedSuccessfully = true |
P1 | Booking.ApplyCancelFees [10-13] |
| F10 | Tax falls back when rates are missing | Either rate empty | Cancel | Pricing.CalculateDetailedBookingLineTax is called instead |
P1 | Booking.ApplyCancelFees [14] |
| F11 | Fee-bearing line stays LIVE | Confirmed chargeable line attracting a fee | Cancel the booking | The line remains LIVE = true so it can carry the charge. Do not assert all lines are delisted |
P1 | see §1 of the deep-dive |
| F12 | Booking lands on CC when fees apply |
Booking where at least one line attracts a fee | Cancel | WindowStatus = CC ("CCost"), not CX |
P1 | CancelBooking [35] |
| F13 | Booking lands on CX when no fees apply |
All lines free (any of D1–D5) | Cancel | WindowStatus = CX |
P1 | CancelBooking [35] |
G. The fee override¶
| ID | Case | Preconditions | Steps | Expected | Pri | Source |
|---|---|---|---|---|---|---|
| G1 | Toggle on | NoCancelFees = false |
Click "Override cancellation fees" | NoCancelFees = true; LogEvent(Event.Cancellation, 'Cancellation fees turned off') |
P1 | Booking.IVK_ToggleCancelFees [2-3] |
| G2 | Toggle off | NoCancelFees = true |
Click "Cancel fees overridden" | NoCancelFees = false; log says 'Cancellation fees turned on' |
P1 | Booking.IVK_ToggleCancelFees [2-3] |
| G3 | No approval and no manager gate | Users in Employee and ExternalConsultant |
Toggle the override | Both succeed. There is no manager check and no approval record beyond the event log. Defect C3 — treat as a control finding and confirm with the business | P1 | model roles on IVK_ToggleCancelFees |
| G4 | Manager role is not required | Manager and non-manager Employee | Toggle | Both behave identically | P2 | model roles |
| G5 | Cancel-with-no-fees button sets the flag then cancels | Any cancellable booking | Click the no-fees cancel button | NoCancelFees = true set before CancelBooking runs, so no fee is calculated; log 'No cancellation fees to be calculated for this booking - assume a postponement' |
P1 | Booking.IVK_CancelBooking_Overview_NoCancelFees [3-7] |
| G6 | Post-cancel redirect by user type | Employee, then Agent | Cancel with no fees | Employee → BookingOverview_Party; Agent → BookingOverview_Agent; other → no redirect |
P3 | Booking.IVK_CancelBooking_Overview_NoCancelFees [11-17] |
| G7 | Override is independent of the cancellation reason | Cancel with reason Cancelled_but_not_charging_fees_as_a_favour_to_agent but NoCancelFees = false |
Cancel | Fees are still charged. The reason is descriptive only. Confirm the business expects this | P1 | Booking.CancelFees [2]; reason enum is not read by the fee engine |
| G8 | Waiver reporting | Several cancellations, some with the override | Report on waivers | Waivers are identifiable only via NoCancelFees and the event log, not via CancelBookingReason |
P2 | as G7 |
H. Reviving a cancelled booking¶
| ID | Case | Preconditions | Steps | Expected | Pri | Source |
|---|---|---|---|---|---|---|
| H1 | Only Debug users can revive | Users in Employee, Manager, Agent, Debug |
Look for "Revive cancelled booking" | Visible and executable only for Debug. There is no self-service undo |
P1 | model roles on IVK_Uncancel |
| H2 | Booking returns to Draft | Cancelled booking | Revive | WindowStatus = Draft, InventoryStatus = Pending, Cancelled = false, ReadyForTravel = false |
P1 | Booking.IVK_Uncancel [2] |
| H3 | Wish linkage is destroyed | Cancelled Wish-backed booking | Revive | WishBookingReference and WishPartyGroupId cleared; WishUpdateStatus and WishPartyGroupStatus both Update_required. The booking must be re-provisioned — the original holds are not recovered |
P1 | Booking.IVK_Uncancel [2] |
| H4 | Guest Wish IDs cleared | Guests with WishGuestId |
Revive | All cleared | P2 | Booking.IVK_Uncancel [4-7] |
| H5 | Party cost group Wish IDs reset | PCGs with WishPartyGroupId != 0 |
Revive | All set to 0 | P2 | Booking.IVK_Uncancel [8-11] |
| H6 | Room configs fully reset | Wish AvailableRoomConfig rows |
Revive | WishStatus = Draft, WishSectorBookingId empty, Waitlist = No, all Booked_* cleared, every BookedRoomCount_* zeroed, HoldingSpace = false |
P1 | Booking.IVK_Uncancel [12-15] |
| H7 | Tourplan header reset | Cancelled booking with a Tourplan record | Revive | TP_ResetTPHeaderStatus runs; on success TourplanStatus = Pending |
P1 | DataManagement.TP_UncancelBooking [2-4] |
| H8 | Tourplan reset failure is tolerated | Force TP_ResetTPHeaderStatus to fail |
Revive | Returns false; the Window-side revive has already happened. Assert the resulting inconsistent state and whether the user is told | P1 | DataManagement.TP_UncancelBooking [7], Booking.IVK_Uncancel [23-25] |
| H9 | Service lines re-attached | Cancelled line with Window-created Tourplan service lines, and a matching live line (same Option, same Day_First) |
Revive | Service lines re-point at the live line; TPServiceLineStatus = VY; new line TourPlanStatus = Pending |
P1 | DataManagement.TP_ReviveCancelledBookingLines [10-11] |
| H10 | Ambiguous match picks arbitrarily | Two cancelled lines for the same Option on the same Day_First (e.g. two rooms, two party cost groups) |
Revive | The match takes first with no further discriminator, so service lines can attach to the wrong line. Defect C7 — assert which line receives them and whether it is stable across runs |
P1 | DataManagement.TP_ReviveCancelledBookingLines [5] |
| H11 | Audit trail | Any revive | Revive | Booking.Event.Update with 'Booking UNCANCELLED' |
P2 | Booking.IVK_Uncancel [17] |
| H12 | Step-2 checks gate the Tourplan revive | Booking failing Step2Checks |
Revive | The Window-side reset happens but TP_UncancelBooking is not reached |
P1 | Booking.IVK_Uncancel [19-21] |
I. Waitlist release¶
| ID | Case | Preconditions | Steps | Expected | Pri | Source |
|---|---|---|---|---|---|---|
| I1 | Weekend no-op | Run on a Saturday or Sunday | Run SE_WaitlistRelease |
Nothing is processed | P1 | Booking.SE_WaitlistRelease [2],[30] |
| I2 | Release on availability | Waitlisted live line, Waitlist = Yes, WaitListNowAvailable, WaitlistAvailableFrom at least 2 days old, booking Source = Wilderness_window |
Run on a weekday | Lines flagged Cancel = true and passed to Sub_DeleteBookingline_Mem; a warning e-mail is sent |
P1 | Booking.SE_WaitlistRelease [3-4],[22-24] |
| I3 | Two-day cut-off respected | WaitlistAvailableFrom is 1 day old |
Run | Not released by the set-A rule | P2 | Booking.SE_WaitlistRelease [3-4] |
| I4 | Release on proximity to travel alone | Waitlisted live line, Waitlist = Yes, booking TripStartDate within 42 days, WaitListNowAvailable false |
Run | Released anyway — set B ignores availability. High-value case; confirm the business expects waitlists inside 6 weeks to be dropped regardless | P1 | Booking.SE_WaitlistRelease [5-6] |
| I5 | Outside 42 days and not available | Trip 60 days out, not available | Run | Not released | P1 | Booking.SE_WaitlistRelease [5-6] |
| I6 | Source filter | Waitlisted line on a booking with Source = Quote_only or Ex_window |
Run | Not processed — only Wilderness_window and EX_Tourplan |
P2 | Booking.SE_WaitlistRelease [4],[6] |
| I7 | Agent-owned bookings are skipped entirely | Waitlisted line on a booking whose Booking_Owner is not an Administration.Employee |
Run | Nothing happens — no release, no e-mail. Defect C8. Confirm whether these are handled elsewhere | P1 | Booking.SE_WaitlistRelease [26-27] |
| I8 | E-mail routing | Employee-owned booking with and without a branch/department e-mail | Run | Sent to GetBranchDeptEmail when present, otherwise the owner's own address |
P2 | Booking.SE_WaitlistRelease [17-18] |
| I9 | E-mail content | I2 | Run | Subject "WARNING - we have released your waitlist of :AdvEmail.EmailType.Waitlist_notification; linked to the booking |
P2 | Booking.SE_WaitlistRelease [18-19] |
| I10 | Already-cancelled lines excluded | Booking with Cancelled and CancelledWithCosts lines |
Run | Those are excluded from the working list | P2 | Booking.SE_WaitlistRelease [12] |
J. Housekeeping¶
| ID | Case | Preconditions | Steps | Expected | Pri | Source |
|---|---|---|---|---|---|---|
| J1 | Orphan drafts removed | Booking with Source = Wilderness_window, BookingStep = Step1, empty PartyName, empty BookingReferenceDescriptive, empty WishBookingReference, empty trip dates, NotificationEmail = false, status not CX/CC |
Run Sub_CancelOrphanDraftBookings |
ArchiveLog with Reason = 'Step 1 no party name or reference or dates', then the booking is deleted (it meets CancelBooking's delete conditions) |
P1 | Booking.Sub_CancelOrphanDraftBookings [2-5] |
| J2 | Partially-filled drafts survive | Same but PartyName populated |
Run | Untouched — every condition is ANDed | P1 | Booking.Sub_CancelOrphanDraftBookings [2] |
| J3 | Already-cancelled excluded | Same but WindowStatus = CX |
Run | Untouched | P2 | Booking.Sub_CancelOrphanDraftBookings [2] |
| J4 | No batch limit | Seed several thousand qualifying orphan drafts | Run | All are processed in one pass — the retrieve is unbounded, unlike the 150-row sweeps in SE_CancelExpiredBookings. Measure runtime and lock behaviour. Defect C9 |
P2 | Booking.Sub_CancelOrphanDraftBookings [2] |
| J5 | Expired-booking sweeps | See regression-quote-to-booking.md §I |
— | SE_CancelExpiredBookings is covered there |
— | — |
N. Intent-vs-implementation checks (new)¶
These derive from developer annotations recovered from the model canvas. Each asserts current behaviour and flags a documented intent the behaviour does not match — so each needs a business answer, not just a pass/fail.
| ID | Case | Preconditions | Steps | Expected | Pri | Source |
|---|---|---|---|---|---|---|
| N1 | Waitlist released at 6 weeks, not 8 | Waitlisted live line, Waitlist = Yes, booking TripStartDate 50 days away (inside 8 weeks, outside 6), WaitListNowAvailable false |
Run SE_WaitlistRelease on a weekday |
Not released. The annotation says waitlists inside "8 weeks" are cleared, but the constant is 42 days. Defect C14 — confirm which the business wants | P1 | Booking.SE_WaitlistRelease [5], flow annotation |
| N2 | Waitlist released at 40 days | Same but TripStartDate 40 days away |
Run | Released | P1 | [5-6] |
| N3 | 48-hour rule matches its note | Waitlist available for exactly 48 h | Run | Released — note and code agree here | P2 | [3-4] |
| N4 | Step-2 cancellation sends no ARC | Cancel a booking from wizard step 2 | Cancel | No supplier availability-release notification is sent. Annotation says "if cancel from step 2 - need to send ARCs". Defect C15 — confirm whether camps should be notified | P1 | CancelBooking annotation |
| N5 | OnBase told about cancellations | Cancel a booking through a confirmation path | Cancel | Onbase.BookingStatus_Post invoked asynchronously; failure is silent |
P2 | CancelBooking, executeMicroflowInBackground |
| N6 | Retention periods match the 2023 agreement | Quote 3 years past trip end; another 5 years past | Run SE_CancelExpiredBookings |
3-year survives, 5-year deleted. The 4-year rule was agreed 2023-05-05 by Joe and Francis; the ArchiveLog.Reason text still says "6 months"/"one year" and is stale. Assert the queries, and log the strings for correction |
P1 | flow annotations, sweeps 2–4 |
| N7 | Legacy fee ladder is not in use | Accommodation line, no payment terms, 58 days before travel | Cancel | 100% under the current ladder. The pre-2021 ladder in the canvas note would have charged 20% at ≥56 days. Confirms the old graduated scale is retired | P2 | Booking.GetCancelFeePerc [11] and its annotation |
| N8 | BookingLine.Cancel is set by amendments, not just cancellations |
A live booking; no cancellation performed | Remove a night via API.ChangeAccomNumberOfNights (or DeleteAccomodation, or AddDaysToBooking) |
Affected lines have Cancel = true without any cancellation having occurred. Assert that BookingLineStatus is not Cancelled/CancelledWithCosts and Booking.Cancelled is still false |
P1 | Booking.DeleteTravelLocation, ClearAccommodationRelatedBookingLines, AdjustBookingLinesForDayChanges |
| N9 | Cancellation reporting uses the right signal | Perform one real cancellation and one amendment that drops a line | Query both ways | Counting BookingLine.Cancel returns 2; counting BookingLineStatus in (Cancelled, CancelledWithCosts) returns 1. Any report using the former over-counts cancellations |
P1 | as N8 |
K. Known defects to assert against¶
| ID | Defect | Test |
|---|---|---|
| K1 | C1 — supplier ownership codes hardcoded in two flows with different lists | E16, E18. Assert that a supplier with a new code silently changes fee treatment |
| K2 | C2 — the two cancel entry points enforce different rules | B6, B7 |
| K3 | C3 — fees waivable by Employee/ExternalConsultant with no approval | G3. Standing control test |
| K4 | C4 — CancelFees computes $Invoiced and discards it |
Cancel an already-invoiced line and an uninvoiced one at the same fee percentage; assert the fee is identical, then raise the question of whether it should be |
| K5 | C6 — GrossProfitPerc stores the pre-fee margin |
F5 |
| K6 | C7 — revive matches on (Option, Day_First) and takes first |
H10 |
| K7 | C8 — waitlist release skips agent-owned bookings | I7 |
| K8 | C9 — orphan cleanup is unbounded | J4 |
| K9 | C10 — CancellationDate null on the simple cancel path |
A5. Any cancellation report filtering on it must be checked |
| K10 | C11 — cancellation auto-resolves open system logs | C10 |
| K11 | C12 — visibility conditions listing every enum value | Cosmetic; confirm the no-fee cancel buttons are meant to be always visible |
| K12 | C13 — dated clones (CancelBooking_230226, SE_WaitlistRelease_230226, SE_WaitlistRelease_260326) |
After any fix, assert clones are updated or deleted |
| K13 | C14 — waitlist released at 42 days against a documented intent of 8 weeks | N1, N2 |
| K14 | C15 — step-2 cancellation ARC notification never implemented | N4 |
| K15 | C16 — 2021 "temporary while we sort out the statuses" workaround still live | Identify what it guards before changing cancellation status logic |
L. Suggested smoke path¶
Fifteen minutes, two bookings, covers the spine and the money.
- A1 — create an empty Step-1 draft, cancel it. Assert the row is deleted and an
ArchiveLogexists. - Build and confirm a second booking with an accommodation line 90 days out and a flight line 5 days out, from a supplier with no payment terms.
- D4 — before cancelling, check the lines have Tourplan service lines. If not, the fee is zero and the rest of the test is void.
- E1 / E10 — cancel. Assert accommodation charged 25%, flight charged 25%.
- F1 / F2 — assert
SellingPricenow holds the fee andSellingPrice_CancellationAdjustmentholds the write-off. - F11 / F12 — assert the fee-bearing lines are still
LIVEand the booking isCC. - C12 — assert the financial-settlement message appeared.
- G1 / G5 — on a third booking, set the fee override and cancel. Assert
CX, no fees, and the 'assume a postponement' log entry. - H1 — confirm the "Revive cancelled booking" button is unavailable to a normal user.
M. Environment notes¶
- Fee cases need controlled
PaymentTermsmaster data. Seed a supplier with terms and one without, and one carrying an$IsAPownership code (M1,J1,J2,K1,CR,DM,N1,TN) to exercise E16. - Section E depends entirely on
daysBetween(now, BookingLine/Day_First). Use fixtures with dates relative to run time; the boundary cases (E2/E3, E8/E9, E11/E12) rot within a day otherwise. SE_WaitlistReleaseno-ops at weekends (I1). Either run it on a weekday or control the clock.- Wish and Tourplan failure cases (C4, C5, H8) need stubbed server settings — see
docs/02-architecture.md. - Reviving (section H) destroys Wish linkage and cannot be undone by re-cancelling. Use throwaway bookings.