@final-commerce/command-frame 0.7.0-staging.9 → 0.7.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.
Files changed (83) hide show
  1. package/dist/CommonTypes.d.ts +14 -26
  2. package/dist/actions/add-product-to-cart/mock.js +18 -1
  3. package/dist/actions/add-product-to-cart/types.d.ts +10 -0
  4. package/dist/actions/get-context/mock.js +0 -4
  5. package/dist/actions/get-product-modifier-selections/action.d.ts +6 -0
  6. package/dist/actions/get-product-modifier-selections/action.js +8 -0
  7. package/dist/actions/get-product-modifier-selections/mock.d.ts +2 -0
  8. package/dist/actions/get-product-modifier-selections/mock.js +30 -0
  9. package/dist/actions/get-product-modifier-selections/types.d.ts +23 -0
  10. package/dist/actions/get-refund-plan/types.d.ts +2 -5
  11. package/dist/actions/get-remaining-refundable-quantities/mock.js +0 -1
  12. package/dist/actions/get-remaining-refundable-quantities/types.d.ts +0 -8
  13. package/dist/actions/process-partial-refund/types.d.ts +2 -2
  14. package/dist/actions/set-product-modifier-selections/action.d.ts +6 -0
  15. package/dist/actions/set-product-modifier-selections/action.js +8 -0
  16. package/dist/actions/set-product-modifier-selections/mock.d.ts +2 -0
  17. package/dist/actions/set-product-modifier-selections/mock.js +49 -0
  18. package/dist/actions/set-product-modifier-selections/types.d.ts +27 -0
  19. package/dist/demo/database.d.ts +24 -12
  20. package/dist/demo/database.js +61 -121
  21. package/dist/index.d.ts +4 -16
  22. package/dist/index.js +4 -15
  23. package/dist/projects/render/mocks.js +4 -14
  24. package/dist/projects/render/types.d.ts +3 -8
  25. package/dist/pubsub/topics/cart/product-modifiers-updated/types.d.ts +16 -0
  26. package/dist/pubsub/topics/cart/types.d.ts +4 -2
  27. package/dist/pubsub/topics/cart/types.js +1 -0
  28. package/dist/pubsub/topics/index.d.ts +16 -17
  29. package/dist/pubsub/topics/index.js +16 -17
  30. package/dist/pubsub/topics/types.d.ts +0 -2
  31. package/package.json +2 -2
  32. package/dist/actions/add-booking-to-cart/action.d.ts +0 -6
  33. package/dist/actions/add-booking-to-cart/action.js +0 -8
  34. package/dist/actions/add-booking-to-cart/mock.d.ts +0 -2
  35. package/dist/actions/add-booking-to-cart/mock.js +0 -42
  36. package/dist/actions/add-booking-to-cart/types.d.ts +0 -23
  37. package/dist/actions/cancel-booking/action.d.ts +0 -6
  38. package/dist/actions/cancel-booking/action.js +0 -8
  39. package/dist/actions/cancel-booking/mock.d.ts +0 -2
  40. package/dist/actions/cancel-booking/mock.js +0 -10
  41. package/dist/actions/cancel-booking/types.d.ts +0 -10
  42. package/dist/actions/get-booking-availability/action.d.ts +0 -6
  43. package/dist/actions/get-booking-availability/action.js +0 -8
  44. package/dist/actions/get-booking-availability/mock.d.ts +0 -2
  45. package/dist/actions/get-booking-availability/mock.js +0 -6
  46. package/dist/actions/get-booking-availability/types.d.ts +0 -18
  47. package/dist/actions/get-booking-resources/action.d.ts +0 -6
  48. package/dist/actions/get-booking-resources/action.js +0 -8
  49. package/dist/actions/get-booking-resources/mock.d.ts +0 -2
  50. package/dist/actions/get-booking-resources/mock.js +0 -7
  51. package/dist/actions/get-booking-resources/types.d.ts +0 -12
  52. package/dist/actions/get-booking-resources/types.js +0 -1
  53. package/dist/actions/get-bookings/action.d.ts +0 -6
  54. package/dist/actions/get-bookings/action.js +0 -8
  55. package/dist/actions/get-bookings/mock.d.ts +0 -2
  56. package/dist/actions/get-bookings/mock.js +0 -20
  57. package/dist/actions/get-bookings/types.d.ts +0 -18
  58. package/dist/actions/get-bookings/types.js +0 -1
  59. package/dist/actions/hold-booking/action.d.ts +0 -6
  60. package/dist/actions/hold-booking/action.js +0 -8
  61. package/dist/actions/hold-booking/mock.d.ts +0 -2
  62. package/dist/actions/hold-booking/mock.js +0 -33
  63. package/dist/actions/hold-booking/types.d.ts +0 -24
  64. package/dist/actions/hold-booking/types.js +0 -1
  65. package/dist/actions/remove-booking-from-cart/action.d.ts +0 -6
  66. package/dist/actions/remove-booking-from-cart/action.js +0 -8
  67. package/dist/actions/remove-booking-from-cart/mock.d.ts +0 -2
  68. package/dist/actions/remove-booking-from-cart/mock.js +0 -26
  69. package/dist/actions/remove-booking-from-cart/types.d.ts +0 -12
  70. package/dist/actions/remove-booking-from-cart/types.js +0 -1
  71. package/dist/pubsub/topics/bookings/booking-created/types.d.ts +0 -6
  72. package/dist/pubsub/topics/bookings/booking-created/types.js +0 -1
  73. package/dist/pubsub/topics/bookings/booking-setup-changed/types.d.ts +0 -15
  74. package/dist/pubsub/topics/bookings/booking-setup-changed/types.js +0 -1
  75. package/dist/pubsub/topics/bookings/booking-updated/types.d.ts +0 -6
  76. package/dist/pubsub/topics/bookings/booking-updated/types.js +0 -1
  77. package/dist/pubsub/topics/bookings/index.d.ts +0 -3
  78. package/dist/pubsub/topics/bookings/index.js +0 -27
  79. package/dist/pubsub/topics/bookings/types.d.ts +0 -8
  80. package/dist/pubsub/topics/bookings/types.js +0 -3
  81. /package/dist/actions/{add-booking-to-cart → get-product-modifier-selections}/types.js +0 -0
  82. /package/dist/actions/{cancel-booking → set-product-modifier-selections}/types.js +0 -0
  83. /package/dist/{actions/get-booking-availability → pubsub/topics/cart/product-modifiers-updated}/types.js +0 -0
