@ticketboothapp/booking 1.2.134 → 1.2.135

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.134",
3
+ "version": "1.2.135",
4
4
  "private": false,
5
5
  "sideEffects": [
6
6
  "**/*.css",
@@ -76,6 +76,68 @@ function mapOriginalReceiptLines(originalReceipt: OriginalReceipt): PriceSummary
76
76
  return withStoredReceiptBreakdowns(mapQuoteLineItemsToPriceSummaryLines(originalReceipt.lineItems));
77
77
  }
78
78
 
79
+ function lineIdentity(line: PriceSummaryLine): string {
80
+ return line.kind === 'ticket'
81
+ ? `ticket:${line.category.trim().toUpperCase()}`
82
+ : `line:${String(line.type ?? '').trim().toUpperCase()}:${line.label.trim().toUpperCase()}`;
83
+ }
84
+
85
+ function lineAmount(line: PriceSummaryLine): number {
86
+ return line.kind === 'ticket' ? line.itemTotal : line.amount;
87
+ }
88
+
89
+ function lineQuantity(line: PriceSummaryLine): number | null {
90
+ return line.kind === 'ticket' ? line.qty : line.quantity ?? null;
91
+ }
92
+
93
+ function amendmentDeltaLines(
94
+ originalLines: PriceSummaryLine[],
95
+ proposedLines: PriceSummaryLine[],
96
+ ): PriceSummaryLine[] {
97
+ const remainingOriginal = new Map<string, { amount: number; quantity: number | null }>();
98
+ for (const line of originalLines) {
99
+ const key = lineIdentity(line);
100
+ const previous = remainingOriginal.get(key);
101
+ remainingOriginal.set(key, {
102
+ amount: roundMoney((previous?.amount ?? 0) + lineAmount(line)),
103
+ quantity:
104
+ previous?.quantity == null && lineQuantity(line) == null
105
+ ? null
106
+ : (previous?.quantity ?? 0) + (lineQuantity(line) ?? 0),
107
+ });
108
+ }
109
+
110
+ return proposedLines.flatMap((line): PriceSummaryLine[] => {
111
+ const original = remainingOriginal.get(lineIdentity(line));
112
+ const amount = roundMoney(lineAmount(line) - (original?.amount ?? 0));
113
+ const proposedQuantity = lineQuantity(line);
114
+ const quantity =
115
+ proposedQuantity == null && original?.quantity == null
116
+ ? null
117
+ : (proposedQuantity ?? 0) - (original?.quantity ?? 0);
118
+ if (Math.abs(amount) < 0.005 && (quantity == null || quantity === 0)) return [];
119
+
120
+ if (line.kind === 'ticket') {
121
+ return [{
122
+ ...line,
123
+ qty: Math.abs(quantity ?? 0) || line.qty,
124
+ itemTotal: amount,
125
+ editable: false,
126
+ lineKey: undefined,
127
+ }];
128
+ }
129
+ const isTax = String(line.type ?? '').toUpperCase() === 'TAX';
130
+ return [{
131
+ ...line,
132
+ label: isTax ? (amount >= 0 ? 'Additional tax' : 'Tax reduction') : line.label,
133
+ amount,
134
+ quantity: quantity == null ? null : Math.abs(quantity),
135
+ editable: false,
136
+ lineKey: undefined,
137
+ }];
138
+ });
139
+ }
140
+
79
141
  export function AdminChangeReceiptComparison({
80
142
  originalReceipt,
81
143
  newReceipt,
@@ -88,11 +150,6 @@ export function AdminChangeReceiptComparison({
88
150
  t,
89
151
  taxRate,
90
152
  newReceiptAdjustments,
91
- lineAmountInputs,
92
- onLineAmountInputChange,
93
- onLineAmountInputBlur,
94
- onLineAmountReset,
95
- lineLabelInputs,
96
153
  }: AdminChangeReceiptComparisonProps) {
97
154
  const displayLocale = normalizeLocale(locale);
98
155
  const originalReceiptLines = useMemo(
@@ -100,6 +157,21 @@ export function AdminChangeReceiptComparison({
100
157
  [originalReceipt],
101
158
  );
102
159
  const originalLinesIncludeTaxRow = hasTaxLine(originalReceiptLines);
160
+ const changeLines = useMemo(
161
+ () => amendmentDeltaLines(originalReceiptLines, newPriceSummaryLines),
162
+ [newPriceSummaryLines, originalReceiptLines],
163
+ );
164
+ const changeLinesIncludeTaxRow = hasTaxLine(changeLines);
165
+ const changeTax = roundMoney(
166
+ changeLines.reduce(
167
+ (sum, line) =>
168
+ line.kind === 'line' && String(line.type ?? '').toUpperCase() === 'TAX'
169
+ ? sum + line.amount
170
+ : sum,
171
+ 0,
172
+ ),
173
+ );
174
+ const changeSubtotal = roundMoney(amountDue - changeTax);
103
175
  const originalCurrency = originalReceipt.currency ?? currency;
104
176
  const amountDueClass =
105
177
  amountDue < -0.005
@@ -113,7 +185,7 @@ export function AdminChangeReceiptComparison({
113
185
  <div className="grid gap-4 md:grid-cols-2">
114
186
  <section className="min-w-0 overflow-visible rounded-lg border border-stone-200 bg-stone-50/70 p-3">
115
187
  <div className="mb-3 flex items-center justify-between gap-3">
116
- <h3 className="text-sm font-semibold text-stone-900">Current receipt</h3>
188
+ <h3 className="text-sm font-semibold text-stone-900">Existing purchase</h3>
117
189
  <span className="shrink-0 text-xs font-medium text-stone-500">
118
190
  {formatCurrencyAmount(originalReceipt.total, originalCurrency, displayLocale)}
119
191
  </span>
@@ -130,32 +202,60 @@ export function AdminChangeReceiptComparison({
130
202
  t={t}
131
203
  />
132
204
  </section>
133
- <section className="min-w-0 overflow-visible rounded-lg border border-stone-300 bg-white p-3 shadow-sm">
205
+ <section className="min-w-0 overflow-visible rounded-lg border border-sky-200 bg-sky-50/50 p-3">
134
206
  <div className="mb-3 flex items-center justify-between gap-3">
135
- <h3 className="text-sm font-semibold text-stone-900">New receipt</h3>
207
+ <div>
208
+ <h3 className="text-sm font-semibold text-stone-900">This change</h3>
209
+ <p className="mt-0.5 text-xs text-stone-500">Only newly charged, credited, or repriced value</p>
210
+ </div>
136
211
  <span className="shrink-0 text-xs font-medium text-stone-500">
137
- {formatCurrencyAmount(newReceipt.total, currency, displayLocale)}
212
+ {formatCurrencyAmount(amountDue, currency, displayLocale)}
138
213
  </span>
139
214
  </div>
140
215
  <PriceSummary
141
- lines={newPriceSummaryLines}
142
- total={newReceipt.total}
216
+ lines={changeLines}
217
+ total={amountDue}
143
218
  currency={currency}
144
219
  locale={displayLocale}
145
- subtotal={newReceipt.subtotal}
146
- taxAmount={!newPriceSummaryLinesIncludeTaxRow ? newReceipt.tax : 0}
220
+ subtotal={changeSubtotal}
221
+ taxAmount={!changeLinesIncludeTaxRow ? changeTax : 0}
147
222
  taxRate={taxRate}
148
223
  size="sm"
149
224
  t={t}
150
- extraBeforeSubtotal={newReceiptAdjustments}
151
- lineAmountInputs={lineAmountInputs}
152
- onLineAmountInputChange={onLineAmountInputChange}
153
- onLineAmountInputBlur={onLineAmountInputBlur}
154
- onLineAmountReset={onLineAmountReset}
155
- lineLabelInputs={lineLabelInputs}
156
225
  />
157
226
  </section>
158
227
  </div>
228
+ <section className="min-w-0 overflow-visible rounded-lg border border-stone-300 bg-white p-3 shadow-sm">
229
+ <div className="mb-3 flex items-center justify-between gap-3">
230
+ <div>
231
+ <h3 className="text-sm font-semibold text-stone-900">Updated booking receipt</h3>
232
+ <p className="mt-0.5 text-xs text-stone-500">Read-only pricing result; historical and added cohorts remain separate when supplied by the server</p>
233
+ </div>
234
+ <span className="shrink-0 text-xs font-medium text-stone-500">
235
+ {formatCurrencyAmount(newReceipt.total, currency, displayLocale)}
236
+ </span>
237
+ </div>
238
+ <PriceSummary
239
+ lines={newPriceSummaryLines.map((line) => ({ ...line, editable: false, lineKey: undefined }))}
240
+ total={newReceipt.total}
241
+ currency={currency}
242
+ locale={displayLocale}
243
+ subtotal={newReceipt.subtotal}
244
+ taxAmount={!newPriceSummaryLinesIncludeTaxRow ? newReceipt.tax : 0}
245
+ taxRate={taxRate}
246
+ size="sm"
247
+ t={t}
248
+ />
249
+ </section>
250
+ {newReceiptAdjustments ? (
251
+ <section className="rounded-lg border border-amber-200 bg-amber-50/40 p-3">
252
+ <div className="mb-2">
253
+ <h3 className="text-sm font-semibold text-stone-900">Admin adjustments</h3>
254
+ <p className="mt-0.5 text-xs text-stone-500">Optional structured charges or credits applied to this amendment</p>
255
+ </div>
256
+ {newReceiptAdjustments}
257
+ </section>
258
+ ) : null}
159
259
  <div className="flex flex-wrap items-center justify-between gap-2 rounded-lg border border-stone-200 bg-stone-50 px-3 py-2">
160
260
  <span className="text-sm font-semibold text-stone-900">{amountDueLabel}</span>
161
261
  <span className={`text-lg font-semibold tabular-nums ${amountDueClass}`}>