@final-commerce/command-frame 0.6.0-staging3.4 → 0.6.0-staging3.5

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.
@@ -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, ResolvedModifier, ResolvedModifierChoice, ModifierSelectionType, ModifierSelection, ModifierChoiceSelection, CartLineModifier, OrderLineItemModifier, CustomerNote, Attribute, AttributeOption, Category, Transaction, ActiveSplitPayment } from '@final-commerce/common/pos-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, 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>;
@@ -29,6 +29,8 @@ export type CFModifierSelection = ModifierSelection;
29
29
  export type CFModifierChoiceSelection = ModifierChoiceSelection;
30
30
  /** One selected choice PRICED onto a cart line — `CFActiveProduct.modifiers` (not the resolved stack). */
31
31
  export type CFCartLineModifier = CartLineModifier;
32
+ /** A priced, line-extended modifier row: "Toppings - Avocado" x2, `amount` 1000. */
33
+ export type CFProdModifierBreakdown = ProdModifierBreakdown;
32
34
  export type CFActiveProduct = ActiveProduct;
33
35
  export type CFCustomer = ActiveCustomer;
34
36
  export type CFActiveCustomer = ActiveCustomer;
@@ -1,4 +1,4 @@
1
- import { MOCK_CART, MOCK_PRODUCTS, buildCartLineModifiers, 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);
@@ -76,6 +76,9 @@ export const mockAddProductToCart = async (params) => {
76
76
  MOCK_CART.remainingBalance = MOCK_CART.total;
77
77
  // Publish cart event to simulate real behavior
78
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);
79
82
  return {
80
83
  success: true,
81
84
  productId: activeProduct.id,
@@ -83,6 +86,8 @@ export const mockAddProductToCart = async (params) => {
83
86
  internalId: activeProduct.internalId,
84
87
  name: activeProduct.name,
85
88
  quantity: quantity,
89
+ rows,
90
+ modifiersTotal,
86
91
  timestamp: new Date().toISOString(),
87
92
  };
88
93
  };
@@ -1,11 +1,7 @@
1
1
  import type { AddProductDiscountParams } from '../add-product-discount/types';
2
2
  import type { AddProductFeeParams } from '../add-product-fee/types';
3
3
  import type { ModifierSelection } from '../get-product-modifier-selections/types';
4
- /**
5
- * One modifier answer passed at ring-time. Same shape as the
6
- * get-product-modifier-selections `ModifierSelection` (single source of truth).
7
- */
8
- export type AddProductToCartModifierParams = ModifierSelection;
4
+ import type { CFProdModifierBreakdown } from '../../CommonTypes';
9
5
  export interface AddProductToCartParams {
10
6
  /** ID of the variant to add. */
11
7
  variantId: string;
@@ -28,7 +24,7 @@ export interface AddProductToCartParams {
28
24
  /** Array of fees to apply immediately. */
29
25
  fees?: AddProductFeeParams[];
30
26
  /** Modifier selections to apply immediately. */
31
- modifiers?: AddProductToCartModifierParams[];
27
+ modifiers?: ModifierSelection[];
32
28
  /** Note or array of notes to add immediately. */
33
29
  notes?: string | string[];
34
30
  }
@@ -42,6 +38,10 @@ export interface AddProductToCartResponse {
42
38
  internalId: string;
43
39
  name: string;
44
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;
45
45
  timestamp: string;
46
46
  }
47
47
  export type AddProductToCart = (params?: AddProductToCartParams) => Promise<AddProductToCartResponse>;
@@ -1,4 +1,4 @@
1
- import { MOCK_CART } from "../../demo/database";
1
+ import { MOCK_CART, buildModifierRows } from "../../demo/database";
2
2
  export const mockGetProductModifierSelections = async (params) => {
3
3
  console.log("[Mock] getProductModifierSelections called", params);
4
4
  // Read the line's stored selections from the mock cart (last line when no internalId).
@@ -11,13 +11,20 @@ export const mockGetProductModifierSelections = async (params) => {
11
11
  reason: "No matching cart line",
12
12
  internalId: params?.internalId,
13
13
  selections: [],
14
+ rows: [],
15
+ modifiersTotal: 0,
14
16
  timestamp: new Date().toISOString()
15
17
  };
16
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);
17
22
  return {
18
23
  success: true,
19
24
  internalId: line.internalId,
20
25
  selections: line.modifierSelections ?? [],
26
+ rows,
27
+ modifiersTotal,
21
28
  timestamp: new Date().toISOString()
22
29
  };
23
30
  };