@@ -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, CustomerNote, Attribute, AttributeOption, Category, Transaction, ActiveSplitPayment, BookingAvailability, BookingSlot, BookingSlotResource, BookingResource, Booking, CartReservation, OrderReservation } 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>;
@@ -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
- export type CFBookingAvailability = BookingAvailability;
22
- export type CFBookingSlot = BookingSlot;
23
- export type CFBookingSlotResource = BookingSlotResource;
24
- export type CFBookingResource = BookingResource;
25
- export type CFBooking = Booking;
26
- /** A service sitting in the cart, and the same sale once it is on an order. A flow that draws a
27
- * cart or a receipt needs both by name — reaching into the package's dist for them is not an
28
- * interface. */
29
- export type CFCartReservation = CartReservation;
30
- export type CFOrderReservation = OrderReservation;
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,2 @@
1
+ import { GetProductModifierSelections } from "./types";
2
+ export declare const mockGetProductModifierSelections: GetProductModifierSelections;
@@ -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>;
@@ -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' | 'reservation';
25
+ type?: 'product' | 'customSale' | 'fee' | 'tip';
29
26
  }[];
30
27
  }
31
28
  export interface RefundPlanSource {
@@ -6,7 +6,6 @@ export const mockGetRemainingRefundableQuantities = async (_params) => {
6
6
  customSales: {},
7
7
  cartFees: {},
8
8
  tips: {},
9
- reservations: {},
10
9
  timestamp: new Date().toISOString(),
11
10
  };
12
11
  };
@@ -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, customSaleId, or a booking's own internalId. */
134
+ /** internalId or variantId or customSaleId. */
135
135
  itemKey: string;
136
136
  quantity: number;
