@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
|
@@ -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">
|
|
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-
|
|
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
|
-
<
|
|
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(
|
|
212
|
+
{formatCurrencyAmount(amountDue, currency, displayLocale)}
|
|
138
213
|
</span>
|
|
139
214
|
</div>
|
|
140
215
|
<PriceSummary
|
|
141
|
-
lines={
|
|
142
|
-
total={
|
|
216
|
+
lines={changeLines}
|
|
217
|
+
total={amountDue}
|
|
143
218
|
currency={currency}
|
|
144
219
|
locale={displayLocale}
|
|
145
|
-
subtotal={
|
|
146
|
-
taxAmount={!
|
|
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}`}>
|