@final-commerce/command-frame 0.8.0-staging.2 → 0.8.0
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/dist/actions/get-refund-plan/mock.d.ts +4 -0
- package/dist/actions/get-refund-plan/mock.js +8 -0
- package/dist/actions/get-refund-plan/mockRows.d.ts +6 -0
- package/dist/actions/get-refund-plan/mockRows.js +114 -0
- package/dist/actions/get-refund-plan/types.d.ts +89 -0
- package/dist/demo/database.js +4 -2
- package/dist/index.d.ts +1 -1
- package/package.json +2 -2
|
@@ -15,5 +15,9 @@ import { GetRefundPlan } from './types';
|
|
|
15
15
|
* and no company cash-rounding setting, so it always describes a FULL refund
|
|
16
16
|
* with `rounding: 0` and no cash `payout`. Against real kaching the allocation
|
|
17
17
|
* tracks the live selection and carries the drawer snap.
|
|
18
|
+
*
|
|
19
|
+
* `rows` / `allocation.breakdown` follow the engine's reading of the order
|
|
20
|
+
* (`line.total` includes the line's tax) but prorate by plain quantity share;
|
|
21
|
+
* the engine's remainder carry across earlier partial refunds is not modelled.
|
|
18
22
|
*/
|
|
19
23
|
export declare const mockGetRefundPlan: GetRefundPlan;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { MOCK_ORDERS } from '../../demo/database';
|
|
2
|
+
import { mockRefundBreakdown, mockRefundRows } from './mockRows';
|
|
2
3
|
/**
|
|
3
4
|
* Demo derivation of the runtime `getRefundPlan`. Builds the per-source rows
|
|
4
5
|
* from the mock order's `paymentMethods`, mirroring the runtime's capacity
|
|
@@ -15,6 +16,10 @@ import { MOCK_ORDERS } from '../../demo/database';
|
|
|
15
16
|
* and no company cash-rounding setting, so it always describes a FULL refund
|
|
16
17
|
* with `rounding: 0` and no cash `payout`. Against real kaching the allocation
|
|
17
18
|
* tracks the live selection and carries the drawer snap.
|
|
19
|
+
*
|
|
20
|
+
* `rows` / `allocation.breakdown` follow the engine's reading of the order
|
|
21
|
+
* (`line.total` includes the line's tax) but prorate by plain quantity share;
|
|
22
|
+
* the engine's remainder carry across earlier partial refunds is not modelled.
|
|
18
23
|
*/
|
|
19
24
|
export const mockGetRefundPlan = async (params) => {
|
|
20
25
|
console.log('[Mock] getRefundPlan called', params);
|
|
@@ -62,16 +67,19 @@ export const mockGetRefundPlan = async (params) => {
|
|
|
62
67
|
requiresGiftCardDestination: s.paymentType === 'redeem',
|
|
63
68
|
}));
|
|
64
69
|
const budget = legs.reduce((sum, l) => sum + l.amount, 0);
|
|
70
|
+
const rows = mockRefundRows(order);
|
|
65
71
|
return {
|
|
66
72
|
success: true,
|
|
67
73
|
orderId: order._id,
|
|
68
74
|
sources,
|
|
75
|
+
rows,
|
|
69
76
|
allocation: {
|
|
70
77
|
budget,
|
|
71
78
|
// No cash rounding in the demo, so the goods value and the budget agree.
|
|
72
79
|
itemTotal: budget,
|
|
73
80
|
rounding: 0,
|
|
74
81
|
legs,
|
|
82
|
+
breakdown: mockRefundBreakdown(rows, params?.items),
|
|
75
83
|
},
|
|
76
84
|
// Demo: no prior refunds, so remaining = captured minus the non-revenue load.
|
|
77
85
|
remainingRefundable: Math.max(0, totalCaptured - nonRefundableLiability),
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { CFActiveOrder } from '../../CommonTypes';
|
|
2
|
+
import type { GetRefundPlanParams, RefundPlanBreakdown, RefundPlanRow } from './types';
|
|
3
|
+
/** Every row of a demo order. The demo has no refund ledger, so everything is refundable. */
|
|
4
|
+
export declare function mockRefundRows(order: CFActiveOrder): RefundPlanRow[];
|
|
5
|
+
/** The selection's breakdown; no `items` means everything, like the demo's full-refund legs. */
|
|
6
|
+
export declare function mockRefundBreakdown(rows: RefundPlanRow[], items?: GetRefundPlanParams['items']): RefundPlanBreakdown;
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// Demo derivation of `getRefundPlan`'s `rows` and `allocation.breakdown`, for
|
|
2
|
+
// local/standalone mode only. See the note on `mockGetRefundPlan`.
|
|
3
|
+
const minor = (v) => Math.round(Number(v ?? 0)) || 0;
|
|
4
|
+
/** A line's whole-quantity money, reading `total` as tax-inclusive (the engine's reading). */
|
|
5
|
+
function lineAmounts(line) {
|
|
6
|
+
const tax = minor(line.totalTax);
|
|
7
|
+
const itemDiscount = (line.discount?.itemDiscounts ?? []).reduce((s, d) => s + minor(d.amount), 0);
|
|
8
|
+
const cartDiscount = minor(line.discount?.cartDiscount?.amount);
|
|
9
|
+
const net = Math.max(0, minor(line.total) - tax);
|
|
10
|
+
const taxes = (line.taxes ?? []).map((t) => ({
|
|
11
|
+
name: String(t.name ?? 'Tax'),
|
|
12
|
+
percentage: t.percentage,
|
|
13
|
+
amount: minor(t.amount),
|
|
14
|
+
}));
|
|
15
|
+
return { subtotal: net + itemDiscount + cartDiscount, itemDiscount, cartDiscount, tax, taxes, total: net + tax };
|
|
16
|
+
}
|
|
17
|
+
function scale(a, share) {
|
|
18
|
+
if (share >= 1)
|
|
19
|
+
return a;
|
|
20
|
+
const r = (n) => Math.round(n * share);
|
|
21
|
+
const out = {
|
|
22
|
+
subtotal: r(a.subtotal),
|
|
23
|
+
itemDiscount: r(a.itemDiscount),
|
|
24
|
+
cartDiscount: r(a.cartDiscount),
|
|
25
|
+
tax: r(a.tax),
|
|
26
|
+
taxes: a.taxes.map((t) => ({ ...t, amount: r(t.amount) })),
|
|
27
|
+
};
|
|
28
|
+
return { ...out, total: out.subtotal - out.itemDiscount - out.cartDiscount + out.tax };
|
|
29
|
+
}
|
|
30
|
+
/** Every row of a demo order. The demo has no refund ledger, so everything is refundable. */
|
|
31
|
+
export function mockRefundRows(order) {
|
|
32
|
+
const rows = [];
|
|
33
|
+
for (const li of order.lineItems ?? []) {
|
|
34
|
+
const itemKey = String(li.internalId || li.variantId || '');
|
|
35
|
+
if (!itemKey || !(li.quantity > 0))
|
|
36
|
+
continue;
|
|
37
|
+
rows.push({
|
|
38
|
+
type: 'product',
|
|
39
|
+
itemKey,
|
|
40
|
+
label: String(li.name ?? 'Item'),
|
|
41
|
+
...(li.sku ? { sku: li.sku } : {}),
|
|
42
|
+
...(li.attributes ? { attributes: li.attributes } : {}),
|
|
43
|
+
quantity: li.quantity,
|
|
44
|
+
refundableQuantity: li.quantity,
|
|
45
|
+
amounts: lineAmounts(li),
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
for (const cs of order.customSales ?? []) {
|
|
49
|
+
if (!cs.customSaleId || !(cs.quantity > 0))
|
|
50
|
+
continue;
|
|
51
|
+
rows.push({
|
|
52
|
+
type: 'customSale',
|
|
53
|
+
itemKey: cs.customSaleId,
|
|
54
|
+
label: String(cs.name ?? 'Custom sale'),
|
|
55
|
+
quantity: cs.quantity,
|
|
56
|
+
refundableQuantity: cs.quantity,
|
|
57
|
+
amounts: lineAmounts(cs),
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
for (const fee of order.cartFees ?? []) {
|
|
61
|
+
const amount = minor(fee.amount);
|
|
62
|
+
const tax = minor(fee.tax);
|
|
63
|
+
rows.push({
|
|
64
|
+
type: 'fee',
|
|
65
|
+
itemKey: String(fee.id),
|
|
66
|
+
label: String(fee.label || 'Cart fee'),
|
|
67
|
+
quantity: 1,
|
|
68
|
+
refundableQuantity: 1,
|
|
69
|
+
amounts: { subtotal: amount, itemDiscount: 0, cartDiscount: 0, tax, taxes: [], total: amount + tax },
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
for (const pm of order.paymentMethods ?? []) {
|
|
73
|
+
const tip = minor(pm.tip?.amount);
|
|
74
|
+
if (tip <= 0)
|
|
75
|
+
continue;
|
|
76
|
+
rows.push({
|
|
77
|
+
type: 'tip',
|
|
78
|
+
itemKey: pm.transactionId,
|
|
79
|
+
label: 'Tip',
|
|
80
|
+
paymentType: pm.paymentType,
|
|
81
|
+
quantity: 1,
|
|
82
|
+
refundableQuantity: 1,
|
|
83
|
+
amounts: { subtotal: tip, itemDiscount: 0, cartDiscount: 0, tax: 0, taxes: [], total: tip },
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
return rows;
|
|
87
|
+
}
|
|
88
|
+
/** The selection's breakdown; no `items` means everything, like the demo's full-refund legs. */
|
|
89
|
+
export function mockRefundBreakdown(rows, items) {
|
|
90
|
+
const picked = new Map((items ?? []).map((i) => [i.itemKey, i.quantity]));
|
|
91
|
+
const selected = rows
|
|
92
|
+
.map((row) => {
|
|
93
|
+
const quantity = items?.length
|
|
94
|
+
? Math.min(picked.get(row.itemKey) ?? 0, row.refundableQuantity)
|
|
95
|
+
: row.refundableQuantity;
|
|
96
|
+
return { type: row.type, itemKey: row.itemKey, quantity, amounts: scale(row.amounts, quantity / row.quantity) };
|
|
97
|
+
})
|
|
98
|
+
.filter((r) => r.quantity > 0);
|
|
99
|
+
const totals = { items: 0, discounts: 0, fees: 0, tax: 0, tip: 0, total: 0 };
|
|
100
|
+
for (const r of selected) {
|
|
101
|
+
const a = r.amounts;
|
|
102
|
+
if (r.type === 'fee')
|
|
103
|
+
totals.fees += a.subtotal;
|
|
104
|
+
else if (r.type === 'tip')
|
|
105
|
+
totals.tip += a.subtotal;
|
|
106
|
+
else {
|
|
107
|
+
totals.items += a.subtotal;
|
|
108
|
+
totals.discounts += a.itemDiscount + a.cartDiscount;
|
|
109
|
+
}
|
|
110
|
+
totals.tax += a.tax;
|
|
111
|
+
totals.total += a.total;
|
|
112
|
+
}
|
|
113
|
+
return { rows: selected, totals };
|
|
114
|
+
}
|
|
@@ -79,6 +79,81 @@ export interface RefundPlanLeg {
|
|
|
79
79
|
rounding: number;
|
|
80
80
|
};
|
|
81
81
|
}
|
|
82
|
+
export type RefundPlanRowType = 'product' | 'customSale' | 'fee' | 'tip';
|
|
83
|
+
/** One tax rate's share of a row's refund (minor units). */
|
|
84
|
+
export interface RefundPlanTaxLine {
|
|
85
|
+
name: string;
|
|
86
|
+
/** Decimal rate as stored on the order (e.g. `0.15`), when the order recorded one. */
|
|
87
|
+
percentage?: number;
|
|
88
|
+
amount: number;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* The money a row refunds, split the way a receipt shows it. Every field is
|
|
92
|
+
* minor units and already rounded; `subtotal − itemDiscount − cartDiscount +
|
|
93
|
+
* tax === total` always holds. DISPLAY IT — never re-add or prorate it.
|
|
94
|
+
*/
|
|
95
|
+
export interface RefundPlanAmounts {
|
|
96
|
+
/** Before discounts, tax excluded. For a tip row, the tip itself. */
|
|
97
|
+
subtotal: number;
|
|
98
|
+
/** Per-item discounts on the refunded quantity (positive). */
|
|
99
|
+
itemDiscount: number;
|
|
100
|
+
/** Cart discount share on the refunded quantity (positive). */
|
|
101
|
+
cartDiscount: number;
|
|
102
|
+
/** Tax on the refunded quantity. Zero for a tip. */
|
|
103
|
+
tax: number;
|
|
104
|
+
/** `tax` per rate — the price breakdown's tax lines. */
|
|
105
|
+
taxes: RefundPlanTaxLine[];
|
|
106
|
+
/** What the row refunds. */
|
|
107
|
+
total: number;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* One refundable row of the order — a line item, custom sale, cart fee or tip —
|
|
111
|
+
* served ready to render. Rows with nothing left to refund are not listed.
|
|
112
|
+
*/
|
|
113
|
+
export interface RefundPlanRow {
|
|
114
|
+
type: RefundPlanRowType;
|
|
115
|
+
/** The key `items[].itemKey` takes, for this call and for `processPartialRefund`. */
|
|
116
|
+
itemKey: string;
|
|
117
|
+
/** Line or fee name; `Tip` for a tip. */
|
|
118
|
+
label: string;
|
|
119
|
+
/** Product lines only, when the order recorded them. */
|
|
120
|
+
sku?: string;
|
|
121
|
+
attributes?: string;
|
|
122
|
+
/** Tip rows only: the tender that took the tip (`card`, `cash`, …). */
|
|
123
|
+
paymentType?: string;
|
|
124
|
+
/** Quantity originally sold. Fees and tips are `1` — all or nothing. */
|
|
125
|
+
quantity: number;
|
|
126
|
+
/** Quantity still refundable — the stepper's max. */
|
|
127
|
+
refundableQuantity: number;
|
|
128
|
+
/** The money for refunding ALL of `refundableQuantity`. */
|
|
129
|
+
amounts: RefundPlanAmounts;
|
|
130
|
+
}
|
|
131
|
+
/** One selected row and what refunding the selected quantity of it moves. */
|
|
132
|
+
export interface RefundPlanSelectedRow {
|
|
133
|
+
type: RefundPlanRowType;
|
|
134
|
+
itemKey: string;
|
|
135
|
+
quantity: number;
|
|
136
|
+
amounts: RefundPlanAmounts;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* The selection's goods value broken down for display. Minor units;
|
|
140
|
+
* `items − discounts + fees + tax + tip === total === allocation.itemTotal`.
|
|
141
|
+
*/
|
|
142
|
+
export interface RefundPlanTotals {
|
|
143
|
+
/** Σ selected line subtotals (before discounts, tax excluded). */
|
|
144
|
+
items: number;
|
|
145
|
+
/** Σ item + cart discounts on the selected lines (positive). */
|
|
146
|
+
discounts: number;
|
|
147
|
+
/** Σ selected cart fees, tax excluded. */
|
|
148
|
+
fees: number;
|
|
149
|
+
tax: number;
|
|
150
|
+
tip: number;
|
|
151
|
+
total: number;
|
|
152
|
+
}
|
|
153
|
+
export interface RefundPlanBreakdown {
|
|
154
|
+
rows: RefundPlanSelectedRow[];
|
|
155
|
+
totals: RefundPlanTotals;
|
|
156
|
+
}
|
|
82
157
|
/**
|
|
83
158
|
* The engine's own allocation of the CURRENT refund selection across the
|
|
84
159
|
* order's captures — what a flow renders and submits instead of computing a
|
|
@@ -106,11 +181,25 @@ export interface RefundPlanAllocation {
|
|
|
106
181
|
rounding: number;
|
|
107
182
|
/** One leg per source that receives money. Submit as `legs`, unchanged. */
|
|
108
183
|
legs: RefundPlanLeg[];
|
|
184
|
+
/**
|
|
185
|
+
* What the selection refunds, per row and in total — the numbers a refund
|
|
186
|
+
* dialog's rows and footer render. Optional so an older runtime still
|
|
187
|
+
* type-checks; kaching 1.12.1+ always sends it.
|
|
188
|
+
*/
|
|
189
|
+
breakdown?: RefundPlanBreakdown;
|
|
109
190
|
}
|
|
110
191
|
export interface GetRefundPlanResponse {
|
|
111
192
|
success: boolean;
|
|
112
193
|
orderId: string;
|
|
113
194
|
sources: RefundPlanSource[];
|
|
195
|
+
/**
|
|
196
|
+
* Every row still refundable on the order, with the money for refunding all
|
|
197
|
+
* of it — what a refund dialog lists before anything is selected. Replaces
|
|
198
|
+
* reading line totals off the order and deciding whether they include tax.
|
|
199
|
+
* Optional so an older runtime still type-checks; kaching 1.12.1+ always
|
|
200
|
+
* sends it.
|
|
201
|
+
*/
|
|
202
|
+
rows?: RefundPlanRow[];
|
|
114
203
|
/**
|
|
115
204
|
* Ready-to-submit allocation of the current selection. Present only when a
|
|
116
205
|
* refund selection exists on the active order. See {@link RefundPlanAllocation}.
|
package/dist/demo/database.js
CHANGED
|
@@ -982,8 +982,10 @@ const CLOSE_HOUR = 18;
|
|
|
982
982
|
// Rooms, not people: a resource is whatever is scarce, and a room is the case that
|
|
983
983
|
// reads the same in every vertical a dataset might describe.
|
|
984
984
|
export const MOCK_BOOKING_RESOURCES = [
|
|
985
|
-
|
|
986
|
-
|
|
985
|
+
// Two different photos, from the assets this demo already ships: a screen that picks a
|
|
986
|
+
// resource by sight has to be fed two that look apart, not one repeated.
|
|
987
|
+
{ id: 'res_room_1', name: 'Room 1', kind: BookingResourceKind.ROOM, image: beetImg },
|
|
988
|
+
{ id: 'res_room_2', name: 'Room 2', kind: BookingResourceKind.ROOM, image: roastedTomatoImg },
|
|
987
989
|
];
|
|
988
990
|
/**
|
|
989
991
|
* The clock this mock shop keeps — the same one `getContext` reports. They used to disagree: the
|
package/dist/index.d.ts
CHANGED
|
@@ -160,7 +160,7 @@ export type { CalculateRefundTotal, CalculateRefundTotalParams, CalculateRefundT
|
|
|
160
160
|
export type { GetRemainingRefundableQuantities, GetRemainingRefundableQuantitiesParams, GetRemainingRefundableQuantitiesResponse, } from './actions/get-remaining-refundable-quantities/types';
|
|
161
161
|
export type { ProcessPartialRefund, ProcessPartialRefundParams, ProcessPartialRefundResponse, } from './actions/process-partial-refund/types';
|
|
162
162
|
export type { RedeemRefund, RedeemRefundParams, RedeemRefundResponse } from './actions/redeem-refund/types';
|
|
163
|
-
export type { GetRefundPlan, GetRefundPlanParams, GetRefundPlanResponse, RefundPlanSource, RefundPlanAllocation, RefundPlanLeg, } from './actions/get-refund-plan/types';
|
|
163
|
+
export type { GetRefundPlan, GetRefundPlanParams, GetRefundPlanResponse, RefundPlanSource, RefundPlanAllocation, RefundPlanLeg, RefundPlanRowType, RefundPlanTaxLine, RefundPlanAmounts, RefundPlanRow, RefundPlanSelectedRow, RefundPlanTotals, RefundPlanBreakdown, } from './actions/get-refund-plan/types';
|
|
164
164
|
export type { CheckPermission, CheckPermissionParams, CheckPermissionResponse } from './actions/check-permission/types';
|
|
165
165
|
export type { InitiateRefund, InitiateRefundParams, InitiateRefundResponse } from './actions/initiate-refund/types';
|
|
166
166
|
export type { GetCurrentCart, GetCurrentCartResponse } from './actions/get-current-cart/types';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@final-commerce/command-frame",
|
|
3
|
-
"version": "0.8.0
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Commands Frame library",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
"ignoreBranchesMissingTickets": true
|
|
58
58
|
},
|
|
59
59
|
"dependencies": {
|
|
60
|
-
"@final-commerce/common": "2.4.0
|
|
60
|
+
"@final-commerce/common": "2.4.0"
|
|
61
61
|
},
|
|
62
62
|
"devDependencies": {
|
|
63
63
|
"@commitlint/cli": "^19.0.0",
|