Skip to content

Add one shared builder for signed paid booking rows - #1829

Merged
stefan-burke merged 3 commits into
mainfrom
split/booking-lines
Jul 15, 2026
Merged

Add one shared builder for signed paid booking rows#1829
stefan-burke merged 3 commits into
mainfrom
split/booking-lines

Conversation

@stefan-burke

@stefan-burke stefan-burke commented Jul 15, 2026

Copy link
Copy Markdown
Member

This pull request is the next staged-checkout foundation step. It creates one pure, canonical representation of the signed paid booking rows that both ordinary payment completion and the later staged runtime can use, and lifts the shared booking date/duration rules into their own helper alongside it — the row builder depends directly on those rules, so splitting them would only add ordering without isolating meaningful risk.

What changes

  • src/shared/booking-date-fields.ts (new): the single definition of how a booking row's date and durationDays are derived from a listing's type and the buyer's chosen span. A customisable_days listing uses the chosen dayCount; a non-customisable daily listing uses its fixed duration_days; a standard listing spans one dateless day. A missing dayCount (a legacy signed session without day_count) still means one day.
  • src/shared/booking-lines.ts (new): orderBookings — an explicitly typed builder for the signed paid listing_attendees rows. It builds one row per signed line carrying its listing, quantity, date, duration, package path, and paid price; expands child lines into one row per (child, parent) when allocations are present; and stamps a folded child with its parent's package only when that parent books through exactly one path.
  • src/features/api/payment-processing/create.ts: the paid webhook's row construction/allocation/package-stamping block is replaced by one orderBookings call. Payment finalization and error behaviour are unchanged. The DB layer still coerces an undefined pricePaid to 0, so removing the old ?? 0 makes no observable difference; it just lets a genuine 0 (a free line that was still signed) stay distinct from an omitted price on the in-memory rows.
  • src/features/api/payment-processing/store-refund.ts: only the bookingDateFields import moves to the shared helper.
  • src/features/public/ticket-payment.ts: uses the shared bookingDateFields for its existing availability and free-booking callers. Current ResponseHandler and modular capacity behaviour are preserved.
  • src/shared/db/attendees/order-parents.ts: expandChildAllocations and expandBooking are made generic over T extends ListingBooking so any required row fields a caller carries on each booking survive the expansion.

Behaviour contract

  • Listing, quantity, date, duration, package path, allocation, input order, and paid price are all preserved.
  • A pricePaid of 0 is kept distinct from an omitted price.
  • Standard non-customisable rows are dateless with duration 1; daily/customisable combinations keep the current date/day rules; a missing legacy day_count means one day.
  • Child allocations preserve total quantity and exact total paid price; the last split row absorbs the rounding residue; every expanded row shares one order token.
  • A folded child is stamped with a parent package only when that parent has one unambiguous package path. Mixed and standalone parent paths do not falsely stamp children.
  • The current main in-memory standalone package contract is kept, including packageGroupId 0 where existing writers expect it.

Tests

  • test/shared/booking-lines.test.ts (new): focused pure tests for standalone and tagged package rows, pricePaid 0 versus omitted, empty and multiple allocations, parentless remainder, exact price conservation, shared order token, sole-parent stamping, and mixed-parent non-stamping.
  • test/shared/booking-date-fields.test.ts (new): the standard/daily/customisable cases moved out of test/features/public/ticket-payment.test.ts, plus a missing-dayCount case and a non-finite-clamp case.

Scope excluded

No checkout_stages, migrations, activation, rollback fences, refund lifecycle, cleanup, expiry, backup split, admin locks, staged callbacks, webhook reconciliation, or wholesale copies of the old reference branch. Newer work on main from PRs #1821#1826 and #1828 is preserved. Build on the current main commit 31417124; #1827 (Stripe webhook setup) was still open at push time, so no main re-merge was needed.

Verification

Biome (lint:ci) and CPD (src + test configs) both run clean. Full typecheck and the test suite are left to CI and the commit hook.

Summary by CodeRabbit

  • New Features
    • Added shared helpers to consistently compute booking dates/durations and to generate signed paid booking rows, including parent/child allocation expansion and package stamping.
  • Bug Fixes
    • Ensures booking date/duration behavior is identical across payment and refund flows.
    • Preserves explicitly provided pricePaid: 0 while omitting pricePaid when not present to allow correct downstream defaults.
  • Tests
    • Added/updated test coverage for booking date/duration logic and signed paid booking row expansion (allocations, price conservation, and package assignment).

Extract bookingDateFields into src/shared/booking-date-fields.ts and route
the paid webhook, store-and-refund, and public/free paths through it.

Add src/shared/booking-lines.ts with orderBookings: one pure builder for
the signed paid listing_attendees rows (date/duration, package path,
allocation expansion, sole-parent package stamping, shared order token).
A genuine pricePaid 0 stays distinct from an omitted price.

Replace create.ts's paid-row construction with orderBookings; payment
finalization and error behaviour are unchanged (the DB layer still coerces
an undefined pricePaid to 0, so no observable difference).

Make expandChildAllocations and expandBooking generic over T extends
ListingBooking so required row fields survive expansion. Update
store-refund.ts's date-helper import only.

Move the bookingDateFields tests to test/shared/booking-date-fields.test.ts
and add the missing-dayCount case; add focused orderBookings tests for
standalone and tagged package rows, pricePaid 0 vs omitted, empty and
multiple allocations, parentless remainder, exact price conservation,
shared order token, and sole-parent vs mixed-parent stamping.

No checkout-stage schema, activation, refund, cleanup, backup, or
admin-lock changes.
@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 7ffbe0f5-c98a-442b-8621-1b0cff4d8af9