@@ -1,13 +1,8 @@
1
- /** One chosen choice: `quantity` is units PER LINE-ITEM UNIT (1 unless the control is a stepper). */
2
- export interface ModifierChoiceSelection {
3
- choiceId: string;
4
- quantity: number;
5
- }
6
- /** The cashier's answer to one modifier. */
7
- export interface ModifierSelection {
8
- modifierId: string;
9
- choices: ModifierChoiceSelection[];
10
- }
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;
11
6
  export interface GetProductModifierSelectionsParams {
12
7
  /** The cart line to read. Defaults to the active product's line. */
13
8
  internalId?: string;
@@ -17,14 +12,12 @@ export interface GetProductModifierSelectionsResponse {
17
12
  /** Set when the read failed (e.g. no such line, no active product). */
18
13
  reason?: string;
19
14
  internalId?: string;
20
- /**
21
- * The line's current modifier selections. Supplied at creation via
22
- * `addProductToCart({ modifiers })`; edit them with
23
- * `setProductModifierSelections` (full replacement, re-validated).
24
- * Modifier DEFINITIONS remain read-only — they flow one-way from
25
- * station-sync into the till.
26
- */
15
+ /** Raw ids — round-trips back into the setter. No names, no money: use `rows` to display. */
27
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;
28
21
  timestamp: string;
29
22
  }
30
23
  export type GetProductModifierSelections = (params?: GetProductModifierSelectionsParams) => Promise<GetProductModifierSelectionsResponse>;
@@ -1,2 +1 @@
1
- // Get Product Modifier Selections Types
2
1
  export {};
@@ -1,4 +1,4 @@
1
- import { MOCK_CART, MOCK_PRODUCTS, buildCartLineModifiers } from "../../demo/database";
1
+ import { MOCK_CART, MOCK_PRODUCTS, buildCartLineModifiers, buildModifierRows } from "../../demo/database";
2
2
  export const mockSetProductModifierSelections = async (params) => {
3
3
  console.log("[Mock] setProductModifierSelections called", params);
4
4
  if (!params?.selections) {
@@ -7,6 +7,8 @@ export const mockSetProductModifierSelections = async (params) => {
7
7
  reason: "selections is required (pass [] to clear)",
8
8
  internalId: params?.internalId,
9
9
  selections: [],
10
+ rows: [],
11
+ modifiersTotal: 0,
10
12
  timestamp: new Date().toISOString()
11
13
  };
12
14
  }
@@ -23,6 +25,8 @@ export const mockSetProductModifierSelections = async (params) => {
23
25
  reason: "No matching cart line",
24
26
  internalId: params.internalId,
25
27
  selections: [],
28
+ rows: [],
29
+ modifiersTotal: 0,
26
30
  timestamp: new Date().toISOString()
27
31
  };
28
32
  }
@@ -33,10 +37,13 @@ export const mockSetProductModifierSelections = async (params) => {
33
37
  const source = MOCK_PRODUCTS.find((product) => product._id === line.id);
34
38
  line.modifierSelections = params.selections;
35
39
  line.modifiers = buildCartLineModifiers(source?.modifiers, params.selections, source?.taxTable);
40
+ const { rows, modifiersTotal } = buildModifierRows(line.modifiers, line.quantity);
36
41
  return {
37
42
  success: true,
38
43
  internalId: line.internalId,
39
44
  selections: params.selections,
45
+ rows,
46
+ modifiersTotal,
40
47
  timestamp: new Date().toISOString()
41
48
  };
42
49
  };
@@ -1,4 +1,5 @@
1
1
  import type { ModifierSelection } from "../get-product-modifier-selections/types";
2
+ import type { CFProdModifierBreakdown } from "../../CommonTypes";
2
3
  export interface SetProductModifierSelectionsParams {
3
4
  /** The cart line to edit. Defaults to the active product's line. */
4
5
  internalId?: string;
@@ -17,6 +18,10 @@ export interface SetProductModifierSelectionsResponse {
17
18
  internalId?: string;
18
19
  /** The selections now on the line (the new ones on success, the old ones on rejection). */
19
20
  selections: ModifierSelection[];
21
+ /** Display-ready — repaint from this instead of re-reading after an edit. */
22
+ rows: CFProdModifierBreakdown[];
23
+ /** Sum of `rows[].amount` for THIS line, in minor units. */
24
+ modifiersTotal: number;
20
25
  timestamp: string;
21
26
  }
22
27
  export type SetProductModifierSelections = (params?: SetProductModifierSelectionsParams) => Promise<SetProductModifierSelectionsResponse>;
@@ -2,7 +2,7 @@
2
2
  * Mock Database for Standalone/Demo Mode
3
3
  * Stores mock data that mimics the Render environment
4
4
  */
5
- import { CFActiveCompany, CFActiveUser, CFActiveStation, CFActiveOutlet, CFActiveOrder, CFCustomer, CFProduct, CFActiveCart, CFCategory, CFActiveProduct, CFSession, CFActiveRefundDetails, CFSmartGridLayout, CFCartLineModifier, CFModifierSelection, CFResolvedModifier } from '../CommonTypes';
5
+ import { CFActiveCompany, CFActiveUser, CFActiveStation, CFActiveOutlet, CFActiveOrder, CFCustomer, CFProduct, CFActiveCart, CFCategory, CFActiveProduct, CFSession, CFActiveRefundDetails, CFSmartGridLayout, CFCartLineModifier, CFProdModifierBreakdown, CFModifierSelection, CFResolvedModifier } from '../CommonTypes';
6
6
  export * from './mocks';
7
7
  /** Replace mock catalog / context data in place (same array references mock handlers use). */
8
8
  export interface MockDatabaseConfig {
@@ -104,4 +104,9 @@ export declare const applyMockPayment: (amount: number, paymentType: string, pro
104
104
  * rewrites an open cart. No `total`: a cart row is per line UNIT, and the host extends it.
105
105
  * Rule validation (required/min/max) is deliberately not mocked; the real host owns it.
106
106
  */
107
+ /** Extend a line's priced modifier rows by the line quantity. Mirrors kaching's `buildProdModifiers`. */
108
+ export declare const buildModifierRows: (modifiers: CFCartLineModifier[] | undefined, lineQuantity: number | undefined) => {
109
+ rows: CFProdModifierBreakdown[];
110
+ modifiersTotal: number;
111
+ };
107
112
  export declare const buildCartLineModifiers: (menu: CFResolvedModifier[] | undefined, selections: CFModifierSelection[] | undefined, productTaxTableId?: string) => CFCartLineModifier[];
@@ -931,6 +931,26 @@ export const applyMockPayment = (amount, paymentType, processor = 'cash') => {
931
931
  * rewrites an open cart. No `total`: a cart row is per line UNIT, and the host extends it.
932
932
  * Rule validation (required/min/max) is deliberately not mocked; the real host owns it.
933
933
  */
934
+ /** Extend a line's priced modifier rows by the line quantity. Mirrors kaching's `buildProdModifiers`. */
935
+ export const buildModifierRows = (modifiers, lineQuantity) => {
936
+ const quantity = lineQuantity ?? 1;
937
+ const rows = (modifiers ?? []).map((modifier) => ({
938
+ modifierId: modifier.modifierId,
939
+ modifierName: modifier.modifierName,
940
+ choiceId: modifier.choiceId,
941
+ choiceName: modifier.choiceName,
942
+ label: modifier.label ?? `${modifier.modifierName} - ${modifier.choiceName}`,
943
+ unitPrice: modifier.unitPrice,
944
+ quantity: modifier.quantity,
945
+ // extendPrice, not a raw multiply: a fractional line quantity (1.5 kg) must not
946
+ // leave fractional minor units on the row.
947
+ amount: extendPrice(modifier.unitPrice * modifier.quantity, quantity),
948
+ tax: 0,
949
+ ...(modifier.taxTableId ? { taxTableId: modifier.taxTableId } : {}),
950
+ ...(modifier.taxRateId ? { taxRateId: modifier.taxRateId } : {})
951
+ }));
952
+ return { rows, modifiersTotal: rows.reduce((sum, row) => sum + row.amount, 0) };
953
+ };
934
954
  export const buildCartLineModifiers = (menu, selections, productTaxTableId) => {
935
955
  if (!menu?.length || !selections?.length)
936
956
  return [];
package/dist/index.d.ts CHANGED
@@ -151,7 +151,7 @@ export type { CheckPermission, CheckPermissionParams, CheckPermissionResponse }
151
151
  export type { InitiateRefund, InitiateRefundParams, InitiateRefundResponse } from './actions/initiate-refund/types';
152
152
  export type { GetCurrentCart, GetCurrentCartResponse } from './actions/get-current-cart/types';
153
153
  export type { AddProductDiscount, AddProductDiscountParams, AddProductDiscountResponse, } from './actions/add-product-discount/types';
154
- export type { AddProductToCart, AddProductToCartParams, AddProductToCartModifierParams, AddProductToCartResponse, } from './actions/add-product-to-cart/types';
154
+ export type { AddProductToCart, AddProductToCartParams, AddProductToCartResponse, } from './actions/add-product-to-cart/types';
155
155
  export type { RemoveProductFromCart, RemoveProductFromCartParams, RemoveProductFromCartResponse, } from './actions/remove-product-from-cart/types';
156
156
  export type { UpdateCartItemQuantity, UpdateCartItemQuantityParams, UpdateCartItemQuantityResponse, } from './actions/update-cart-item-quantity/types';
157
157
  export type { AddCartDiscount, AddCartDiscountParams, AddCartDiscountResponse, } from './actions/add-cart-discount/types';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@final-commerce/command-frame",
3
- "version": "0.6.0-staging3.4",
3
+ "version": "0.6.0-staging3.5",
4
4
  "description": "Commands Frame library",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",