137
- type?: 'product' | 'customSale' | 'fee' | 'tip' | 'reservation';
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,6 @@
1
+ /**
2
+ * Set product modifier selections action
3
+ * Calls the setProductModifierSelections action on the parent window
4
+ */
5
+ import type { SetProductModifierSelections } from "./types";
6
+ export declare const setProductModifierSelections: SetProductModifierSelections;
@@ -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,2 @@
1
+ import { SetProductModifierSelections } from "./types";
2
+ export declare const mockSetProductModifierSelections: SetProductModifierSelections;
@@ -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
+ };
@@ -0,0 +1,27 @@
1
+ import type { ModifierSelection } from "../get-product-modifier-selections/types";
2
+ import type { CFProdModifierBreakdown } from "../../CommonTypes";
3
+ export interface SetProductModifierSelectionsParams {
4
+ /** The cart line to edit. Defaults to the active product's line. */
5
+ internalId?: string;
6
+ /**
7
+ * The line's COMPLETE new answers — replaces every existing selection on the
8
+ * line (send [] to clear all optional modifiers). Validated against the
9
+ * product's category-modifier rules exactly like addProductToCart({ modifiers }):
10
+ * a selection that breaks a rule rejects the edit and the line is unchanged.
11
+ */
12
+ selections: ModifierSelection[];
13
+ }
14
+ export interface SetProductModifierSelectionsResponse {
15
+ success: boolean;
16
+ /** Set when the edit was rejected (rule violation) — the line is unchanged. */
17
+ reason?: string;
18
+ internalId?: string;
19
+ /** The selections now on the line (the new ones on success, the old ones on rejection). */
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;
25
+ timestamp: string;
26
+ }
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, CFBooking, CFBookingAvailability, CFBookingResource } 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 {
@@ -15,10 +15,6 @@ export interface MockDatabaseConfig {
15
15
  products?: CFProduct[];
16
16
  orders?: CFActiveOrder[];
17
17
  parkedOrders?: CFActiveOrder[];
18
- /** Who is scarce: the staff, rooms or machines the dataset's services are booked against. */
19
- bookingResources?: CFBookingResource[];
20
- /** Windows already taken when the dataset loads, so a calendar does not open empty. */
21
- bookings?: CFBooking[];
22
18
  }
23
19
  export declare const MOCK_COMPANY: CFActiveCompany;
24
20
  export declare const MOCK_OUTLET_MAIN: CFActiveOutlet;
@@ -63,7 +59,6 @@ export declare const MOCK_STATIONS: import("@final-commerce/common/pos-types").A
63
59
  export declare const MOCK_OUTLETS: import("@final-commerce/common/pos-types").ActiveOutlet[];
64
60
  export declare const MOCK_CUSTOMERS: import("@final-commerce/common/pos-types").ActiveCustomer[];
65
61
  export declare const MOCK_CATEGORIES: import("@final-commerce/common/pos-types").Category[];
66
- export declare const MOCK_PRODUCT_HAIRCUT: CFProduct;
67
62
  export declare const MOCK_PRODUCTS: import("@final-commerce/common/pos-types").FullProduct[];
68
63
  export declare const MOCK_ORDERS: import("@final-commerce/common/pos-types").ActiveOrder[];
69
64
  export declare const MOCK_PARKED_ORDERS: CFActiveOrder[];
@@ -92,9 +87,26 @@ export declare const createOrderFromCart: (paymentType: string, amount: number,
92
87
  * open with `amountToBeCharged` reset to what's left, and returns null.
93
88
  */
94
89
  export declare const applyMockPayment: (amount: number, paymentType: string, processor?: string) => CFActiveOrder | null;
95
- export declare const MOCK_BOOKING_RULES_ID = "rule_salon_30";
96
- export declare const MOCK_BOOKING_RESOURCES: CFBookingResource[];
97
- export declare const MOCK_BOOKINGS: CFBooking[];
98
- /** Live = confirmed, or held and not yet expired. An expired hold occupies nothing. */
99
- export declare const mockLiveBookings: () => CFBooking[];
100
- export declare const mockBookingAvailability: (productId: string, from: Date, to: Date, resourceId?: string) => CFBookingAvailability;
90
+ /**
91
+ * Price the cashier's raw modifier answers into the rows a CART line carries (FT-0010).
92
+ *
93
+ * Here rather than in an action folder because two mocks need it — addProductToCart and
94
+ * setProductModifierSelections — and this module is already where the shared mock helpers
95
+ * live (`safeSerialize`, `resetMockCart`, `mockPublishEvent`).
96
+ *
97
+ * Note the two shapes that share the name `modifiers` and are NOT interchangeable:
98
+ * `FullProduct.modifiers` is `ResolvedModifier[]` (the menu — question + every choice),
99
+ * `ActiveProduct.modifiers` is `CartLineModifier[]` (one row per CHOSEN choice). The mock
100
+ * builds a cart line by spreading the product, so the menu must be stripped off the line
101
+ * before anything reads it, or every surface renders the catalogue as if it were picked.
102
+ *
103
+ * Names and unitPrice are snapshotted, as the real host does — a later rename never
104
+ * rewrites an open cart. No `total`: a cart row is per line UNIT, and the host extends it.
105
+ * Rule validation (required/min/max) is deliberately not mocked; the real host owns it.
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
+ };
112
+ export declare const buildCartLineModifiers: (menu: CFResolvedModifier[] | undefined, selections: CFModifierSelection[] | undefined, productTaxTableId?: string) => CFCartLineModifier[];