📥 Commits

Reviewing files that changed from the base of the PR and between e24b94f and 45cade2.

📒 Files selected for processing (1)
  • test/shared/booking-lines.test.ts

📝 Walkthrough

Walkthrough

Booking date derivation and allocation-aware booking construction are centralized in shared modules. Attendee creation uses the shared builder, while payment and refund flows import the shared date helper. Tests cover date rules, price omission, allocation expansion, price conservation, and package stamping.

Changes

Booking row centralization

Layer / File(s) Summary
Centralize booking date derivation
src/shared/booking-date-fields.ts, src/features/public/ticket-payment.ts, src/features/api/payment-processing/store-refund.ts, test/shared/booking-date-fields.test.ts, test/features/public/ticket-payment.test.ts
Booking date and duration logic moves to a shared helper, with callers and tests updated accordingly.
Build allocation-aware booking rows
src/shared/booking-lines.ts, src/shared/db/attendees/order-parents.ts, test/shared/booking-lines.test.ts
orderBookings creates signed booking rows, preserves omitted versus zero prices, expands allocations, stamps package metadata, and retains extra row fields through generic expansion.
Use canonical rows in attendee creation
src/features/api/payment-processing/create.ts
createAttendeeForSession delegates booking construction to orderBookings before atomic booking creation.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant createAttendeeForSession
  participant orderBookings
  participant expandChildAllocations
  createAttendeeForSession->>orderBookings: signed lines, date, dayCount, allocations
  orderBookings->>expandChildAllocations: allocation-aware booking rows
  expandChildAllocations-->>orderBookings: expanded rows with quantities and prices
  orderBookings-->>createAttendeeForSession: ListingBooking rows
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: introducing a shared builder for signed paid booking rows.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch split/booking-lines

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/features/api/payment-processing/create.ts`:
- Around line 264-274: Update the booking-line mapping inside orderBookings to
conditionally spread pricePaid only when
paidByIntentItem.get(pricingIntent.items[index]!) is defined, preserving 0 as a
valid value and omitting the property otherwise; keep the remaining fields
unchanged.

In `@test/shared/booking-lines.test.ts`:
- Around line 88-89: Replace the compound .every(...).toBe(true) assertions in
the booking-lines test with explicit per-row assertions for orderToken and
parentListingId, including the checks around the additional assertions at lines
108-113. Keep the result length and row-presence validation intact so dropped or
incorrect rows are detected with row-specific failure output.
- Around line 162-170: Remove the unnecessary nullish fallback from the
childPackageFor helper’s find result. Keep the non-null assertion on the
matching listing and return its numeric packageGroupId directly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 771c84d3-f268-47fe-83d7-58e79cc723ac

📥 Commits

Reviewing files that changed from the base of the PR and between 2c5eb0a and 4a41789.

📒 Files selected for processing (9)
  • src/features/api/payment-processing/create.ts
  • src/features/api/payment-processing/store-refund.ts
  • src/features/public/ticket-payment.ts
  • src/shared/booking-date-fields.ts
  • src/shared/booking-lines.ts
  • src/shared/db/attendees/order-parents.ts
  • test/features/public/ticket-payment.test.ts
  • test/shared/booking-date-fields.test.ts
  • test/shared/booking-lines.test.ts
💤 Files with no reviewable changes (1)
  • test/features/public/ticket-payment.test.ts

Comment thread src/features/api/payment-processing/create.ts
Comment thread test/shared/booking-lines.test.ts Outdated
Comment thread test/shared/booking-lines.test.ts Outdated
Address CodeRabbit review feedback on PR #1829:

- create.ts: pricePaid is now conditionally spread (not assigned as
  number|undefined) so a genuine 0 stays while an absent price is omitted,
  satisfying exactOptionalPropertyTypes. OrderBookingsInput.dayCount and
  allocations are typed `| undefined` to match BookingIntent's optionals so
  an intent's values pass through directly (the builder handles undefined
  via bookingDateFields's default and the allocations-length check).
- booking-lines.test.ts: replace compound .every().toBe(true) assertions
  with per-row loops for row-specific failure output and stronger mutation
  resistance. Remove the dead ?? 0 fallback in childPackageFor so an
  unexpected undefined surfaces as a real test failure.

Confirmed via deno check on create.ts (the CI-failing file) and Biome/CPD
on all changed files.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@test/shared/booking-lines.test.ts`:
- Around line 173-181: Update the childPackageFor helper to return number rather
than number | undefined, and assert the guaranteed packageGroupId invariant when
retrieving the listingId 20 result. Keep the existing orderBookings lookup and
return behavior unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: a26de6e7-6261-4421-8518-d147010e8c85

📥 Commits

Reviewing files that changed from the base of the PR and between 4a41789 and e24b94f.

📒 Files selected for processing (3)
  • src/features/api/payment-processing/create.ts
  • src/shared/booking-lines.ts
  • test/shared/booking-lines.test.ts

Comment thread test/shared/booking-lines.test.ts Outdated
Address CodeRabbit follow-up review on PR #1829: childPackageFor now
returns number (not number | undefined) and asserts the invariant with
a non-null assertion, per the repo's Trust application invariants rule.
The builder always sets packageGroupId on every output row (set on the
raw line, preserved by expandChildAllocations' spread, kept or stamped
by stampChildRowPackages), so undefined is a state the application says
is impossible — model it with ! rather than propagating the optionality.
@stefan-burke
stefan-burke added this pull request to the merge queue Jul 15, 2026
Merged via the queue into main with commit cb29db7 Jul 15, 2026
3 checks passed
@stefan-burke
stefan-burke deleted the split/booking-lines branch July 15, 2026 09:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant