@ticketboothapp/booking 1.2.135 → 1.2.137

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ticketboothapp/booking",
3
- "version": "1.2.135",
3
+ "version": "1.2.137",
4
4
  "private": false,
5
5
  "sideEffects": [
6
6
  "**/*.css",
@@ -2,7 +2,6 @@ import { useMemo, type ReactNode } from 'react';
2
2
  import type { AdminFeAuthoritativeReceipt } from '../../lib/booking-api';
3
3
  import { roundMoney } from '../../lib/booking/change-flow-pricing';
4
4
  import { mapQuoteLineItemsToPriceSummaryLines } from '../../lib/booking/change-booking-server-preview';
5
- import type { PriceBreakdown as PriceBreakdownData } from '../../lib/booking/pricing';
6
5
  import { formatCurrencyAmount } from '../../lib/currency';
7
6
  import type { Locale } from '../../lib/booking/i18n/config';
8
7
  import type { ChangeBookingFlowProps } from './booking-flow-types';
@@ -41,45 +40,21 @@ function hasTaxLine(lines: PriceSummaryLine[]): boolean {
41
40
  );
42
41
  }
43
42
 
44
- function storedReceiptTicketBreakdown(line: Extract<PriceSummaryLine, { kind: 'ticket' }>): PriceBreakdownData {
45
- const qty = Math.max(1, line.qty);
46
- const unitAmount = roundMoney(line.itemTotal / qty);
47
- return {
48
- lineItems: [
49
- {
50
- type: 'base',
51
- name: 'Stored receipt unit',
52
- amountInDisplayCurrency: unitAmount,
53
- },
54
- ],
55
- subtotalAfterAdjustments: unitAmount,
56
- feeLines: [],
57
- taxRate: 0,
58
- taxAmount: 0,
59
- isTaxIncluded: false,
60
- finalPrice: unitAmount,
61
- };
62
- }
63
-
64
- function withStoredReceiptBreakdowns(lines: PriceSummaryLine[]): PriceSummaryLine[] {
65
- return lines.map((line) => {
66
- if (line.kind !== 'ticket' || line.breakdown) return line;
67
- return {
68
- ...line,
69
- breakdown: storedReceiptTicketBreakdown(line),
70
- };
71
- });
72
- }
73
-
74
43
  function mapOriginalReceiptLines(originalReceipt: OriginalReceipt): PriceSummaryLine[] {
75
44
  if (!originalReceipt.lineItems?.length) return [];
76
- return withStoredReceiptBreakdowns(mapQuoteLineItemsToPriceSummaryLines(originalReceipt.lineItems));
45
+ return mapQuoteLineItemsToPriceSummaryLines(originalReceipt.lineItems);
77
46
  }
78
47
 
79
48
  function lineIdentity(line: PriceSummaryLine): string {
49
+ const normalizedLabel = (value: string) =>
50
+ value
51
+ .replace(/\s*\([^)]*(?:people|person|x\s*\d+)[^)]*\)\s*$/i, '')
52
+ .replace(/\s+/g, ' ')
53
+ .trim()
54
+ .toUpperCase();
80
55
  return line.kind === 'ticket'
81
56
  ? `ticket:${line.category.trim().toUpperCase()}`
82
- : `line:${String(line.type ?? '').trim().toUpperCase()}:${line.label.trim().toUpperCase()}`;
57
+ : `line:${String(line.type ?? '').trim().toUpperCase()}:${normalizedLabel(line.label)}`;
83
58
  }
84
59
 
