@ticketboothapp/booking 1.2.133 → 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 +1 -1
- package/src/components/booking/AdminChangeBookingFlow.tsx +2 -0
- package/src/components/booking/AdminChangePricingPanel.tsx +54 -2
- package/src/components/booking/AdminChangeReceiptComparison.tsx +119 -19
- package/src/components/booking/admin-change-provider-payload.ts +3 -0
- package/src/components/booking/provider-dashboard-change-booking.ts +39 -0
- package/src/components/booking/useAdminChangeCheckoutController.ts +3 -0
- package/src/components/booking/useAdminChangePriceSummary.ts +6 -1
- package/src/components/booking/useAdminChangeProtectedPricing.ts +7 -1
- package/src/components/booking/useAdminChangeQuoteDisplayState.tsx +2 -0
- package/src/components/booking/useAdminCustomReceiptLines.ts +12 -1
- package/src/components/booking/useAdminProviderPricingAdjustments.ts +40 -3
- package/src/index.ts +4 -0
package/package.json
CHANGED
|
@@ -554,6 +554,7 @@ export function AdminChangeBookingFlow({
|
|
|
554
554
|
providerPricingOverrides,
|
|
555
555
|
adminCustomLinesAsAdditionalAdjustments,
|
|
556
556
|
mergedProviderAdditionalAdjustments,
|
|
557
|
+
adminStructuredAdjustments,
|
|
557
558
|
unchangedReceiptAnchor,
|
|
558
559
|
showChangeFlowManualPriceLines,
|
|
559
560
|
showAdminCustomLineEditor,
|
|
@@ -870,6 +871,7 @@ export function AdminChangeBookingFlow({
|
|
|
870
871
|
displayChangeFlowProposedTotalWithEditableLines,
|
|
871
872
|
providerPricingOverrides,
|
|
872
873
|
mergedProviderAdditionalAdjustments,
|
|
874
|
+
adminStructuredAdjustments,
|
|
873
875
|
providerApplyAuthoritativeReceipt,
|
|
874
876
|
onSuccess,
|
|
875
877
|
totalPrice,
|
|
@@ -9,10 +9,17 @@ export type AdminCustomReceiptLine = {
|
|
|
9
9
|
label: string;
|
|
10
10
|
amountInput: string;
|
|
11
11
|
amountSign: 1 | -1;
|
|
12
|
+
calculation: 'FIXED_AMOUNT' | 'PERCENTAGE';
|
|
13
|
+
scope: 'POSITIVE_AMENDMENT_VALUE' | 'CURRENT_ACTIVE_TICKETS' | 'CURRENT_ACTIVE_BOOKING';
|
|
14
|
+
taxBehavior: 'NON_TAXABLE' | 'TAXABLE' | 'PRE_TAX_DISCOUNT' | 'POST_TAX_DISCOUNT';
|
|
15
|
+
reasonCode: string;
|
|
12
16
|
};
|
|
13
17
|
|
|
14
18
|
type AdminCustomReceiptLinePatch = Partial<
|
|
15
|
-
Pick<
|
|
19
|
+
Pick<
|
|
20
|
+
AdminCustomReceiptLine,
|
|
21
|
+
'label' | 'amountInput' | 'amountSign' | 'calculation' | 'scope' | 'taxBehavior' | 'reasonCode'
|
|
22
|
+
>
|
|
16
23
|
>;
|
|
17
24
|
|
|
18
25
|
export interface AdminChangePricingMessagesProps {
|
|
@@ -181,6 +188,19 @@ export function AdminChangePricingAdjustments({
|
|
|
181
188
|
onUpdateAdminCustomReceiptLine(adj.id, { label: e.target.value })
|
|
182
189
|
}
|
|
183
190
|
/>
|
|
191
|
+
<select
|
|
192
|
+
className="rounded-md border border-stone-300 bg-white px-2 py-1 text-xs text-stone-700"
|
|
193
|
+
aria-label="Adjustment calculation"
|
|
194
|
+
value={adj.calculation}
|
|
195
|
+
onChange={(e) =>
|
|
196
|
+
onUpdateAdminCustomReceiptLine(adj.id, {
|
|
197
|
+
calculation: e.target.value as AdminCustomReceiptLine['calculation'],
|
|
198
|
+
})
|
|
199
|
+
}
|
|
200
|
+
>
|
|
201
|
+
<option value="FIXED_AMOUNT">Fixed amount</option>
|
|
202
|
+
<option value="PERCENTAGE">Percentage</option>
|
|
203
|
+
</select>
|
|
184
204
|
<div
|
|
185
205
|
className="flex shrink-0 rounded-lg border border-stone-300 bg-stone-100/90 p-0.5 shadow-sm"
|
|
186
206
|
role="group"
|
|
@@ -215,7 +235,7 @@ export function AdminChangePricingAdjustments({
|
|
|
215
235
|
type="text"
|
|
216
236
|
inputMode="decimal"
|
|
217
237
|
className="admin-custom-receipt-input-amount w-[7rem] shrink-0 rounded-md border border-stone-300 bg-white text-right text-sm font-medium tabular-nums text-stone-800 placeholder:text-stone-400 focus:outline-none focus:ring-2 focus:ring-stone-400/80"
|
|
218
|
-
placeholder=
|
|
238
|
+
placeholder={adj.calculation === 'PERCENTAGE' ? '10' : '0.00'}
|
|
219
239
|
aria-label="Amount"
|
|
220
240
|
value={adj.amountInput}
|
|
221
241
|
onChange={(e) =>
|
|
@@ -224,6 +244,38 @@ export function AdminChangePricingAdjustments({
|
|
|
224
244
|
})
|
|
225
245
|
}
|
|
226
246
|
/>
|
|
247
|
+
<span className="text-xs text-stone-500" aria-hidden>
|
|
248
|
+
{adj.calculation === 'PERCENTAGE' ? '%' : ''}
|
|
249
|
+
</span>
|
|
250
|
+
<select
|
|
251
|
+
className="rounded-md border border-stone-300 bg-white px-2 py-1 text-xs text-stone-700"
|
|
252
|
+
aria-label="Adjustment scope"
|
|
253
|
+
value={adj.scope}
|
|
254
|
+
onChange={(e) =>
|
|
255
|
+
onUpdateAdminCustomReceiptLine(adj.id, {
|
|
256
|
+
scope: e.target.value as AdminCustomReceiptLine['scope'],
|
|
257
|
+
})
|
|
258
|
+
}
|
|
259
|
+
>
|
|
260
|
+
<option value="POSITIVE_AMENDMENT_VALUE">Added/change value</option>
|
|
261
|
+
<option value="CURRENT_ACTIVE_TICKETS">All active tickets</option>
|
|
262
|
+
<option value="CURRENT_ACTIVE_BOOKING">Entire active booking</option>
|
|
263
|
+
</select>
|
|
264
|
+
<select
|
|
265
|
+
className="rounded-md border border-stone-300 bg-white px-2 py-1 text-xs text-stone-700"
|
|
266
|
+
aria-label="Tax treatment"
|
|
267
|
+
value={adj.taxBehavior}
|
|
268
|
+
onChange={(e) =>
|
|
269
|
+
onUpdateAdminCustomReceiptLine(adj.id, {
|
|
270
|
+
taxBehavior: e.target.value as AdminCustomReceiptLine['taxBehavior'],
|
|
271
|
+
})
|
|
272
|
+
}
|
|
273
|
+
>
|
|
274
|
+
<option value="NON_TAXABLE">Non-taxable</option>
|
|
275
|
+
<option value="TAXABLE">Taxable</option>
|
|
276
|
+
<option value="PRE_TAX_DISCOUNT">Pre-tax discount</option>
|
|
277
|
+
<option value="POST_TAX_DISCOUNT">Post-tax discount</option>
|
|
278
|
+
</select>
|
|
227
279
|
<button
|
|
228
280
|
type="button"
|
|
229
281
|
className="admin-custom-receipt-remove ml-auto shrink-0 rounded-md text-stone-400 hover:bg-stone-100 hover:text-stone-700 sm:ml-0"
|
|
@@ -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}`}>
|
|
@@ -32,6 +32,7 @@ export interface BuildAdminChangeProviderPayloadParams {
|
|
|
32
32
|
newTotalAmount: number;
|
|
33
33
|
providerPricingOverrides: AdminChangeProviderLineOverride[];
|
|
34
34
|
mergedProviderAdditionalAdjustments: AdminChangeProviderAdditionalAdjustment[];
|
|
35
|
+
adminStructuredAdjustments: NonNullable<ProviderDashboardChangeBookingPayload['structuredAdjustments']>;
|
|
35
36
|
providerApplyAuthoritativeReceipt?: ProviderDashboardChangeBookingPayload['authoritativeReceipt'];
|
|
36
37
|
previousPassengerCount: number;
|
|
37
38
|
previousAvailabilityId?: string | null;
|
|
@@ -52,6 +53,7 @@ export function buildAdminChangeProviderPayload({
|
|
|
52
53
|
newTotalAmount,
|
|
53
54
|
providerPricingOverrides,
|
|
54
55
|
mergedProviderAdditionalAdjustments,
|
|
56
|
+
adminStructuredAdjustments,
|
|
55
57
|
providerApplyAuthoritativeReceipt,
|
|
56
58
|
previousPassengerCount,
|
|
57
59
|
previousAvailabilityId,
|
|
@@ -90,6 +92,7 @@ export function buildAdminChangeProviderPayload({
|
|
|
90
92
|
}
|
|
91
93
|
: undefined,
|
|
92
94
|
authoritativeReceipt: providerApplyAuthoritativeReceipt,
|
|
95
|
+
structuredAdjustments: adminStructuredAdjustments,
|
|
93
96
|
capacitySeatCredit: {
|
|
94
97
|
enabled: true,
|
|
95
98
|
previousPassengerCount,
|
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
import type { Currency } from './CurrencySwitcher';
|
|
2
2
|
|
|
3
|
+
export type AdminAmendmentAdjustmentMode =
|
|
4
|
+
| 'FIXED_AMOUNT'
|
|
5
|
+
| 'PERCENTAGE_DISCOUNT'
|
|
6
|
+
| 'PERCENTAGE_SURCHARGE';
|
|
7
|
+
|
|
8
|
+
export type AdminAmendmentAdjustmentScope =
|
|
9
|
+
| 'POSITIVE_AMENDMENT_VALUE'
|
|
10
|
+
| 'CURRENT_ACTIVE_TICKETS'
|
|
11
|
+
| 'CURRENT_ACTIVE_BOOKING'
|
|
12
|
+
| 'SELECTED_COMPONENTS';
|
|
13
|
+
|
|
14
|
+
export type AdminAmendmentTaxBehavior =
|
|
15
|
+
| 'NON_TAXABLE'
|
|
16
|
+
| 'TAXABLE'
|
|
17
|
+
| 'PRE_TAX_DISCOUNT'
|
|
18
|
+
| 'POST_TAX_DISCOUNT';
|
|
19
|
+
|
|
20
|
+
export type AdminAmendmentStructuredAdjustment = {
|
|
21
|
+
adjustmentId: string;
|
|
22
|
+
mode: AdminAmendmentAdjustmentMode;
|
|
23
|
+
fixedAmount?: number | null;
|
|
24
|
+
percentageBasisPoints?: number | null;
|
|
25
|
+
scope: AdminAmendmentAdjustmentScope;
|
|
26
|
+
selectedComponentIds?: string[];
|
|
27
|
+
taxBehavior: AdminAmendmentTaxBehavior;
|
|
28
|
+
reasonCode: string;
|
|
29
|
+
referenceCode?: string | null;
|
|
30
|
+
reason?: string | null;
|
|
31
|
+
};
|
|
32
|
+
|
|
3
33
|
/** Payload passed to `onChangeBooking` when applying a dashboard-managed booking change. */
|
|
4
34
|
export type ProviderDashboardChangeBookingPayload = {
|
|
5
35
|
/** Parent catalog product id. Present when admin changes the booking to a different product. */
|
|
@@ -26,6 +56,15 @@ export type ProviderDashboardChangeBookingPayload = {
|
|
|
26
56
|
/** Signed major-unit rows; may include admin custom receipt lines merged with provider inline adjustments. */
|
|
27
57
|
additionalAdjustments?: Array<{ label: string; amount: number }>;
|
|
28
58
|
} | null;
|
|
59
|
+
/** Server-resolved amendment adjustments. Percentage amounts are never calculated by the browser. */
|
|
60
|
+
structuredAdjustments?: AdminAmendmentStructuredAdjustment[];
|
|
61
|
+
/** Immutable adjustment ids selected for reversal. */
|
|
62
|
+
reverseAdjustmentIds?: string[];
|
|
63
|
+
/** Admin refund decision for removal operations returned by the quote preview. */
|
|
64
|
+
refundDispositionsByOperationId?: Record<
|
|
65
|
+
string,
|
|
66
|
+
'REFUND_TO_ORIGINAL_PAYMENT' | 'PENDING_REFUND' | 'NO_REFUND'
|
|
67
|
+
>;
|
|
29
68
|
/**
|
|
30
69
|
* Admin/provider change flow: when FE-authoritative quoting is used, apply must persist
|
|
31
70
|
* the exact receipt that was quoted instead of rebuilding from catalog pricing.
|
|
@@ -126,6 +126,7 @@ export interface UseAdminChangeCheckoutControllerParams {
|
|
|
126
126
|
displayChangeFlowProposedTotalWithEditableLines: number;
|
|
127
127
|
providerPricingOverrides: AdminChangeProviderLineOverride[];
|
|
128
128
|
mergedProviderAdditionalAdjustments: AdminChangeProviderAdditionalAdjustment[];
|
|
129
|
+
adminStructuredAdjustments: NonNullable<ProviderDashboardChangeBookingPayload['structuredAdjustments']>;
|
|
129
130
|
providerApplyAuthoritativeReceipt?: ProviderDashboardChangeBookingPayload['authoritativeReceipt'];
|
|
130
131
|
onSuccess?: ChangeBookingFlowProps['onSuccess'];
|
|
131
132
|
totalPrice: number;
|
|
@@ -223,6 +224,7 @@ export function useAdminChangeCheckoutController({
|
|
|
223
224
|
displayChangeFlowProposedTotalWithEditableLines,
|
|
224
225
|
providerPricingOverrides,
|
|
225
226
|
mergedProviderAdditionalAdjustments,
|
|
227
|
+
adminStructuredAdjustments,
|
|
226
228
|
providerApplyAuthoritativeReceipt,
|
|
227
229
|
onSuccess,
|
|
228
230
|
totalPrice,
|
|
@@ -320,6 +322,7 @@ export function useAdminChangeCheckoutController({
|
|
|
320
322
|
newTotalAmount: displayChangeFlowProposedTotalWithEditableLines,
|
|
321
323
|
providerPricingOverrides,
|
|
322
324
|
mergedProviderAdditionalAdjustments,
|
|
325
|
+
adminStructuredAdjustments,
|
|
323
326
|
providerApplyAuthoritativeReceipt,
|
|
324
327
|
previousPassengerCount: changeFlowInitialTicketCount,
|
|
325
328
|
previousAvailabilityId: initialValues?.availabilityId ?? null,
|
|
@@ -27,7 +27,12 @@ import { useAdminChangeProtectedPricing } from './useAdminChangeProtectedPricing
|
|
|
27
27
|
import { useAdminChangePromoDiscount } from './useAdminChangePromoDiscount';
|
|
28
28
|
|
|
29
29
|
type AddOnSelection = { addOnId: string; variantId?: string; quantity?: number };
|
|
30
|
-
type AdminCustomReceiptLine = {
|
|
30
|
+
type AdminCustomReceiptLine = {
|
|
31
|
+
label: string;
|
|
32
|
+
amountInput: string;
|
|
33
|
+
amountSign?: number;
|
|
34
|
+
calculation: 'FIXED_AMOUNT' | 'PERCENTAGE';
|
|
35
|
+
};
|
|
31
36
|
type ProviderPricingUi = NonNullable<
|
|
32
37
|
NonNullable<ChangeBookingFlowProps['flowUi']>['providerDashboardChangePricingUi']
|
|
33
38
|
>;
|
|
@@ -15,7 +15,12 @@ import type { Currency } from './CurrencySwitcher';
|
|
|
15
15
|
import { normalizeLineLabelForCompare } from './change-booking-flow-helpers';
|
|
16
16
|
|
|
17
17
|
type AddOnSelection = { addOnId: string; variantId?: string; quantity?: number };
|
|
18
|
-
type AdminCustomReceiptLine = {
|
|
18
|
+
type AdminCustomReceiptLine = {
|
|
19
|
+
label: string;
|
|
20
|
+
amountInput: string;
|
|
21
|
+
amountSign?: number;
|
|
22
|
+
calculation: 'FIXED_AMOUNT' | 'PERCENTAGE';
|
|
23
|
+
};
|
|
19
24
|
|
|
20
25
|
function addOnSelectionTotal(selections: AddOnSelection[] | null | undefined, addOns: AddOn[]): number {
|
|
21
26
|
let sum = 0;
|
|
@@ -215,6 +220,7 @@ export function useAdminChangeProtectedPricing({
|
|
|
215
220
|
() =>
|
|
216
221
|
roundMoney(
|
|
217
222
|
adminCustomReceiptLines.reduce((sum, line) => {
|
|
223
|
+
if (line.calculation !== 'FIXED_AMOUNT') return sum;
|
|
218
224
|
const raw = line.amountInput.trim();
|
|
219
225
|
if (raw === '' || raw === '+' || raw === '-') return sum;
|
|
220
226
|
const n = Number(raw);
|
|
@@ -336,6 +336,7 @@ export function useAdminChangeQuoteDisplayState({
|
|
|
336
336
|
providerPricingOverrides,
|
|
337
337
|
adminCustomLinesAsAdditionalAdjustments,
|
|
338
338
|
mergedProviderAdditionalAdjustments,
|
|
339
|
+
adminStructuredAdjustments,
|
|
339
340
|
} = useAdminProviderPricingAdjustments({
|
|
340
341
|
isProviderDashboardChange,
|
|
341
342
|
showProviderPricingInlineEditor,
|
|
@@ -425,6 +426,7 @@ export function useAdminChangeQuoteDisplayState({
|
|
|
425
426
|
providerPricingOverrides,
|
|
426
427
|
adminCustomLinesAsAdditionalAdjustments,
|
|
427
428
|
mergedProviderAdditionalAdjustments,
|
|
429
|
+
adminStructuredAdjustments,
|
|
428
430
|
unchangedReceiptAnchor,
|
|
429
431
|
showChangeFlowManualPriceLines,
|
|
430
432
|
showAdminCustomLineEditor,
|
|
@@ -5,10 +5,17 @@ export type AdminCustomReceiptLine = {
|
|
|
5
5
|
label: string;
|
|
6
6
|
amountInput: string;
|
|
7
7
|
amountSign: 1 | -1;
|
|
8
|
+
calculation: 'FIXED_AMOUNT' | 'PERCENTAGE';
|
|
9
|
+
scope: 'POSITIVE_AMENDMENT_VALUE' | 'CURRENT_ACTIVE_TICKETS' | 'CURRENT_ACTIVE_BOOKING';
|
|
10
|
+
taxBehavior: 'NON_TAXABLE' | 'TAXABLE' | 'PRE_TAX_DISCOUNT' | 'POST_TAX_DISCOUNT';
|
|
11
|
+
reasonCode: string;
|
|
8
12
|
};
|
|
9
13
|
|
|
10
14
|
type AdminCustomReceiptLinePatch = Partial<
|
|
11
|
-
Pick<
|
|
15
|
+
Pick<
|
|
16
|
+
AdminCustomReceiptLine,
|
|
17
|
+
'label' | 'amountInput' | 'amountSign' | 'calculation' | 'scope' | 'taxBehavior' | 'reasonCode'
|
|
18
|
+
>
|
|
12
19
|
>;
|
|
13
20
|
|
|
14
21
|
export function useAdminCustomReceiptLines(resetKey: string | null | undefined) {
|
|
@@ -25,6 +32,10 @@ export function useAdminCustomReceiptLines(resetKey: string | null | undefined)
|
|
|
25
32
|
label: '',
|
|
26
33
|
amountInput: '',
|
|
27
34
|
amountSign: 1,
|
|
35
|
+
calculation: 'FIXED_AMOUNT',
|
|
36
|
+
scope: 'POSITIVE_AMENDMENT_VALUE',
|
|
37
|
+
taxBehavior: 'NON_TAXABLE',
|
|
38
|
+
reasonCode: 'STAFF_ADJUSTMENT',
|
|
28
39
|
},
|
|
29
40
|
]);
|
|
30
41
|
}, []);
|
|
@@ -6,7 +6,19 @@ import type {
|
|
|
6
6
|
} from './booking-flow-ui';
|
|
7
7
|
import type { ProviderDashboardChangeBookingPayload } from './provider-dashboard-change-booking';
|
|
8
8
|
|
|
9
|
-
type AdminCustomReceiptLine = {
|
|
9
|
+
type AdminCustomReceiptLine = {
|
|
10
|
+
id: string;
|
|
11
|
+
label: string;
|
|
12
|
+
amountInput: string;
|
|
13
|
+
amountSign?: number;
|
|
14
|
+
calculation: 'FIXED_AMOUNT' | 'PERCENTAGE';
|
|
15
|
+
scope: 'POSITIVE_AMENDMENT_VALUE' | 'CURRENT_ACTIVE_TICKETS' | 'CURRENT_ACTIVE_BOOKING';
|
|
16
|
+
taxBehavior: 'NON_TAXABLE' | 'TAXABLE' | 'PRE_TAX_DISCOUNT' | 'POST_TAX_DISCOUNT';
|
|
17
|
+
reasonCode: string;
|
|
18
|
+
};
|
|
19
|
+
type AdminStructuredAdjustment = NonNullable<
|
|
20
|
+
ProviderDashboardChangeBookingPayload['structuredAdjustments']
|
|
21
|
+
>[number];
|
|
10
22
|
type ProviderLineOverride = NonNullable<
|
|
11
23
|
NonNullable<ProviderDashboardChangeBookingPayload['pricingAdjustment']>['lineOverrides']
|
|
12
24
|
>[number];
|
|
@@ -124,9 +136,33 @@ export function useAdminProviderPricingAdjustments({
|
|
|
124
136
|
[adminCustomReceiptLines],
|
|
125
137
|
);
|
|
126
138
|
|
|
139
|
+
const adminStructuredAdjustments = useMemo(
|
|
140
|
+
(): AdminStructuredAdjustment[] =>
|
|
141
|
+
adminCustomReceiptLines.flatMap((line) => {
|
|
142
|
+
const value = Number(line.amountInput.trim());
|
|
143
|
+
if (!Number.isFinite(value) || value <= 0) return [];
|
|
144
|
+
const discount = (line.amountSign ?? 1) < 0;
|
|
145
|
+
const percentage = line.calculation === 'PERCENTAGE';
|
|
146
|
+
return [{
|
|
147
|
+
adjustmentId: line.id,
|
|
148
|
+
mode: percentage
|
|
149
|
+
? discount ? 'PERCENTAGE_DISCOUNT' : 'PERCENTAGE_SURCHARGE'
|
|
150
|
+
: 'FIXED_AMOUNT',
|
|
151
|
+
...(percentage
|
|
152
|
+
? { percentageBasisPoints: Math.round(value * 100) }
|
|
153
|
+
: { fixedAmount: roundMoney((discount ? -1 : 1) * value) }),
|
|
154
|
+
scope: line.scope,
|
|
155
|
+
taxBehavior: line.taxBehavior,
|
|
156
|
+
reasonCode: line.reasonCode.trim() || 'STAFF_ADJUSTMENT',
|
|
157
|
+
reason: line.label.trim() || null,
|
|
158
|
+
}];
|
|
159
|
+
}),
|
|
160
|
+
[adminCustomReceiptLines],
|
|
161
|
+
);
|
|
162
|
+
|
|
127
163
|
const mergedProviderAdditionalAdjustments = useMemo(
|
|
128
|
-
() =>
|
|
129
|
-
[providerAdditionalAdjustments
|
|
164
|
+
() => providerAdditionalAdjustments,
|
|
165
|
+
[providerAdditionalAdjustments],
|
|
130
166
|
);
|
|
131
167
|
|
|
132
168
|
const hasEffectiveChangeSelection =
|
|
@@ -142,5 +178,6 @@ export function useAdminProviderPricingAdjustments({
|
|
|
142
178
|
providerPricingOverrides,
|
|
143
179
|
adminCustomLinesAsAdditionalAdjustments,
|
|
144
180
|
mergedProviderAdditionalAdjustments,
|
|
181
|
+
adminStructuredAdjustments,
|
|
145
182
|
};
|
|
146
183
|
}
|
package/src/index.ts
CHANGED
|
@@ -26,6 +26,10 @@ export type {
|
|
|
26
26
|
export { PrivateShuttleBookingFlow } from './components/booking/PrivateShuttleBookingFlow';
|
|
27
27
|
export type { BookingFlowUiOptions } from './components/booking/booking-flow-ui';
|
|
28
28
|
export type {
|
|
29
|
+
AdminAmendmentAdjustmentMode,
|
|
30
|
+
AdminAmendmentAdjustmentScope,
|
|
31
|
+
AdminAmendmentStructuredAdjustment,
|
|
32
|
+
AdminAmendmentTaxBehavior,
|
|
29
33
|
ProviderDashboardChangeBookingPayload,
|
|
30
34
|
ProviderDashboardInitialBooking,
|
|
31
35
|
} from './components/booking/provider-dashboard-change-booking';
|