@final-commerce/command-frame 0.7.0-staging.9 → 0.7.1
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/CommonTypes.d.ts +14 -26
- package/dist/actions/add-product-to-cart/mock.js +18 -1
- package/dist/actions/add-product-to-cart/types.d.ts +10 -0
- package/dist/actions/get-context/mock.js +0 -4
- package/dist/actions/get-product-modifier-selections/action.d.ts +6 -0
- package/dist/actions/get-product-modifier-selections/action.js +8 -0
- package/dist/actions/get-product-modifier-selections/mock.d.ts +2 -0
- package/dist/actions/get-product-modifier-selections/mock.js +30 -0
- package/dist/actions/get-product-modifier-selections/types.d.ts +23 -0
- 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 +91 -5
- package/dist/actions/get-remaining-refundable-quantities/mock.js +0 -1
- package/dist/actions/get-remaining-refundable-quantities/types.d.ts +0 -8
- package/dist/actions/process-partial-refund/types.d.ts +2 -2
- package/dist/actions/set-product-modifier-selections/action.d.ts +6 -0
- package/dist/actions/set-product-modifier-selections/action.js +8 -0
- package/dist/actions/set-product-modifier-selections/mock.d.ts +2 -0
- package/dist/actions/set-product-modifier-selections/mock.js +49 -0
- package/dist/actions/set-product-modifier-selections/types.d.ts +27 -0
- package/dist/demo/database.d.ts +24 -12
- package/dist/demo/database.js +61 -121
- package/dist/index.d.ts +5 -17
- package/dist/index.js +4 -15
- package/dist/projects/render/mocks.js +4 -14
- package/dist/projects/render/types.d.ts +3 -8
- package/dist/pubsub/topics/cart/product-modifiers-updated/types.d.ts +16 -0
- package/dist/pubsub/topics/cart/types.d.ts +4 -2
- package/dist/pubsub/topics/cart/types.js +1 -0
- package/dist/pubsub/topics/index.d.ts +16 -17
- package/dist/pubsub/topics/index.js +16 -17
- package/dist/pubsub/topics/types.d.ts +0 -2
- package/package.json +5 -2
- package/dist/actions/add-booking-to-cart/action.d.ts +0 -6
- package/dist/actions/add-booking-to-cart/action.js +0 -8
- package/dist/actions/add-booking-to-cart/mock.d.ts +0 -2
- package/dist/actions/add-booking-to-cart/mock.js +0 -42
- package/dist/actions/add-booking-to-cart/types.d.ts +0 -23
- package/dist/actions/cancel-booking/action.d.ts +0 -6
- package/dist/actions/cancel-booking/action.js +0 -8
- package/dist/actions/cancel-booking/mock.d.ts +0 -2
- package/dist/actions/cancel-booking/mock.js +0 -10
- package/dist/actions/cancel-booking/types.d.ts +0 -10
- package/dist/actions/get-booking-availability/action.d.ts +0 -6
- package/dist/actions/get-booking-availability/action.js +0 -8
- package/dist/actions/get-booking-availability/mock.d.ts +0 -2
- package/dist/actions/get-booking-availability/mock.js +0 -6
- package/dist/actions/get-booking-availability/types.d.ts +0 -18
- package/dist/actions/get-booking-resources/action.d.ts +0 -6
- package/dist/actions/get-booking-resources/action.js +0 -8
- package/dist/actions/get-booking-resources/mock.d.ts +0 -2
- package/dist/actions/get-booking-resources/mock.js +0 -7
- package/dist/actions/get-booking-resources/types.d.ts +0 -12
- package/dist/actions/get-booking-resources/types.js +0 -1
- package/dist/actions/get-bookings/action.d.ts +0 -6
- package/dist/actions/get-bookings/action.js +0 -8
- package/dist/actions/get-bookings/mock.d.ts +0 -2
- package/dist/actions/get-bookings/mock.js +0 -20
- package/dist/actions/get-bookings/types.d.ts +0 -18
- package/dist/actions/get-bookings/types.js +0 -1
- package/dist/actions/hold-booking/action.d.ts +0 -6
- package/dist/actions/hold-booking/action.js +0 -8
- package/dist/actions/hold-booking/mock.d.ts +0 -2
- package/dist/actions/hold-booking/mock.js +0 -33
- package/dist/actions/hold-booking/types.d.ts +0 -24
- package/dist/actions/hold-booking/types.js +0 -1
- package/dist/actions/remove-booking-from-cart/action.d.ts +0 -6
- package/dist/actions/remove-booking-from-cart/action.js +0 -8
- package/dist/actions/remove-booking-from-cart/mock.d.ts +0 -2
- package/dist/actions/remove-booking-from-cart/mock.js +0 -26
- package/dist/actions/remove-booking-from-cart/types.d.ts +0 -12
- package/dist/actions/remove-booking-from-cart/types.js +0 -1
- package/dist/pubsub/topics/bookings/booking-created/types.d.ts +0 -6
- package/dist/pubsub/topics/bookings/booking-created/types.js +0 -1
- package/dist/pubsub/topics/bookings/booking-setup-changed/types.d.ts +0 -15
- package/dist/pubsub/topics/bookings/booking-setup-changed/types.js +0 -1
- package/dist/pubsub/topics/bookings/booking-updated/types.d.ts +0 -6
- package/dist/pubsub/topics/bookings/booking-updated/types.js +0 -1
- package/dist/pubsub/topics/bookings/index.d.ts +0 -3
- package/dist/pubsub/topics/bookings/index.js +0 -27
- package/dist/pubsub/topics/bookings/types.d.ts +0 -8
- package/dist/pubsub/topics/bookings/types.js +0 -3
- /package/dist/actions/{add-booking-to-cart → get-product-modifier-selections}/types.js +0 -0
- /package/dist/actions/{cancel-booking → set-product-modifier-selections}/types.js +0 -0
- /package/dist/{actions/get-booking-availability → pubsub/topics/cart/product-modifiers-updated}/types.js +0 -0
package/dist/CommonTypes.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export * from './common-types';
|
|
2
|
-
import type { Tax, Tip, Address, MetadataItem, PosDataItem, CartFeeTaxEntry, CartFeeItem, CartDiscountItem, OrderNote, NonRevenueItem, DiscountDetail, FeeDetail, DiscountLineItem, FeeLineItem, LineItem, TipPayment, RefundedTipPayment, PaymentMethod, Discount, CustomFee, Summary, CustomSale, RefundedCustomSale, RefundItem, RefundedLineItem, ActiveStation, ActiveSession, ActiveOutlet, ActiveUser, ActiveUserRole, ActiveOrder, ActiveCart, ActivePark, ActiveCompany, ActiveCustomSales, ActiveProduct, ActiveCustomer, FullProduct, ProductVariant, Inventory,
|
|
2
|
+
import type { Tax, Tip, Address, MetadataItem, PosDataItem, CartFeeTaxEntry, CartFeeItem, CartDiscountItem, OrderNote, NonRevenueItem, DiscountDetail, FeeDetail, DiscountLineItem, FeeLineItem, LineItem, TipPayment, RefundedTipPayment, PaymentMethod, Discount, CustomFee, Summary, CustomSale, RefundedCustomSale, RefundItem, RefundedLineItem, ActiveStation, ActiveSession, ActiveOutlet, ActiveUser, ActiveUserRole, ActiveOrder, ActiveCart, ActivePark, ActiveCompany, ActiveCustomSales, ActiveProduct, ActiveCustomer, FullProduct, ProductVariant, Inventory, ResolvedModifier, ResolvedModifierChoice, ModifierSelectionType, ModifierSelection, ModifierChoiceSelection, CartLineModifier, ProdModifierBreakdown, OrderLineItemModifier, CustomerNote, Attribute, AttributeOption, Category, Transaction, ActiveSplitPayment } from '@final-commerce/common/pos-types';
|
|
3
3
|
export { CurrencyCode, ProductType as CFProductType, UserTypes as CFUserTypes } from '@final-commerce/common';
|
|
4
4
|
/** Open record used as a base for active entities. */
|
|
5
5
|
export type CFActiveEntity = Record<string, unknown>;
|
|
@@ -18,16 +18,19 @@ export type CFTransaction = Transaction;
|
|
|
18
18
|
export type CFCategory = Category;
|
|
19
19
|
export type CFProductVariant = ProductVariant;
|
|
20
20
|
export type CFProduct = FullProduct;
|
|
21
|
-
|
|
22
|
-
export type
|
|
23
|
-
export type
|
|
24
|
-
export type
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
export type
|
|
30
|
-
|
|
21
|
+
/** Modifier (the till question) resolved onto a product — `CFProduct.modifiers`. */
|
|
22
|
+
export type CFResolvedModifier = ResolvedModifier;
|
|
23
|
+
export type CFResolvedModifierChoice = ResolvedModifierChoice;
|
|
24
|
+
export type CFModifierSelectionType = ModifierSelectionType;
|
|
25
|
+
/** One selected modifier choice on an order line item (wire shape). */
|
|
26
|
+
export type CFOrderLineItemModifier = OrderLineItemModifier;
|
|
27
|
+
/** Cart-line modifier answer (pre-pricing) — `CFActiveProduct.modifierSelections`. */
|
|
28
|
+
export type CFModifierSelection = ModifierSelection;
|
|
29
|
+
export type CFModifierChoiceSelection = ModifierChoiceSelection;
|
|
30
|
+
/** One selected choice PRICED onto a cart line — `CFActiveProduct.modifiers` (not the resolved stack). */
|
|
31
|
+
export type CFCartLineModifier = CartLineModifier;
|
|
32
|
+
/** A priced, line-extended modifier row: "Toppings - Avocado" x2, `amount` 1000. */
|
|
33
|
+
export type CFProdModifierBreakdown = ProdModifierBreakdown;
|
|
31
34
|
export type CFActiveProduct = ActiveProduct;
|
|
32
35
|
export type CFCustomer = ActiveCustomer;
|
|
33
36
|
export type CFActiveCustomer = ActiveCustomer;
|
|
@@ -116,21 +119,6 @@ export interface CFContextRender {
|
|
|
116
119
|
stationName: string | null;
|
|
117
120
|
outletId: string | null;
|
|
118
121
|
outletName: string | null;
|
|
119
|
-
/**
|
|
120
|
-
* IANA zone the SHOP keeps, resolved as the outlet's own zone, else the
|
|
121
|
-
* company's — never the device's. A booking is stored as an instant, so which
|
|
122
|
-
* hour and which DAY it reads as is decided entirely by the zone it is printed
|
|
123
|
-
* in, and the screen's own machine is the one zone that is certainly wrong: a
|
|
124
|
-
* 17:00 Monday appointment in Vancouver is 21:30 for a till in St John's, and
|
|
125
|
-
* for some viewers it lands on Tuesday. Without this an app cannot label a
|
|
126
|
-
* slot or an appointment honestly — it can only guess, and it guesses wrong
|
|
127
|
-
* for every business that does not sit in the same zone as its screen.
|
|
128
|
-
*
|
|
129
|
-
* `null` when neither the outlet nor the company has one configured. Say so
|
|
130
|
-
* rather than falling back to the device: a missing timezone is a setup
|
|
131
|
-
* problem, and printing the machine's clock hides it.
|
|
132
|
-
*/
|
|
133
|
-
timeZone: string | null;
|
|
134
122
|
buildId: string | null;
|
|
135
123
|
buildName: string | null;
|
|
136
124
|
buildVersion: string | null;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { MOCK_CART, MOCK_PRODUCTS, mockPublishEvent } from '../../demo/database';
|
|
1
|
+
import { MOCK_CART, MOCK_PRODUCTS, buildCartLineModifiers, buildModifierRows, mockPublishEvent, } from '../../demo/database';
|
|
2
2
|
import { extendPrice, isValidQuantity, resolveUnit } from '@final-commerce/common';
|
|
3
3
|
export const mockAddProductToCart = async (params) => {
|
|
4
4
|
console.log('[Mock] addProductToCart called', params);
|
|
@@ -53,6 +53,18 @@ export const mockAddProductToCart = async (params) => {
|
|
|
53
53
|
note: note,
|
|
54
54
|
// discount/fee could be added here to mock object if CFActiveProduct supports it
|
|
55
55
|
};
|
|
56
|
+
// The spread above copied the PRODUCT's `modifiers` — a ResolvedModifier[] menu — into a
|
|
57
|
+
// field that means "the choices this line carries" (CartLineModifier[]). Two different
|
|
58
|
+
// shapes, one name. Drop it before anything reads the line, or every surface renders the
|
|
59
|
+
// whole catalogue as if the cashier had picked all of it.
|
|
60
|
+
delete activeProduct.modifiers;
|
|
61
|
+
// Keep the raw answers AND the priced rows, the same pair the real host writes. The cart
|
|
62
|
+
// TOTAL is deliberately left alone: like a product fee (see add-product-fee), per-line
|
|
63
|
+
// money is not accumulated by this mock — `total` stays Σ extendPrice(price, quantity).
|
|
64
|
+
if (params?.modifiers?.length) {
|
|
65
|
+
activeProduct.modifierSelections = params.modifiers;
|
|
66
|
+
activeProduct.modifiers = buildCartLineModifiers(product.modifiers, params.modifiers, product.taxTable);
|
|
67
|
+
}
|
|
56
68
|
MOCK_CART.products.push(activeProduct);
|
|
57
69
|
// Recalculate totals. extendPrice, not a raw multiply: a fractional quantity times an
|
|
58
70
|
// integer-minor price leaves fractional cents, which no order may carry.
|
|
@@ -64,6 +76,9 @@ export const mockAddProductToCart = async (params) => {
|
|
|
64
76
|
MOCK_CART.remainingBalance = MOCK_CART.total;
|
|
65
77
|
// Publish cart event to simulate real behavior
|
|
66
78
|
mockPublishEvent('cart', 'product-added', { product: activeProduct });
|
|
79
|
+
// Display-ready modifier rows for the line that was just created, so a flow can
|
|
80
|
+
// show what the modifiers added without re-reading the cart or multiplying.
|
|
81
|
+
const { rows, modifiersTotal } = buildModifierRows(activeProduct.modifiers, quantity);
|
|
67
82
|
return {
|
|
68
83
|
success: true,
|
|
69
84
|
productId: activeProduct.id,
|
|
@@ -71,6 +86,8 @@ export const mockAddProductToCart = async (params) => {
|
|
|
71
86
|
internalId: activeProduct.internalId,
|
|
72
87
|
name: activeProduct.name,
|
|
73
88
|
quantity: quantity,
|
|
89
|
+
rows,
|
|
90
|
+
modifiersTotal,
|
|
74
91
|
timestamp: new Date().toISOString(),
|
|
75
92
|
};
|
|
76
93
|
};
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { AddProductDiscountParams } from '../add-product-discount/types';
|
|
2
2
|
import type { AddProductFeeParams } from '../add-product-fee/types';
|
|
3
|
+
import type { ModifierSelection } from '../get-product-modifier-selections/types';
|
|
4
|
+
import type { CFProdModifierBreakdown } from '../../CommonTypes';
|
|
3
5
|
export interface AddProductToCartParams {
|
|
4
6
|
/** ID of the variant to add. */
|
|
5
7
|
variantId: string;
|
|
@@ -21,17 +23,25 @@ export interface AddProductToCartParams {
|
|
|
21
23
|
discounts?: AddProductDiscountParams[];
|
|
22
24
|
/** Array of fees to apply immediately. */
|
|
23
25
|
fees?: AddProductFeeParams[];
|
|
26
|
+
/** Modifier selections to apply immediately. */
|
|
27
|
+
modifiers?: ModifierSelection[];
|
|
24
28
|
/** Note or array of notes to add immediately. */
|
|
25
29
|
notes?: string | string[];
|
|
26
30
|
}
|
|
27
31
|
export interface AddProductToCartResponse {
|
|
28
32
|
success: boolean;
|
|
33
|
+
/** Set when the add was rejected (e.g. a required modifier is unanswered). */
|
|
34
|
+
reason?: string;
|
|
29
35
|
productId: string;
|
|
30
36
|
variantId: string;
|
|
31
37
|
/** The unique identifier for the specific item instance added to the cart. */
|
|
32
38
|
internalId: string;
|
|
33
39
|
name: string;
|
|
34
40
|
quantity: number;
|
|
41
|
+
/** The modifiers the line was created with, display-ready. Empty when there are none. */
|
|
42
|
+
rows: CFProdModifierBreakdown[];
|
|
43
|
+
/** Sum of `rows[].amount` — what the modifiers added to this line, in minor units. */
|
|
44
|
+
modifiersTotal: number;
|
|
35
45
|
timestamp: string;
|
|
36
46
|
}
|
|
37
47
|
export type AddProductToCart = (params?: AddProductToCartParams) => Promise<AddProductToCartResponse>;
|
|
@@ -11,10 +11,6 @@ export const mockGetContext = () => {
|
|
|
11
11
|
stationName: MOCK_STATION.name,
|
|
12
12
|
outletId: MOCK_OUTLET.id,
|
|
13
13
|
outletName: MOCK_OUTLET.name || null,
|
|
14
|
-
// Deliberately NOT the developer's own zone: an app that quietly formats with
|
|
15
|
-
// the machine's clock looks correct in preview and is wrong on every till that
|
|
16
|
-
// sits in a different city. Here it cannot look correct by accident.
|
|
17
|
-
timeZone: "America/Vancouver",
|
|
18
14
|
buildId: "mock_build_id",
|
|
19
15
|
buildName: "Mock Build",
|
|
20
16
|
buildVersion: "1.0.0-mock",
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Get product modifier selections action (read-only)
|
|
3
|
+
* Calls the getProductModifierSelections action on the parent window
|
|
4
|
+
*/
|
|
5
|
+
import type { GetProductModifierSelections } from "./types";
|
|
6
|
+
export declare const getProductModifierSelections: GetProductModifierSelections;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Get product modifier selections action (read-only)
|
|
3
|
+
* Calls the getProductModifierSelections action on the parent window
|
|
4
|
+
*/
|
|
5
|
+
import { commandFrameClient } from "../../client";
|
|
6
|
+
export const getProductModifierSelections = async (params) => {
|
|
7
|
+
return await commandFrameClient.call("getProductModifierSelections", params);
|
|
8
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { MOCK_CART, buildModifierRows } from "../../demo/database";
|
|
2
|
+
export const mockGetProductModifierSelections = async (params) => {
|
|
3
|
+
console.log("[Mock] getProductModifierSelections called", params);
|
|
4
|
+
// Read the line's stored selections from the mock cart (last line when no internalId).
|
|
5
|
+
const line = params?.internalId
|
|
6
|
+
? MOCK_CART.products.find((p) => p.internalId === params.internalId)
|
|
7
|
+
: MOCK_CART.products[MOCK_CART.products.length - 1];
|
|
8
|
+
if (!line) {
|
|
9
|
+
return {
|
|
10
|
+
success: false,
|
|
11
|
+
reason: "No matching cart line",
|
|
12
|
+
internalId: params?.internalId,
|
|
13
|
+
selections: [],
|
|
14
|
+
rows: [],
|
|
15
|
+
modifiersTotal: 0,
|
|
16
|
+
timestamp: new Date().toISOString()
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
// The priced rows already sit on the line; extending them by the line quantity is
|
|
20
|
+
// the host's job, not the flow's.
|
|
21
|
+
const { rows, modifiersTotal } = buildModifierRows(line.modifiers, line.quantity);
|
|
22
|
+
return {
|
|
23
|
+
success: true,
|
|
24
|
+
internalId: line.internalId,
|
|
25
|
+
selections: line.modifierSelections ?? [],
|
|
26
|
+
rows,
|
|
27
|
+
modifiersTotal,
|
|
28
|
+
timestamp: new Date().toISOString()
|
|
29
|
+
};
|
|
30
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { CFModifierSelection, CFModifierChoiceSelection, CFProdModifierBreakdown } from "../../CommonTypes";
|
|
2
|
+
/** Alias of common's — `quantity` is units PER LINE-ITEM UNIT. */
|
|
3
|
+
export type ModifierChoiceSelection = CFModifierChoiceSelection;
|
|
4
|
+
/** Alias of common's — the cashier's answer to one modifier. */
|
|
5
|
+
export type ModifierSelection = CFModifierSelection;
|
|
6
|
+
export interface GetProductModifierSelectionsParams {
|
|
7
|
+
/** The cart line to read. Defaults to the active product's line. */
|
|
8
|
+
internalId?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface GetProductModifierSelectionsResponse {
|
|
11
|
+
success: boolean;
|
|
12
|
+
/** Set when the read failed (e.g. no such line, no active product). */
|
|
13
|
+
reason?: string;
|
|
14
|
+
internalId?: string;
|
|
15
|
+
/** Raw ids — round-trips back into the setter. No names, no money: use `rows` to display. */
|
|
16
|
+
selections: ModifierSelection[];
|
|
17
|
+
/** Display-ready: "Toppings - Avocado" x2, `amount` 1000. One row per chosen choice. */
|
|
18
|
+
rows: CFProdModifierBreakdown[];
|
|
19
|
+
/** Sum of `rows[].amount` for THIS line, in minor units. */
|
|
20
|
+
modifiersTotal: number;
|
|
21
|
+
timestamp: string;
|
|
22
|
+
}
|
|
23
|
+
export type GetProductModifierSelections = (params?: GetProductModifierSelectionsParams) => Promise<GetProductModifierSelectionsResponse>;
|
|
@@ -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
|
+
}
|
|
@@ -18,14 +18,11 @@ export interface GetRefundPlanParams {
|
|
|
18
18
|
* nothing staged, no `allocation` comes back.
|
|
19
19
|
*/
|
|
20
20
|
items?: {
|
|
21
|
-
/**
|
|
22
|
-
* `internalId` / `variantId` for a product, `customSaleId`, cart-fee id, tip
|
|
23
|
-
* `transactionId`, or a booking's own `internalId` on `order.reservations[]`.
|
|
24
|
-
*/
|
|
21
|
+
/** `internalId` / `variantId` for a product, `customSaleId`, cart-fee id, or tip `transactionId`. */
|
|
25
22
|
itemKey: string;
|
|
26
23
|
quantity: number;
|
|
27
24
|
/** Optional hint; inferred from the order when omitted. */
|
|
28
|
-
type?: 'product' | 'customSale' | 'fee' | 'tip'
|
|
25
|
+
type?: 'product' | 'customSale' | 'fee' | 'tip';
|
|
29
26
|
}[];
|
|
30
27
|
}
|
|
31
28
|
export interface RefundPlanSource {
|
|
@@ -79,6 +76,81 @@ export interface RefundPlanLeg {
|
|
|
79
76
|
rounding: number;
|
|
80
77
|
};
|
|
81
78
|
}
|
|
79
|
+
export type RefundPlanRowType = 'product' | 'customSale' | 'fee' | 'tip';
|
|
80
|
+
/** One tax rate's share of a row's refund (minor units). */
|
|
81
|
+
export interface RefundPlanTaxLine {
|
|
82
|
+
name: string;
|
|
83
|
+
/** Decimal rate as stored on the order (e.g. `0.15`), when the order recorded one. */
|
|
84
|
+
percentage?: number;
|
|
85
|
+
amount: number;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* The money a row refunds, split the way a receipt shows it. Every field is
|
|
89
|
+
* minor units and already rounded; `subtotal − itemDiscount − cartDiscount +
|
|
90
|
+
* tax === total` always holds. DISPLAY IT — never re-add or prorate it.
|
|
91
|
+
*/
|
|
92
|
+
export interface RefundPlanAmounts {
|
|
93
|
+
/** Before discounts, tax excluded. For a tip row, the tip itself. */
|
|
94
|
+
subtotal: number;
|
|
95
|
+
/** Per-item discounts on the refunded quantity (positive). */
|
|
96
|
+
itemDiscount: number;
|
|
97
|
+
/** Cart discount share on the refunded quantity (positive). */
|
|
98
|
+
cartDiscount: number;
|
|
99
|
+
/** Tax on the refunded quantity. Zero for a tip. */
|
|
100
|
+
tax: number;
|
|
101
|
+
/** `tax` per rate — the price breakdown's tax lines. */
|
|
102
|
+
taxes: RefundPlanTaxLine[];
|
|
103
|
+
/** What the row refunds. */
|
|
104
|
+
total: number;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* One refundable row of the order — a line item, custom sale, cart fee or tip —
|
|
108
|
+
* served ready to render. Rows with nothing left to refund are not listed.
|
|
109
|
+
*/
|
|
110
|
+
export interface RefundPlanRow {
|
|
111
|
+
type: RefundPlanRowType;
|
|
112
|
+
/** The key `items[].itemKey` takes, for this call and for `processPartialRefund`. */
|
|
113
|
+
itemKey: string;
|
|
114
|
+
/** Line or fee name; `Tip` for a tip. */
|
|
115
|
+
label: string;
|
|
116
|
+
/** Product lines only, when the order recorded them. */
|
|
117
|
+
sku?: string;
|
|
118
|
+
attributes?: string;
|
|
119
|
+
/** Tip rows only: the tender that took the tip (`card`, `cash`, …). */
|
|
120
|
+
paymentType?: string;
|
|
121
|
+
/** Quantity originally sold. Fees and tips are `1` — all or nothing. */
|
|
122
|
+
quantity: number;
|
|
123
|
+
/** Quantity still refundable — the stepper's max. */
|
|
124
|
+
refundableQuantity: number;
|
|
125
|
+
/** The money for refunding ALL of `refundableQuantity`. */
|
|
126
|
+
amounts: RefundPlanAmounts;
|
|
127
|
+
}
|
|
128
|
+
/** One selected row and what refunding the selected quantity of it moves. */
|
|
129
|
+
export interface RefundPlanSelectedRow {
|
|
130
|
+
type: RefundPlanRowType;
|
|
131
|
+
itemKey: string;
|
|
132
|
+
quantity: number;
|
|
133
|
+
amounts: RefundPlanAmounts;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* The selection's goods value broken down for display. Minor units;
|
|
137
|
+
* `items − discounts + fees + tax + tip === total === allocation.itemTotal`.
|
|
138
|
+
*/
|
|
139
|
+
export interface RefundPlanTotals {
|
|
140
|
+
/** Σ selected line subtotals (before discounts, tax excluded). */
|
|
141
|
+
items: number;
|
|
142
|
+
/** Σ item + cart discounts on the selected lines (positive). */
|
|
143
|
+
discounts: number;
|
|
144
|
+
/** Σ selected cart fees, tax excluded. */
|
|
145
|
+
fees: number;
|
|
146
|
+
tax: number;
|
|
147
|
+
tip: number;
|
|
148
|
+
total: number;
|
|
149
|
+
}
|
|
150
|
+
export interface RefundPlanBreakdown {
|
|
151
|
+
rows: RefundPlanSelectedRow[];
|
|
152
|
+
totals: RefundPlanTotals;
|
|
153
|
+
}
|
|
82
154
|
/**
|
|
83
155
|
* The engine's own allocation of the CURRENT refund selection across the
|
|
84
156
|
* order's captures — what a flow renders and submits instead of computing a
|
|
@@ -106,11 +178,25 @@ export interface RefundPlanAllocation {
|
|
|
106
178
|
rounding: number;
|
|
107
179
|
/** One leg per source that receives money. Submit as `legs`, unchanged. */
|
|
108
180
|
legs: RefundPlanLeg[];
|
|
181
|
+
/**
|
|
182
|
+
* What the selection refunds, per row and in total — the numbers a refund
|
|
183
|
+
* dialog's rows and footer render. Optional so an older runtime still
|
|
184
|
+
* type-checks; kaching 1.12.1+ always sends it.
|
|
185
|
+
*/
|
|
186
|
+
breakdown?: RefundPlanBreakdown;
|
|
109
187
|
}
|
|
110
188
|
export interface GetRefundPlanResponse {
|
|
111
189
|
success: boolean;
|
|
112
190
|
orderId: string;
|
|
113
191
|
sources: RefundPlanSource[];
|
|
192
|
+
/**
|
|
193
|
+
* Every row still refundable on the order, with the money for refunding all
|
|
194
|
+
* of it — what a refund dialog lists before anything is selected. Replaces
|
|
195
|
+
* reading line totals off the order and deciding whether they include tax.
|
|
196
|
+
* Optional so an older runtime still type-checks; kaching 1.12.1+ always
|
|
197
|
+
* sends it.
|
|
198
|
+
*/
|
|
199
|
+
rows?: RefundPlanRow[];
|
|
114
200
|
/**
|
|
115
201
|
* Ready-to-submit allocation of the current selection. Present only when a
|
|
116
202
|
* refund selection exists on the active order. See {@link RefundPlanAllocation}.
|
|
@@ -18,14 +18,6 @@ export interface GetRemainingRefundableQuantitiesResponse {
|
|
|
18
18
|
* 0/1 semantics: `1` = still refundable, `0` = already refunded.
|
|
19
19
|
*/
|
|
20
20
|
tips: Record<string, number>;
|
|
21
|
-
/**
|
|
22
|
-
* Remaining refundable bookings, keyed by `order.reservations[].internalId` —
|
|
23
|
-
* the same key `processPartialRefund` takes for `type: 'reservation'` items.
|
|
24
|
-
* A service is sold as a reservation rather than a line item, so without this
|
|
25
|
-
* map a refund screen can show everything on the order EXCEPT the appointment.
|
|
26
|
-
* 0/1 semantics: `1` = still refundable, `0` = already refunded.
|
|
27
|
-
*/
|
|
28
|
-
reservations: Record<string, number>;
|
|
29
21
|
timestamp: string;
|
|
30
22
|
}
|
|
31
23
|
export type GetRemainingRefundableQuantities = (params?: GetRemainingRefundableQuantitiesParams) => Promise<GetRemainingRefundableQuantitiesResponse>;
|
|
@@ -131,10 +131,10 @@ export interface ProcessPartialRefundParams {
|
|
|
131
131
|
};
|
|
132
132
|
/** Optional items to refund. */
|
|
133
133
|
items?: {
|
|
134
|
-
/** internalId or variantId
|
|
134
|
+
/** internalId or variantId or customSaleId. */
|
|
135
135
|
itemKey: string;
|
|
136
136
|
quantity: number;
|
|
137
|
-
type?: 'product' | 'customSale' | 'fee' | 'tip'
|
|
137
|
+
type?: 'product' | 'customSale' | 'fee' | 'tip';
|
|
138
138
|
/**
|
|
139
139
|
* Per-item stock disposition for a refunded **product** line — the
|
|
140
140
|
* headless equivalent of the old refund popup's per-row restock/damaged
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Set product modifier selections action
|
|
3
|
+
* Calls the setProductModifierSelections action on the parent window
|
|
4
|
+
*/
|
|
5
|
+
import { commandFrameClient } from "../../client";
|
|
6
|
+
export const setProductModifierSelections = async (params) => {
|
|
7
|
+
return await commandFrameClient.call("setProductModifierSelections", params);
|
|
8
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { MOCK_CART, MOCK_PRODUCTS, buildCartLineModifiers, buildModifierRows } from "../../demo/database";
|
|
2
|
+
export const mockSetProductModifierSelections = async (params) => {
|
|
3
|
+
console.log("[Mock] setProductModifierSelections called", params);
|
|
4
|
+
if (!params?.selections) {
|
|
5
|
+
return {
|
|
6
|
+
success: false,
|
|
7
|
+
reason: "selections is required (pass [] to clear)",
|
|
8
|
+
internalId: params?.internalId,
|
|
9
|
+
selections: [],
|
|
10
|
+
rows: [],
|
|
11
|
+
modifiersTotal: 0,
|
|
12
|
+
timestamp: new Date().toISOString()
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
// Write the selections onto the mock cart line (last line when no internalId).
|
|
16
|
+
// The mock does not re-run rule VALIDATION — the real host does — but it does reprice,
|
|
17
|
+
// because a replacement that left the old rows and the old money behind would make the
|
|
18
|
+
// cart disagree with itself the moment a cashier edits a line.
|
|
19
|
+
const line = params.internalId
|
|
20
|
+
? MOCK_CART.products.find((p) => p.internalId === params.internalId)
|
|
21
|
+
: MOCK_CART.products[MOCK_CART.products.length - 1];
|
|
22
|
+
if (!line) {
|
|
23
|
+
return {
|
|
24
|
+
success: false,
|
|
25
|
+
reason: "No matching cart line",
|
|
26
|
+
internalId: params.internalId,
|
|
27
|
+
selections: [],
|
|
28
|
+
rows: [],
|
|
29
|
+
modifiersTotal: 0,
|
|
30
|
+
timestamp: new Date().toISOString()
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
// Full replacement — passing [] clears the rows. The line drops the product's resolved
|
|
34
|
+
// stack when it is built, so the menu is re-read from the catalogue to price against.
|
|
35
|
+
// Cart totals are not touched, for the same reason addProductToCart does not move them
|
|
36
|
+
// for modifiers: this mock does not accumulate per-line money.
|
|
37
|
+
const source = MOCK_PRODUCTS.find((product) => product._id === line.id);
|
|
38
|
+
line.modifierSelections = params.selections;
|
|
39
|
+
line.modifiers = buildCartLineModifiers(source?.modifiers, params.selections, source?.taxTable);
|
|
40
|
+
const { rows, modifiersTotal } = buildModifierRows(line.modifiers, line.quantity);
|
|
41
|
+
return {
|
|
42
|
+
success: true,
|
|
43
|
+
internalId: line.internalId,
|
|
44
|
+
selections: params.selections,
|
|
45
|
+
rows,
|
|
46
|
+
modifiersTotal,
|
|
47
|
+
timestamp: new Date().toISOString()
|
|
48
|
+
};
|
|
49
|
+
};
|