Skip to content

Regression test cases — Quote to Booking conversion

Audience: QA. Scope: creating a quote → converting it to a live booking → holding inventory provisionally → confirming to Tourplan, plus the expiry and housekeeping jobs that act on quotes.

Every case below is derived from a specific branch in the Mendix model, and the Source column names the microflow and node number so you can re-derive the expected result rather than trusting this table. Mechanism detail lives in docs/deep-dives/quote-to-booking.md.

Priority: P1 = blocks release, P2 = must pass before sign-off, P3 = worth covering when time allows.

None of these cases have been executed. They are derived from the model's decision logic and state what the system currently does; a passing case says the behaviour is unchanged, not that the behaviour is correct. Cases that assert a known defect are marked as such.

Revised 2026-08-07. Sections M (API path), N (OnBase) and O (HubSpot) are new — they cover behaviour that was invisible in the previous model extract. See quote-to-booking.md §12.


How to read a booking's state

Four attributes change independently. Assert on all four, not just the one on screen.

Attribute Quote After convert After provisional After confirm
Source Quote_only ("Quote") Wilderness_window ("Window") unchanged unchanged
WindowStatus Quote_only ("Quote") Draft P ("Prov") C ("Conf")
InventoryStatus Quote_only Draft set by Wish sync set by Wish sync
TourplanStatus Draft Draft — unchanged unchanged set by Tourplan write

Booking-line status runs Draft → Provisional → Confirmed, with Waitlisted as the no-availability branch and Requested ("On request") for BookingMethod = RQ lines.


A. Quote creation