85
60
  function lineAmount(line: PriceSummaryLine): number {
@@ -157,10 +132,25 @@ export function AdminChangeReceiptComparison({
157
132
  [originalReceipt],
158
133
  );
159
134
  const originalLinesIncludeTaxRow = hasTaxLine(originalReceiptLines);
160
- const changeLines = useMemo(
135
+ const calculatedChangeLines = useMemo(
161
136
  () => amendmentDeltaLines(originalReceiptLines, newPriceSummaryLines),
162
137
  [newPriceSummaryLines, originalReceiptLines],
163
138
  );
139
+ const receiptTaxDelta = roundMoney(newReceipt.tax - originalReceipt.tax);
140
+ const changeLines = useMemo(() => {
141
+ if (hasTaxLine(calculatedChangeLines) || Math.abs(receiptTaxDelta) < 0.005) {
142
+ return calculatedChangeLines;
143
+ }
144
+ return [
145
+ ...calculatedChangeLines,
146
+ {
147
+ kind: 'line' as const,
148
+ label: receiptTaxDelta >= 0 ? 'Additional tax' : 'Tax reduction',
149
+ amount: receiptTaxDelta,
150
+ type: 'TAX',
151
+ },
152
+ ];
153
+ }, [calculatedChangeLines, receiptTaxDelta]);
164
154
  const changeLinesIncludeTaxRow = hasTaxLine(changeLines);
165
155
  const changeTax = roundMoney(
166
156
  changeLines.reduce(
@@ -212,17 +202,24 @@ export function AdminChangeReceiptComparison({
212
202
  {formatCurrencyAmount(amountDue, currency, displayLocale)}
213
203
  </span>
214
204
  </div>
215
- <PriceSummary
216
- lines={changeLines}
217
- total={amountDue}
218
- currency={currency}
219
- locale={displayLocale}
220
- subtotal={changeSubtotal}
221
- taxAmount={!changeLinesIncludeTaxRow ? changeTax : 0}
222
- taxRate={taxRate}
223
- size="sm"
224
- t={t}
225
- />
205
+ {changeLines.length === 0 && Math.abs(amountDue) < 0.005 ? (
206
+ <div className="rounded-md border border-dashed border-stone-300 bg-white/70 px-3 py-4 text-center">
207
+ <p className="text-sm font-medium text-stone-700">No pricing changes</p>
208
+ <p className="mt-1 text-xs text-stone-500">The selected booking details match the existing purchase.</p>
209
+ </div>
210
+ ) : (
211
+ <PriceSummary
212
+ lines={changeLines}
213
+ total={amountDue}
214
+ currency={currency}
215
+ locale={displayLocale}
216
+ subtotal={changeSubtotal}
217
+ taxAmount={!changeLinesIncludeTaxRow ? changeTax : 0}
218
+ taxRate={taxRate}
219
+ size="sm"
220
+ t={t}
221
+ />
222
+ )}
226
223
  </section>
227
224
  </div>
228
225
  <section className="min-w-0 overflow-visible rounded-lg border border-stone-300 bg-white p-3 shadow-sm">
@@ -177,6 +177,7 @@ export interface ChangeBookingFlowProps extends BookingFlowBaseProps {
177
177
  label?: string;
178
178
  amount?: number;
179
179
  quantity?: number;
180
+ priceBasis?: import('../../lib/booking-api').PriceBasisSnapshot | null;
180
181
  }>;
181
182
  } | null;
182
183
  /** Admin/provider dashboard integration hook (used by AdminChangeBookingFlow). */
@@ -6,6 +6,7 @@
6
6
  */
7
7
  import type { ChangeBookingQuoteResponse } from '../booking-api';
8
8
  import type { PricingV2QuoteLineSnapshot } from '../booking-api';
9
+ import type { PriceBasisSnapshot } from '../booking-api';
9
10
  import type { PriceSummaryLine } from '../../components/booking/PriceSummary';
10
11
  import { roundMoney, serverTotalsFromChangeQuoteResponse } from './change-flow-pricing';
11
12
 
@@ -41,6 +42,7 @@ type QuoteSummaryInputLine =
41
42
  amount?: number;
42
43
  type?: string;
43
44
  quantity?: number;
45
+ priceBasis?: PriceBasisSnapshot | null;
44
46
  };
45
47
 
46
48
  function lineItemsForSummary(quote: ChangeBookingQuoteResponse): QuoteSummaryInputLine[] | undefined {
@@ -102,6 +104,34 @@ function amountForDisplay(line: QuoteSummaryInputLine): number {
102
104
  return isReductionLine(line) && amount > 0 ? -amount : amount;
103
105
  }
104
106
 
107
+ function breakdownFromPriceBasis(priceBasis: PriceBasisSnapshot | null | undefined) {
108
+ if (!priceBasis) return undefined;
109
+ return {
110
+ lineItems: [
111
+ {
112
+ type: 'base' as const,
113
+ name: 'Base price',
114
+ amountInDisplayCurrency: priceBasis.baseUnitAmount,
115
+ },
116
+ ...(priceBasis.adjustments ?? []).map((adjustment) => ({
117
+ type: 'adjustment' as const,
118
+ id: adjustment.id,
119
+ name: adjustment.name,
120
+ amountInDisplayCurrency: adjustment.amount,
121
+ adjustmentType: adjustment.adjustmentType,
122
+ adjustmentValue: adjustment.adjustmentValue,
123
+ sourceType: adjustment.type === 'deal' ? ('deal' as const) : ('dynamic' as const),
124
+ })),
125
+ ],
126
+ subtotalAfterAdjustments: priceBasis.finalUnitAmount,
127
+ feeLines: [],
128
+ taxRate: 0,
129
+ taxAmount: 0,
130
+ isTaxIncluded: false,
131
+ finalPrice: priceBasis.finalUnitAmount,
132
+ };
133
+ }
134
+
105
135
  function hasPaymentCreditLine(lines: PriceSummaryLine[]): boolean {
106
136
  return lines.some((line) => {
107
137
  if (line.kind !== 'line') return false;
@@ -163,6 +193,7 @@ export function mapQuoteLineItemsToPriceSummaryLines(
163
193
  category,
164
194
  qty: qty > 0 ? qty : 1,
165
195
  itemTotal: amount,
196
+ breakdown: breakdownFromPriceBasis(item.priceBasis),
166
197
  });
167
198
  continue;
168
199
  }
@@ -182,6 +213,7 @@ export function mapQuoteLineItemsToPriceSummaryLines(
182
213
  category: lettersOnly,
183
214
  qty,
184
215
  itemTotal: amount,
216
+ breakdown: breakdownFromPriceBasis(item.priceBasis),
185
217
  });
186
218
  continue;
187
219
  }
@@ -1972,6 +1972,24 @@ export interface PricingV2QuoteLineSnapshot {
1972
1972
  discountBehavior?: string | null;
1973
1973
  source?: string | null;
1974
1974
  metadata?: Record<string, string> | null;
1975
+ priceBasis?: PriceBasisSnapshot | null;
1976
+ }
1977
+
1978
+ export interface PriceBasisAdjustmentSnapshot {
1979
+ type: string;
1980
+ id: string;
1981
+ name: string;
1982
+ amount: number;
1983
+ adjustmentType: string;
1984
+ adjustmentValue: number;
1985
+ }
1986
+
1987
+ export interface PriceBasisSnapshot {
1988
+ baseUnitAmount: number;
1989
+ finalUnitAmount: number;
1990
+ currency: string;
1991
+ adjustments?: PriceBasisAdjustmentSnapshot[] | null;
1992
+ pricingProfileId?: string | null;
1975
1993
  }
1976
1994
 
1977
1995
  export interface PricingV2QuoteSnapshot {