ID Case Preconditions Steps Expected Pri Source
A1 Agent creates a quote, single lead consultant Agent user, agency with AgencyAccess ≠ PortalUserOnly, exactly one Agency_EmployeeLead New Quote Booking created; Source = Quote_only, WindowStatus = Quote_only, BookingStep = Step1, reference DRAFT; wizard Step 1 opens P1 Booking.IVK_NewQuote_Only [13-14]
A2 Agent creates a quote, multiple lead consultants Same but two or more Agency_EmployeeLead New Quote Page SelectConsultantBooking_Quote_Only opens instead of creating directly P2 Booking.IVK_NewQuote_Only [16]
A3 Portal-only agent is blocked Agent whose agency has AgencyAccess = PortalUserOnly New Quote Warning: "You currently only have access rights to the Information Centre and camp availability…". No booking created P1 Booking.IVK_NewQuote_Only [7-8]
A4 Employee creates a quote Employee user New Quote NewBookingByEmployee path runs with BookingSource.Quote_only P2 Booking.IVK_NewQuote_Only [21-22]
A5 Consultant not set up in Tourplan Owner/agency combination with no TPLoginDetails, or a TPLoginDetails with empty instance New Quote Info: "{name} is not setup in any Tourplan". No booking created — verify no orphan Booking row P1 Booking.CreateAgencyBooking [4],[6],[29]
A6 Default child structure is seeded A1 passes Inspect the new quote One BookingLeg (number 1, active), one PartyCostGroup named Guests with Pax_Adults_Std = 2, Pax_Total = 2, StandardRoomCount = 1, TotalRooms = 1, one Booking_RequiredRooms P2 Booking.CreateAgencyBooking [15-17]
A7 Agency defaults are copied onto the quote Agencies configured with contrasting values (CurrencyPreferred, UsesDMC, AllowInvoiceSharing, UseGEL, HasAccessSADCPricing, MeetGreet_*) Create a quote from each Each corresponding booking attribute matches its agency source. Regression risk: these are copied once at creation and never re-read P2 Booking.CreateAgencyBooking [10]
A8 Audit trail on creation A1 passes Check event log Booking.Event._New with 'New quote by agent #:' + BookingNumber (note the colon — matching #12345 will not hit #:12345); a Logging.SystemLog row 'New booking by agent' P3 Booking.CreateAgencyBooking [21],[24]
A9 API-created quote Partner API call with IsQuote = true POST via API Source = Quote_only, WindowStatus = Quote_only, and BookingStep set to Step3 by the API path (not Step1) P2 API.CreateWindowBooking [105],[192]
A10 API-created live booking Same with IsQuote = false POST via API Source = Wilderness_window, WindowStatus = Draft P2 API.CreateWindowBooking [105]

B. Convert to live booking — guards

All of these must reject and leave Source = Quote_only unchanged. Assert the booking is untouched, not merely that a message appeared.

ID Case Preconditions Steps Expected Pri Source
B1 Non-quote cannot be converted Booking with Source = Wilderness_window Convert Info: "You can only convert quote bookings with availability to a live booking"; nothing changes P1 Booking.ConvertToLiveBooking [2],[22]
B2 Already-converted quote cannot be re-converted Run C1 first, then convert again Convert twice Second attempt hits B1's message. Conversion is not idempotent — confirm no second Event._New is logged P1 Booking.ConvertToLiveBooking [2]
B3 Wrong wizard step Quote at BookingStep = Step1 Convert Info: "You cannot convert the booking unless you are on step 3 of the booking wizard and have valid pricing" P1 Booking.ConvertToLiveBooking [3-4]
B4 Step 2 also rejected Quote at BookingStep = Step2 Convert Same rejection as B3. Only Step3 and Step4 are accepted P2 Booking.ConvertToLiveBooking [3]
B5 Non-manager, trip starts inside 24 h Non-manager user; TripStartDate less than 1 day away; quote at Step 3/4 Convert Warning: "Only a manager can book less than 1 day prior to the trip start date. Please ask you manager to create the booking (complete step1)"; nothing changes P1 Booking.ConvertToLiveBooking [6],[17-19]
B6 Manager, trip starts inside 24 h Manager user, same booking Convert Info: "Please be aware that this booking is less than a day before trip start date. Because you have manager rights, we will allow this booking", then conversion proceeds P1 Booking.ConvertToLiveBooking [21]
B7 Boundary — exactly 1 day TripStartDate exactly 1 day out Convert as non-manager Condition is daysBetween(now, TripStartDate) < 1, so 1 day should pass without the manager gate. Verify the boundary explicitly P2 Booking.ConvertToLiveBooking [6]
B8 Rooming check fails Quote with an AvailableRoomConfig flagged BookThis whose RequiredRoom violates its Option_Policy (e.g. child outside the age policy) Convert Conversion is blocked and booking unchanged. Known defect D1: Booking.ConvertToLiveBooking [9] has no message of its own — if RoomingChecks is silent for this failure mode, the button appears to do nothing. Raise a bug if no message appears P1 Booking.ConvertToLiveBooking [8-9],[16]
B9 Locked booking cannot re-enter the wizard Quote with NoChangesAllowed = true at Step 3/4 with valid rooming Convert Status changes are applied at [10], then Booking.WizardStep2_FromBookingFile [2-3] shows "This booking has been locked. You may not make any changes to this booking in the Window, it must be taken ex-Window."the conversion still happened. Confirm this is intended behaviour with the product owner P2 Booking.ConvertToLiveBooking [10-12], Booking.WizardStep2_FromBookingFile [2]
B10 Grid button — nothing selected Quotes overview grid Click Convert with no rows selected Info: "Please select quote to convert" P3 Booking.IVK_ConvertToBooking_List [10]
B11 Grid button — multiple selected Same Select 2 quotes, click Convert Info: "Please select only 1 booking at a time"; neither booking changes P2 Booking.IVK_ConvertToBooking_List [8]
B12 Role check Users in each of Agent, Employee, Manager, Debug, and one role outside that list (e.g. Booking.SupplierUser) Attempt convert Only the four listed roles can execute ConvertToLiveBooking / IVK_ConvertToBooking_List P2 model roles on ConvertToLiveBooking

C. Convert to live booking — happy path

ID Case Preconditions Steps Expected Pri Source
C1 Convert from Step 3 Quote at BookingStep = Step3, rooming valid, trip more than 1 day out Convert Source = Wilderness_window, WindowStatus = Draft, InventoryStatus = Draft, NotificationEmail = false. Wizard returns to Step 2 via IVK_GoBackToScreen2 P1 Booking.ConvertToLiveBooking [10-12], Booking.WizardStep2_FromBookingFile [17]
C2 Convert from Step 4 clears pricing Quote at BookingStep = Step4 with Priced = Yes Convert Same status changes and Priced is cleared to empty. The booking cannot be confirmed until it is repriced P1 Booking.WizardStep2_FromBookingFile [14-15]
C3 Tourplan status untouched C1 Convert, inspect TourplanStatus still Draft. Nothing is written to Tourplan at conversion P1 Booking.ConvertToLiveBooking [10]
C4 Audit trail on conversion C1 Convert, check log Booking.Event._New with description "New real booking created from a quote" P2 Booking.ConvertToLiveBooking [11]
C5 Availability is refreshed C1 with a camp whose availability changed since the quote was priced Convert IVK_RefreshAvailability_X runs; lines whose availability has gone should surface as unavailable on Step 2 rather than silently carrying stale quote availability P1 Booking.ConvertToLiveBooking [13]
C6 Commit fires no event handlers C1 Convert The write is commit YesWithoutEvents. Any integration or handler expected to trigger on the booking commit will not fire. Verify nothing downstream depends on it P2 Booking.ConvertToLiveBooking [10]
C7 API conversion locks the ITRVL booking Quote with APIBooking = true, API_System = ITRVL, converted with CalledFromApi = true Convert via API AgentBlock = true and API.SendWebHookLockBooking is called. Agent can no longer see the booking P2 Booking.WizardStep2_FromBookingFile [6-7]
C8 API conversion, non-ITRVL APIBooking = true, different API_System Convert via API AgentBlock unchanged; webhook still called P3 Booking.WizardStep2_FromBookingFile [6-7]
C9 Unknown wizard step Booking whose BookingStep is empty or otherwise unhandled, forced to pass guards Convert Info: "Unknown booking step - apologies just use wizard" P3 Booking.WizardStep2_FromBookingFile [12]
C10 Quote-to-booking on every entry point Repeat C1 from: booking file (BookingFile_Agent_050526), booking menu snippet (Sn_BookingMenu), agent overview grid (BookingOverview_Agent), quotes overview grid (BookingOverview_Quotes) Convert from each Identical outcome from all four. These are separate buttons wired to two different wrappers P2 pages.txt references

D. Provisional hold

ID Case Preconditions Steps Expected Pri Source
D1 Draft booking goes provisional Converted booking, WindowStatus = Draft, all lines bookable Book provisionally (all inventory) WindowStatus = P; live lines move to Provisional P1 Booking.Sub_BookProvisionally_AllInventory [6],[17]
D2 Pending also goes provisional WindowStatus = Pending Same WindowStatus = P P2 Booking.Sub_BookProvisionally_AllInventory [6]
D3 Confirmed booking is not downgraded WindowStatus = C Same WindowStatus stays C P1 Booking.Sub_BookProvisionally_AllInventory [6]
D4 Unavailable accommodation waitlists Accommodation line where CanBook is false and line is Draft or Waitlisted Book provisionally Line → Waitlisted, RoomWaitlistFlag = true P1 Booking.Sub_BookProvisionally_AllInventory [19]
D5 Unavailable non-accommodation drops to Draft Non-accommodation line where CanBook is false Book provisionally Line → Draft, RoomWaitlistFlag = false P2 Booking.Sub_BookProvisionally_AllInventory [19]
D6 On-request lines Line with BookingMethod = RQ Book provisionally BookingLineStatus = Provisional, InventoryBookingSectorStatus = Requested ("On request") P2 Booking.Sub_BookProvisionally_AllInventory [11],[17]
D7 Third-party inventory included on "All" Booking with LiveRequest lines, selection All Book provisionally ProvisionallyBookLR runs before the Wilderness-only path P2 Booking.Sub_BookProvisionally_AllInventory [38-39]
D8 Wish failure does not report success Force Wish_Provisional to fail Book provisionally Flow does not take the success path — no Event.Provisional log, no consultant e-mail, no expiry date assigned P1 Booking.Sub_BookProvisionally_AllInventory [22-23]
D9 Provisional notifications D1 passes Book provisionally EmailConsultantsAgentBooking(..., BookingStatus.P) sends; Logging.SystemLog row "Booking updated to provisional"; booking-history entry Booking.Event.Provisional P2 Booking.Sub_BookProvisionally_AllInventory [29],[31],[32]

E. Expiry

ID Case Preconditions Steps Expected Pri Source
E1 Expiry taken from earliest inventory line Provisional booking with several Wish inventory lines with differing ExpirationDate Book provisionally Booking.ExpirationDate = earliest such line's date, trimmed to day (UTC) P1 Booking.Sub_AssignExpiryDate [2],[4]
E2 48-hour warning Earliest line expires within 2 days Book provisionally Info: "Please be aware one or more of your Wish bookings will expire within the next 48 hours" plus a matching event-log entry P2 Booking.Sub_AssignExpiryDate [6-8]
E3 Already-expired warning Earliest line's ExpirationDate in the past Book provisionally Info: "…may already be released in Wish" plus event-log entry P2 Booking.Sub_AssignExpiryDate [23-24]
E4 No inventory line, trip far out No qualifying inventory line; trip start more than 7 days away Book provisionally ExpirationDate = today + Agency/ExpiryDays. Test with at least two agencies with different ExpiryDays P1 Booking.Sub_AssignExpiryDate [20-21]
E5 No inventory line, trip within 7 days Trip start within 7 days Book provisionally ExpirationDate = TripEndDate + 1 day P2 Booking.Sub_AssignExpiryDate [22]
E6 Expiry cascades to chargeable lines Booking-level expiry set; chargeable non-Wish lines have no expiry of their own Book provisionally Those lines inherit the booking's ExpirationDate P2 Booking.Sub_AssignExpiryDate [15-18]
E7 Only a Manager can extend Non-manager and Manager users Attempt Extend Expiry Non-manager cannot execute IVK_ExtendExpiry; Manager can P1 model roles on IVK_ExtendExpiry
E8 Extension pushes to Wish Manager extends expiry Extend DataManagement.UpdateExpiryDatesWish runs; event log "Expiry date updated with warning of WAITLISTS" P2 Booking.IVK_ExtendExpiry [4-5]
E9 Expired quote prices warn but do not block Live Draft line with BookingMethod = 'LR' and QuoteExpirationDate in the past Continue through the wizard Info listing each expired line with its start date and expiry date. The user is not blocked P2 Booking.CheckQuoteExpiry [2],[7]
E10 Dynamic-pricing expiry sourced correctly Supplier with UsesDynamicPricing = true Price a quote QuoteExpirationDate comes from the earliest PricingDynamic.DynamicPrice/ExpiryDate for the room config P3 Booking.GetQuoteExpiry [2-4]

F. Confirmation — guards

Each of these must block confirmation and leave WindowStatus unchanged.

ID Case Preconditions Steps Expected Pri Source
F1 Missing tax indicator Agency with no Agency.TaxIndicator for the booking's TourplanInstance Confirm Info: "The is no TaxIndicator setup for Agency {name} for Tourplan Instance {instance}. Please contact RARA if you urgently need to confirm the booking or else try tommorrow after the nighly updates."; confirmation aborts P1 ConfirmBooking [3-4], Booking.CheckAgencyTaxIndicator [6]
F2 Tax indicator exists for a different Tourplan instance Agency has a TaxIndicator but for another instance Confirm Still blocked — the check is per instance, not per agency P1 Booking.CheckAgencyTaxIndicator [3]
F3 Wrong wizard step BookingStep = Step1 or Step2 Confirm Info: "You must be on step 3 of the booking wizard before you can confirm your booking, please go to the booking wizard and navigate to the quote screen" P1 ConfirmBooking [6-7]
F4 Locked booking NoChangesAllowed = true Confirm Info: "Please have your consultant take this booking ex-window as it is locked for further changes." P1 ConfirmBooking [10-11]
F5 Tourplan closed Tourplan closed in the Window Confirm Info: "Tourplan is currently closed in the Window. Please try again later or contact support to reopen Tourplan"; WindowStatus unchanged P1 ConfirmBooking [19-20]
F6 Unpriced booking Priced is No (or empty) Confirm Info: "The booking cannot be taken further as the booking is not priced currently - please reprice, otherwise there may be a pricing issue that needs to be resolved first" P1 Booking.Sub_ValidateBefore_TPConfirm [16-17]
F7 Convert-then-confirm without repricing Quote converted from Step 4 (so Priced was cleared by C2), then confirm immediately Convert, Confirm Blocked by F6's message. This is the highest-value cross-stage regression in the suite P1 Booking.WizardStep2_FromBookingFile [14] + Booking.Sub_ValidateBefore_TPConfirm [16]
F8 Partial confirm with no confirmed line $Full = false (Confirm Partial), no live chargeable line in Confirmed Confirm partial Info: "At least one chargeable booking line needs to be in a confirmed status otherwise there are no lines to write to Tourplan" P1 Booking.Sub_ValidateBefore_TPConfirm [46]
F9 Full confirm with un-provisionalised inventory $Full = true, a live chargeable line whose InventoryBookingSectorStatus is outside Confirmed/Provisional/Cancelled/Not_applicable/Waitlisted Confirm Page Booking.UnProvisionalBookingLines opens listing them; confirmation aborts P1 Booking.Sub_ValidateBefore_TPConfirm [49-51]
F10 Draft booking is auto-provisionalised WindowStatus = Draft at confirm time Confirm Sub_BookProvisionally_AllInventory(All, …) runs first; if it succeeds, validation continues. A user can go Draft → Confirmed in one click P1 Booking.Sub_ValidateBefore_TPConfirm [14],[42]
F11 Auto-provisional failure blocks confirm Same as F10 but force the provisional step to fail Confirm Confirmation aborts, returns false P1 Booking.Sub_ValidateBefore_TPConfirm [43-45]
F12 Override line without an edited description Live chargeable line with OverrideOption, non-Accommodation, linked Option also OverrideOption, DescriptionEdited = false, not a private journey, subtype not Provide_wiggle/DMC_Override Confirm Info: "You cannot confirm this booking as the following override lines require an updated description …" naming line description and supplier P2 Booking.Sub_ValidateBefore_TPConfirm [21-34]
F13 Override line exclusions Same but subtype Provide_wiggle / DMC_Override / accommodation / private journey Confirm Those lines are excluded and do not block P2 Booking.Sub_ValidateBefore_TPConfirm [21]
F14 Unconfirmed suppliers — advisory only Supplier confirmations outstanding, HoldingPattern = false Confirm InventoryStatus set to Pending, page UnconfirmedBookingLines_AP opens — but validation returns true and confirmation continues. Verify this is intended P1 Booking.Sub_ValidateBefore_TPConfirm [35-38]
F15 Holding pattern bypasses supplier check Same but HoldingPattern = true Confirm No InventoryStatus = Pending, no page; proceeds P2 Booking.Sub_ValidateBefore_TPConfirm [36]
F16 Holding pattern bypasses validation failure Any validation failure with HoldingPattern = true Confirm ConfirmBooking [16] proceeds regardless of the validation result. Deliberately powerful — verify it is restricted appropriately P1 ConfirmBooking [16]
F17 Wish lines not all confirmed Wish booking where Confirm_Wish cannot confirm every sector Confirm Info: "Not all of your Wish booking lines were confirmed, please resolve this before confirming to Tourplan (or exclude these Wish lines from the confirmation process)"; returns false P1 Booking.Sub_ConfirmBooking [4-5],[39]

G. Confirmation — permissions

ID Case Preconditions Steps Expected Pri Source
G1 Agent without confirm rights Agent/AllowConfirmOfBookings = false Confirm Info: "Please contact your Agency Administrator in order to confirm this booking." P1 Booking.Sub_ValidateBefore_TPConfirm [72]
G2 Agent whose agency lacks full access AllowConfirmOfBookings = true but Agency/AgencyAccess ≠ FullWindowAccess Confirm Info: "You do not have rights to confirm a booking, please speak to a WS consultant" P1 Booking.Sub_ValidateBefore_TPConfirm [68-69]
G3 Agent fully entitled AllowConfirmOfBookings = true, AgencyAccess = FullWindowAccess Confirm Proceeds to the content checks P1 Booking.Sub_ValidateBefore_TPConfirm [68]
G4 ZZZ agency blocked Employee, Agency/EmployeeAccess = ProvisionalBookingOnly, agency Code starts ZZZ Confirm Info: "You cannot confirm a 'ZZZ' agency in Tourplan. Please change the agency in order to proceed." P1 Booking.Sub_ValidateBefore_TPConfirm [55-56]
G5 Provisional-only agency, non-manager Employee (not manager), EmployeeAccess = ProvisionalBookingOnly, non-ZZZ agency Confirm Info: "You cannot confirm this agency, unless you are a manager and it is within 14 days of travel date." P1 Booking.Sub_ValidateBefore_TPConfirm [65]
G6 Provisional-only agency, manager outside 14 days Manager, EmployeeAccess = ProvisionalBookingOnly, trip more than 14 days out Confirm Same rejection as G5 — both conditions are required P1 Booking.Sub_ValidateBefore_TPConfirm [63-64]
G7 Provisional-only agency, manager inside 14 days Manager, trip 14 days or less away Confirm Proceeds P1 Booking.Sub_ValidateBefore_TPConfirm [63]
G8 Boundary — exactly 14 days Manager, daysBetween(now, TripStartDate) exactly 14 Confirm Condition is <= 14, so this should pass. Test explicitly P2 Booking.Sub_ValidateBefore_TPConfirm [63]
G9 Unrestricted agency employee Employee, EmployeeAccess ≠ ProvisionalBookingOnly Confirm No manager or 14-day constraint applies P2 Booking.Sub_ValidateBefore_TPConfirm [53]

H. Confirmation — happy path and downstream effects

ID Case Preconditions Steps Expected Pri Source
H1 Full confirm All guards pass, $Full = true, provisional lines present Confirm WindowStatus = C; ConfirmationDate set to now; all live Provisional lines and Draft lines with empty BookingMethod become Confirmed P1 ConfirmBooking [23],[26-29]
H2 ConfirmationDate not overwritten on reconfirm Booking already confirmed once, ConfirmationDate populated Reconfirm ConfirmationDate retains its original value P1 ConfirmBooking [23]
H3 Partial confirm leaves other lines alone $Full = false, mix of Confirmed and Provisional lines Confirm partial Only the already-Confirmed lines go to Tourplan; Provisional lines are untouched P1 ConfirmBooking [25]
H4 No Tourplan confirmation required TP_IsConfirmationRequired returns false Confirm Info: "No Tourplan Confirmation Required !", returns success, WindowStatus = C. Nothing is written to Tourplan and FinishConfirmation does not run — so no deposit invoice, no HubSpot push. Verify against expectations P1 ConfirmBooking [31-34]
H5 Tourplan write succeeds Normal path Confirm TP_ConfirmLinesInTourplan runs; WishAPI.UpdateTourplanReference populates TourPlanFullReference; GuestDetailsStatus = NeedsUpdate; OriginalConfirmedStartDate set to TripStartDate if it was empty P1 Booking.Sub_ConfirmBooking [18],[20],[25]
H6 Tourplan notes updated for eligible statuses TourplanStatus in C, D, I, Pending Confirm TP_UpdateTourplanNotes runs; failure is logged Critical but does not abort P2 Booking.Sub_ConfirmBooking [21-23],[31]
H7 Tourplan write fails Force TP_ConfirmLinesInTourplan to error Confirm Error: "Booking has failed to confirmed. Please see error log for details."; a Critical Logging.SystemLog entry naming Sub_ConfirmBooking. WindowStatus is already C — assert the inconsistent state and that LeaveBookingChecks nags on exit (defect D2) P1 Booking.ConfirmBooking [43], Booking.Sub_ConfirmBooking [34]
H8 Ex-Tourplan booking TourplanStatus = EX_Tourplan and confirmation fails Confirm Error: "The booking has been disconnected from Tourplan (ExTP). All Tourplan changes required must now be made in Tourplan as the booking has travelled already." P2 Booking.ConfirmBooking [46]
H9 Referral marked confirmed Booking linked to a Booking.Referral not yet Confirmed Confirm Referral/Status = LeadStatus.Confirmed P2 Booking.UpdateReferralAfterConfirm [5]
H10 Referral already confirmed Referral already Confirmed Confirm No change, no error P3 Booking.UpdateReferralAfterConfirm [4],[7]
H11 First confirm raises a deposit invoice TourPlanFullReference empty; AllowInvoiceSplitting, GroupsBooking, EU_Booking, HoldingPattern all false; CheckInvoicing allows Confirm CheckDepositInvoice runs and a deposit invoice is created P1 Booking.FinishConfirmation [12-14]
H12 Second confirm does not re-invoice TourPlanFullReference populated Reconfirm $FirstConfirm = false → no deposit invoice P1 ConfirmBooking [14], Booking.FinishConfirmation [13]
H13 Deposit suppressed — invoice splitting AllowInvoiceSplitting = true Confirm Info: "No deposit invoice has been created due to invoice splitting being allowed on this booking. Please ensure the booking is deposit invoiced."; no invoice P2 Booking.FinishConfirmation [30]
H14 Deposit suppressed — groups/series GroupsBooking = true Confirm Info: "…due to this being a Groups or Series booking…" P2 Booking.FinishConfirmation [29]
H15 Deposit suppressed — EU booking EU_Booking = true Confirm Info: "…due to this being an EU booking…" P2 Booking.FinishConfirmation [28]
H16 Deposit suppressed — holding pattern HoldingPattern = true Confirm Info: "No deposit invoice has been created due to this being in a holding patter. Please ensure the booking is deposit invoiced." (typo is in the model) P2 Booking.FinishConfirmation [26]
H17 HubSpot receives confirmed lodges H1 passes Confirm HubSpot.AddConfirmedLodgesToHubSpotBooking runs; confirmed lodges appear against the HubSpot booking P2 Booking.FinishConfirmation [4]
H18 Onbase notified H1 passes Confirm Onbase.BookingStatus_Post runs P2 Booking.FinishConfirmation [21]
H19 Guests written to Wish WishBooking = true Confirm WriteGuestsToWish($Booking, true) runs. On failure a Critical log "Failed to write guests to Wish on confirmation" is written and confirmation still completes P2 Booking.FinishConfirmation [23-24]
H20 Agency invoice link set H1 passes Confirm Booking_AgencyInvoice points at the booking's agency P3 Booking.FinishConfirmation [19-20]
H21 Confirmation e-mails H1 passes Confirm SendAutomatedEmails_Confirmation for chargeable confirmed lines whose TourPlanStatus ≠ Confirmed; EmailConsultantsAgentBooking(…, BookingStatus.C) P2 Booking.Sub_ConfirmBooking [7],[12]
H22 Audit trail on confirmation H1 passes Confirm Booking.Event.Confirm "Booking updated to confirmed"; booking-history entry from BHL_LogEntry_Create with 'Confirm booking' P2 Booking.ConfirmBooking [38], Booking.Sub_ConfirmBooking [26]
H23 Gorilla permits on cancelled lines Booking with a GorillaReceiptedVoucher on a Cancelled/CancelledWithCosts line, or a permit-date mismatch Confirm HasCancelLinesWithPermits = true; false when no such line P3 Booking.Sub_ConfirmBooking [14-15]

I. Housekeeping job — SE_CancelExpiredBookings

ID Case Preconditions Steps Expected Pri Source
I1 Stale provisional cancelled in Wish Booking with TripEndDate more than 13 months ago, WindowStatus P or Pending, TourplanStatus Draft or EX_Tourplan, Source Wilderness_window or EX_Tourplan, TourPlanReference = 0, and Wish reports CANC Run the job WindowStatus = CX, InventoryStatus = CX, CancelBooking runs, ArchiveLog row written P1 Booking.SE_CancelExpiredBookings [8-17]
I2 Not cancelled in Wish is skipped Same but Wish does not report CANC Run the job Booking untouched P1 Booking.SE_CancelExpiredBookings [11-12]
I3 Old quotes deleted Source Quote_only or Placeholder, BrochureCosting = false, TripEndDate more than 4 years ago Run the job DeleteBooking runs; ArchiveLog written first P1 Booking.SE_CancelExpiredBookings [23-28]
I4 Brochure-costing quotes deleted earlier Same but BrochureCosting = true, TripEndDate more than 2 years ago Run the job Deleted P2 Booking.SE_CancelExpiredBookings [32-37]
I5 Recent quotes survive Quote with TripEndDate 1 year ago Run the job Untouched. Guards against an off-by-one in the date arithmetic P1 Booking.SE_CancelExpiredBookings [23]
I6 Cancelled bookings deleted only when the party has none live WindowStatus = CX, TripStartDate more than 4 years ago, no Tourplan or Wish reference Run twice: once where the party has a live C/P booking, once where it does not Deleted only in the second case P1 Booking.SE_CancelExpiredBookings [43-49]
I7 Batch cap More than 150 qualifying records in any sweep Run the job At most 150 processed per sweep per run; the rest are picked up on later runs P2 SE_CancelExpiredBookings limits
I8 Archive log accuracy I3 or I6 Run, inspect ArchiveLog Known defect D4/D5: reason strings state 6 months / 1 year while the queries use 13 months / 4 years, and the sweep-4 party name is read off an empty object. Log the mismatch rather than adjusting expectations P2 Booking.SE_CancelExpiredBookings [16],[27],[46-47]

J. Known defects to assert against

Turn each of these into a standing test so a fix is detected, and a regression is caught.

ID Defect Test
J1 D1 — silent rooming rejection on convert B8. Assert some user-visible message appears
J2 D2WindowStatus = C before the Tourplan write succeeds H7. Assert the inconsistent state is reachable and that the warning appears on leaving the booking
J3 D3Sub_ConfirmBooking returns success when Tourplan is closed If ConfirmBooking's own Tourplan guard can ever be bypassed (e.g. HoldingPattern), confirm whether FinishConfirmation runs with nothing written to Tourplan
J4 D6 — quote-expiry chaser e-mail is dead Maintenance.SE_SendQuoteExpiryEmails is excluded from deployment and filters on QuoteExpiry_notused. Assert no such e-mail is sent, and correct any test or runbook expecting one
J5 D7ConvertToLiveBooking_170626 stale duplicate After any fix to ConvertToLiveBooking, assert the clone is deleted or updated too
J6 D9 — messages say "step 3" while Step3 or Step4 is accepted B3, F3. Cosmetic; log for copy review
J7 D10BookQuote collapses all conversion failures into a bare Success = false M10
J8 D11API_System stamped ITRVL for every API caller M8
J9 D12Sub_AssignExpiryDate's note says "latest", the sort takes the earliest E1. Assert the earliest is used and raise the contradiction with the flow's owner
J10 OnBase failures are silent N4

K. Suggested smoke path

Ten minutes, one booking, covers the spine:

  1. A1 — agent creates a quote. Assert Source/WindowStatus = Quote_only.
  2. Complete Steps 1–3, price it. Assert BookingStep = Step4, Priced = Yes.
  3. B3 — try to convert from Step 1 first (use a second quote). Assert rejection.
  4. C2 — convert from Step 4. Assert Source = Wilderness_window, WindowStatus = Draft, TourplanStatus still Draft, Priced cleared.
  5. F7 — try to confirm without repricing. Assert the "not priced" rejection.
  6. Reprice. D1 — book provisionally. Assert WindowStatus = P and an ExpirationDate.
  7. H1 — confirm. Assert WindowStatus = C, ConfirmationDate set, TourPlanFullReference populated, lines Confirmed.
  8. H11 — assert a deposit invoice was raised.
  9. H12 — reconfirm. Assert no second deposit invoice and ConfirmationDate unchanged.

M. The API conversion path — API.BookQuote

ID Case Preconditions Steps Expected Pri Source
M1 Missing payload Request with no BookQuote object Call BookQuote Error SYS1010, no conversion P1 API.BookQuote [6-7]
M2 Unknown booking number BookingNumber matching nothing Call Error IN4050 P1 [10-11]
M3 Non-API booking is invisible Booking created in the UI (APIBooking = false) with a valid number Call Error IN4050 — the lookup is constrained to [APIBooking], so UI bookings cannot be converted via the API even with a correct reference P1 [9]
M4 Cancelled booking WindowStatus = CX or CC Call Error IN4080 P1 [14-15]
M5 Dropped booking Source neither Wilderness_window nor Quote_only Call Error IN4090 P1 [17-18]
M6 Already converted Source = Wilderness_window Call Error IN4100 P1 [20-21]
M7 Full happy path Valid API quote at Step 3/4, rooming valid Call ConvertToLiveBookingProceedToScreen3_CreateBLsIVK_ProvisionalAllUpdateReferralAfterCreate all run; booking ends Provisional with booking lines built, in one call P1 [23-26]
M8 ITRVL stamped for every caller Call from a non-iTrvl partner integration Call API_System = ITRVL regardless of actual caller — defect D11. Assert and log P2 [27]
M9 Lock released M7 Call Booking_CurrentlyUsedBy cleared, AgentBlock = false P2 [27]
M10 Conversion failure returns a bare false API quote that passes all five guards but fails CheckRooming Call BookQuoteResponse.Success = false with no error code and no diagnostic, because success is inferred from $Booking/Source. Defect D10 — highest-value API case P1 [28],[32]
M11 Success flag on the happy path M7 Call Success = true; request/response logged via LogXMLRequestResponse P2 [28-30]

N. OnBase notification

Onbase.BookingStatus_Post is invoked fire-and-forget on convert, provisional, confirm and cancel. UseReturnVariable is false everywhere, so no caller detects failure.

ID Case Preconditions Steps Expected Pri Source
N1 Fired on conversion Valid conversion Convert Onbase.BookingStatus_Post invoked asynchronously with the booking as context P2 Booking.ConvertToLiveBooking [14]
N2 Fired on provisional D1 Book provisionally Same call fires P2 Booking.Sub_BookProvisionally_AllInventory [34]
N3 Fired on confirmation H1 Confirm Same call fires P2 ConfirmBooking [24]
N4 Failure is invisible Make Onbase.BookingStatus_Post fail Convert The calling flow completes normally and reports success. Nothing surfaces to the user or the caller. Confirm the business accepts silent loss of document-generation triggers P1 UseReturnVariable: false at all sites
N5 Asynchronous, not inline Any of N1–N3 Trigger Runs in its own transaction after the caller commits; a rollback in the caller does not prevent it P2 CommunityCommons.executeMicroflowInBackground semantics

O. HubSpot on provisional

ID Case Preconditions Steps Expected Pri Source
O1 Standard provisional updates HubSpot Booking provisioned via IVK_ProvisionalAll Book provisionally HubSpot.AddProvisionalExpiryDateToHubSpot and HubSpot_DMC.UpdateDealAfterProv both run P1 Booking.IVK_ProvisionalAll [12-13]
O2 Agent variant does not Same booking via IVK_ProvisionalAll_Agents Book provisionally Neither HubSpot call occurs. The deal is not updated P1 IVK_ProvisionalAll_Agents
O3 The split is per button, not per user An Agent-role user Reach both buttons on BookingWizard_Step_3 IVK_ProvisionalAll allows Agent, so an agent using that button does update HubSpot. Confirm which button each role actually sees P1 role lists on both flows
O4 API path updates HubSpot API.BookQuote Call Reaches IVK_ProvisionalAll, so HubSpot is updated for partner bookings P2 API.BookQuote [25]
O5 HubSpot outage does not block Force HubSpot to fail Book provisionally Both calls are [queued], so the provisional hold still completes P1 Booking.IVK_ProvisionalAll [12-13]
O6 Locked booking blocked in both variants NoChangesAllowed = true Book provisionally Info "Please have your consultant take this booking ex-window as it is locked for further changes." in both P2 [2],[19]
O7 ProvisionalDate stamped once First and second provisional Book provisionally twice Set on first only P2 [16]

L. Environment notes

  • Tourplan and Wish are live integrations. Cases F5, H5, H7, D8 and H19 need either a Tourplan/Wish test instance or a stubbed DataManagement.ServerSettings — see docs/02-architecture.md for the 17 configured server configurations.
  • TourplanInstance matters for F1/F2: the tax-indicator lookup is keyed on (Agency, TourplanInstance), so an agency valid in one instance can be invalid in another.
  • Several checks depend on daysBetween(now, TripStartDate). Prefer fixtures with dates set relative to run time over fixed dates, or the boundary cases (B7, G8) rot.