@final-commerce/command-frame 0.1.74 → 0.1.75

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.
@@ -2,17 +2,20 @@ import { MOCK_CART, mockPublishEvent } from "../../demo/database";
2
2
  export const mockAddCartDiscount = async (params) => {
3
3
  console.log("[Mock] addCartDiscount called", params);
4
4
  if (params) {
5
+ // Mirror render: input is raw (50 = 50%, 5 = $5). Store percent as a
6
+ // fraction (0.5) and fixed as minor units (500), like the real handler.
7
+ const minorFactor = 10 ** (MOCK_CART.minorUnits ?? 2);
8
+ const value = params.isPercent ? params.amount / 100 : Math.round(params.amount * minorFactor);
5
9
  MOCK_CART.discount = {
6
- value: params.amount,
10
+ value,
7
11
  isPercent: params.isPercent,
8
12
  label: params.label
9
13
  };
10
- // Simple mock calc (not real logic)
11
14
  if (params.isPercent) {
12
15
  MOCK_CART.total = MOCK_CART.subtotal * (1 - params.amount / 100);
13
16
  }
14
17
  else {
15
- MOCK_CART.total = MOCK_CART.subtotal - params.amount;
18
+ MOCK_CART.total = MOCK_CART.subtotal - value;
16
19
  }
17
20
  MOCK_CART.amountToBeCharged = MOCK_CART.total;
18
21
  MOCK_CART.remainingBalance = MOCK_CART.total;
@@ -4,18 +4,18 @@ export const mockAddCartFee = async (params) => {
4
4
  if (params) {
5
5
  if (!MOCK_CART.customFee)
6
6
  MOCK_CART.customFee = [];
7
+ // Mirror render: input is raw (50 = 50%, 5 = $5). Store percent as a
8
+ // fraction (0.5) and fixed as minor units (500), like the real handler.
9
+ const minorFactor = 10 ** (MOCK_CART.minorUnits ?? 2);
10
+ const storedAmount = params.isPercent ? params.amount / 100 : Math.round(params.amount * minorFactor);
7
11
  MOCK_CART.customFee.push({
8
12
  label: params.label || "Fee",
9
- amount: params.amount,
13
+ amount: storedAmount,
10
14
  isPercent: params.isPercent || false,
11
15
  applyTaxes: params.applyTaxes || false,
12
16
  taxTableId: params.taxTableId
13
17
  });
14
- // Simple calc
15
- let feeAmount = params.amount;
16
- if (params.isPercent) {
17
- feeAmount = MOCK_CART.subtotal * (params.amount / 100);
18
- }
18
+ const feeAmount = params.isPercent ? MOCK_CART.subtotal * (params.amount / 100) : storedAmount;
19
19
  MOCK_CART.total += feeAmount;
20
20
  MOCK_CART.amountToBeCharged = MOCK_CART.total;
21
21
  MOCK_CART.remainingBalance = MOCK_CART.total;
@@ -1,11 +1,14 @@
1
- import { MOCK_CART } from "../../demo/database";
1
+ import { MOCK_CART, mockPublishEvent } from "../../demo/database";
2
2
  export const mockAddCustomSale = async (params) => {
3
3
  console.log("[Mock] addCustomSale called", params);
4
4
  if (!params)
5
5
  throw new Error("Params required");
6
6
  // Simple mock ID generation
7
7
  const mockId = 'sale_' + Math.random().toString(36).substr(2, 9);
8
- const price = Number(params.price);
8
+ // Mirror render: the flow sends raw dollars ($4); render does toMinorUnits.
9
+ // MOCK_CART tracks minor units, so convert here too.
10
+ const minorFactor = 10 ** (MOCK_CART.minorUnits ?? 2);
11
+ const price = Math.round(Number(params.price) * minorFactor);
9
12
  const quantity = 1; // Default to 1 for custom sale usually
10
13
  const customSale = {
11
14
  id: mockId,
@@ -24,6 +27,8 @@ export const mockAddCustomSale = async (params) => {
24
27
  MOCK_CART.total += price * quantity;
25
28
  MOCK_CART.amountToBeCharged = MOCK_CART.total;
26
29
  MOCK_CART.remainingBalance = MOCK_CART.total;
30
+ // Publish custom-sale-added event so cart subscribers refresh
31
+ mockPublishEvent('cart', 'custom-sale-added', { customSale });
27
32
  return {
28
33
  success: true,
29
34
  customSaleId: mockId,
@@ -1,4 +1,4 @@
1
- import { MOCK_CART } from "../../demo/database";
1
+ import { MOCK_CART, mockPublishEvent } from "../../demo/database";
2
2
  export const mockAddOrderNote = async (params) => {
3
3
  console.log("[Mock] addOrderNote called", params);
4
4
  // In Render, AddOrderNote usually adds a note to the active cart if it's not checked out yet.
@@ -9,6 +9,8 @@ export const mockAddOrderNote = async (params) => {
9
9
  else {
10
10
  MOCK_CART.orderNotes = params.note;
11
11
  }
12
+ // Publish order-note-added event so cart subscribers refresh.
13
+ mockPublishEvent("cart", "order-note-added", { note: params.note });
12
14
  }
13
15
  return {
14
16
  success: true,
@@ -1,4 +1,4 @@
1
- import { MOCK_CART } from "../../demo/database";
1
+ import { MOCK_CART, mockPublishEvent } from "../../demo/database";
2
2
  export const mockAddProductDiscount = async (params) => {
3
3
  console.log("[Mock] addProductDiscount called", params);
4
4
  if (params && (params.amount > 0 || params.amount < 0)) { // Allow 0 to clear discount if logic permits
@@ -10,13 +10,20 @@ export const mockAddProductDiscount = async (params) => {
10
10
  item = MOCK_CART.products[MOCK_CART.products.length - 1];
11
11
  }
12
12
  if (item) {
13
+ // Mirror render's product handler: it stores `amount` AS-IS — the flow
14
+ // already sends a fraction (0.5 = 50%) for percent and minor units (500 = $5)
15
+ // for fixed. tax.ts then uses value as the fraction / minor amount directly.
13
16
  item.discount = {
14
17
  value: params.amount,
15
18
  isPercent: params.isPercent || false,
16
19
  label: params.label
17
20
  };
18
- // Recalculate cart totals (simplified)
19
- // Ideally, this should trigger a full recalculation function
21
+ const linePrice = (item.price || 0) * (item.quantity || 1);
22
+ const discountMinor = params.isPercent ? Math.round(linePrice * params.amount) : params.amount;
23
+ MOCK_CART.total = Math.max(0, MOCK_CART.total - discountMinor);
24
+ MOCK_CART.amountToBeCharged = MOCK_CART.total;
25
+ MOCK_CART.remainingBalance = MOCK_CART.total;
26
+ mockPublishEvent("cart", "product-discount-added", { internalId: params.internalId });
20
27
  }
21
28
  }
22
29
  return {
@@ -1,4 +1,4 @@
1
- import { MOCK_CART } from "../../demo/database";
1
+ import { MOCK_CART, mockPublishEvent } from "../../demo/database";
2
2
  export const mockAddProductFee = async (params) => {
3
3
  console.log("[Mock] addProductFee called", params);
4
4
  if (params) {
@@ -10,12 +10,21 @@ export const mockAddProductFee = async (params) => {
10
10
  item = MOCK_CART.products[MOCK_CART.products.length - 1];
11
11
  }
12
12
  if (item) {
13
+ // Mirror render's product handler: it stores `amount` AS-IS — the flow
14
+ // already sends a fraction (0.5 = 50%) for percent and minor units (500 = $5)
15
+ // for fixed. tax.ts then uses amount as the fraction / minor amount directly.
13
16
  item.fee = {
14
17
  label: params.label || "Fee",
15
18
  amount: params.amount,
16
19
  isPercent: params.isPercent || false,
17
20
  applyTaxes: params.applyTaxes || false
18
21
  };
22
+ const linePrice = (item.price || 0) * (item.quantity || 1);
23
+ const feeMinor = params.isPercent ? Math.round(linePrice * params.amount) : params.amount;
24
+ MOCK_CART.total += feeMinor;
25
+ MOCK_CART.amountToBeCharged = MOCK_CART.total;
26
+ MOCK_CART.remainingBalance = MOCK_CART.total;
27
+ mockPublishEvent("cart", "product-fee-added", { internalId: params.internalId });
19
28
  }
20
29
  }
21
30
  return {
@@ -1,14 +1,14 @@
1
- import { createOrderFromCart, MOCK_CART, mockPublishEvent } from "../../demo/database";
1
+ import { applyMockPayment, MOCK_CART } from "../../demo/database";
2
2
  export const mockCashPayment = async (params) => {
3
3
  console.log("[Mock] cashPayment called", params);
4
- // Default to true to match action behavior
4
+ // Default to true to match action behavior.
5
5
  const openChangeCalculator = params?.openChangeCalculator ?? true;
6
- let amount = params?.amount || MOCK_CART.total;
6
+ // Amount due for THIS tender (the queued amount-to-be-charged), in minor units.
7
+ const due = params?.amount ?? MOCK_CART.amountToBeCharged ?? MOCK_CART.total;
7
8
  if (openChangeCalculator) {
8
9
  try {
9
- const input = window.prompt(`Total Due: $${MOCK_CART.total.toFixed(2)}\nEnter amount tendered:`, amount.toString());
10
+ const input = window.prompt(`Amount due: $${(due / 100).toFixed(2)}\nEnter amount tendered:`, (due / 100).toFixed(2));
10
11
  if (input === null) {
11
- // User cancelled
12
12
  return {
13
13
  success: false,
14
14
  amount: 0,
@@ -18,28 +18,22 @@ export const mockCashPayment = async (params) => {
18
18
  timestamp: new Date().toISOString()
19
19
  };
20
20
  }
21
- const tendered = parseFloat(input);
21
+ const tendered = parseFloat(input); // dollars
22
22
  if (!isNaN(tendered)) {
23
- amount = tendered;
24
- const change = tendered - MOCK_CART.total;
25
- if (change >= 0) {
26
- window.alert(`Change Due: $${change.toFixed(2)}`);
27
- }
28
- else {
29
- window.alert(`Warning: Tendered amount is less than total. Short by: $${Math.abs(change).toFixed(2)}`);
30
- }
23
+ const change = tendered - due / 100;
24
+ window.alert(change >= 0
25
+ ? `Change Due: $${change.toFixed(2)}`
26
+ : `Warning: Tendered is short by: $${Math.abs(change).toFixed(2)}`);
31
27
  }
32
28
  }
33
29
  catch (e) {
34
30
  console.warn("Could not open prompt/alert (possibly in non-interactive environment)", e);
35
31
  }
36
32
  }
37
- const order = createOrderFromCart("cash", amount, "cash");
38
- // Publish payment-done event
39
- mockPublishEvent('payments', 'payment-done', { order });
33
+ const order = applyMockPayment(due, "cash", "cash");
40
34
  return {
41
35
  success: true,
42
- amount,
36
+ amount: due,
43
37
  openChangeCalculator,
44
38
  paymentType: "cash",
45
39
  order,
@@ -1,10 +1,12 @@
1
- import { MOCK_PARKED_ORDERS } from "../../demo/database";
1
+ import { MOCK_PARKED_ORDERS, mockPublishEvent } from "../../demo/database";
2
2
  export const mockDeleteParkedOrder = async (params) => {
3
3
  console.log("[Mock] deleteParkedOrder called", params);
4
4
  if (params?.orderId) {
5
5
  const index = MOCK_PARKED_ORDERS.findIndex(o => o._id === params.orderId);
6
6
  if (index !== -1) {
7
7
  MOCK_PARKED_ORDERS.splice(index, 1);
8
+ // Refresh order lists (the parked-order modal reads getOrders).
9
+ mockPublishEvent("orders", "parked-order-deleted", { orderId: params.orderId });
8
10
  }
9
11
  }
10
12
  return {
@@ -24,7 +24,7 @@ export const mockGetContext = () => {
24
24
  thousandSeparator: ",",
25
25
  decimalSeparator: ".",
26
26
  user: null,
27
- company: null,
27
+ company: { _id: MOCK_COMPANY.id, name: MOCK_COMPANY.name, logo: MOCK_COMPANY.logo },
28
28
  station: null,
29
29
  outlet: null,
30
30
  timestamp: new Date().toISOString()
@@ -1,8 +1,8 @@
1
- import { MOCK_ORDERS, MOCK_USERS, MOCK_STATIONS, MOCK_OUTLETS, safeSerialize } from "../../demo/database";
1
+ import { MOCK_ORDERS, MOCK_PARKED_ORDERS, MOCK_USERS, MOCK_STATIONS, MOCK_OUTLETS, safeSerialize } from "../../demo/database";
2
2
  export const mockGetOrders = async (params) => {
3
3
  console.log("[Mock] getOrders called", params);
4
- // Start with a safe copy of mock orders
5
- let orders = safeSerialize(MOCK_ORDERS);
4
+ // Start with a safe copy of mock orders, including parked (status: "parked").
5
+ let orders = safeSerialize([...MOCK_ORDERS, ...MOCK_PARKED_ORDERS]);
6
6
  // Filter simulation
7
7
  if (params) {
8
8
  const { customerId, status, sessionId, searchValue, limit, offset, sortBy, sortDirection } = params;
@@ -1,4 +1,4 @@
1
- import { MOCK_PARKED_ORDERS, createOrderFromCart, MOCK_ORDERS } from "../../demo/database";
1
+ import { MOCK_PARKED_ORDERS, createOrderFromCart, MOCK_ORDERS, mockPublishEvent } from "../../demo/database";
2
2
  export const mockParkOrder = async () => {
3
3
  console.log("[Mock] parkOrder called");
4
4
  // Create a temporary order to capture cart state
@@ -13,6 +13,9 @@ export const mockParkOrder = async () => {
13
13
  MOCK_ORDERS.splice(foundIndex, 1);
14
14
  }
15
15
  MOCK_PARKED_ORDERS.push(tempOrder);
16
+ // Refresh order lists so the new parked order appears (createOrderFromCart
17
+ // already published the cart-created event that clears the cart).
18
+ mockPublishEvent("orders", "order-parked", { order: tempOrder });
16
19
  return {
17
20
  success: true,
18
21
  order: tempOrder,
@@ -1,17 +1,35 @@
1
- import { MOCK_ORDERS } from "../../demo/database";
1
+ import { MOCK_CART, mockPublishEvent } from "../../demo/database";
2
2
  export const mockPartialPayment = async (params) => {
3
3
  console.log("[Mock] partialPayment called", params);
4
4
  const openUI = params?.openUI ?? true;
5
5
  if (openUI) {
6
- // Simulate UI opening
6
+ // Split-payment UI is host-owned; nothing to simulate here.
7
7
  window.alert("Demo: Split Payment UI would open here.");
8
+ return {
9
+ success: true,
10
+ amount: params?.amount,
11
+ isPercent: params?.isPercent || false,
12
+ openUI,
13
+ order: null,
14
+ timestamp: new Date().toISOString()
15
+ };
8
16
  }
17
+ // Queue a partial amount as the next tender. The remaining balance is left
18
+ // untouched until the payment is actually taken (see applyMockPayment).
19
+ const remaining = MOCK_CART.remainingBalance ?? MOCK_CART.total;
20
+ const raw = params?.amount ?? 0;
21
+ // Mirror render: fixed amount is raw dollars (render does toMinorUnits), so
22
+ // convert to minor units here; percent is a percentage of the remaining total.
23
+ const minorFactor = 10 ** (MOCK_CART.minorUnits ?? 2);
24
+ const charge = params?.isPercent ? Math.round((remaining * raw) / 100) : Math.round(raw * minorFactor);
25
+ MOCK_CART.amountToBeCharged = Math.min(Math.max(0, charge), remaining);
26
+ mockPublishEvent("cart", "partial-payment-set", { amountToBeCharged: MOCK_CART.amountToBeCharged });
9
27
  return {
10
28
  success: true,
11
29
  amount: params?.amount,
12
30
  isPercent: params?.isPercent || false,
13
31
  openUI,
14
- order: openUI ? null : MOCK_ORDERS[0],
32
+ order: null,
15
33
  timestamp: new Date().toISOString()
16
34
  };
17
35
  };
@@ -0,0 +1,2 @@
1
+ import { RemoveCustomSale } from "./types";
2
+ export declare const mockRemoveCustomSale: RemoveCustomSale;
@@ -0,0 +1,29 @@
1
+ import { MOCK_CART, mockPublishEvent } from "../../demo/database";
2
+ export const mockRemoveCustomSale = (params) => {
3
+ console.log("[Mock] removeCustomSale called", params);
4
+ if (!params?.id) {
5
+ throw new Error("id is required");
6
+ }
7
+ const { id } = params;
8
+ const sales = MOCK_CART.customSales ?? [];
9
+ const index = sales.findIndex(s => s.id === id);
10
+ if (index === -1) {
11
+ throw new Error(`Custom sale with id ${id} not found`);
12
+ }
13
+ const sale = sales[index];
14
+ // Remove from cart
15
+ sales.splice(index, 1);
16
+ // Recalculate totals
17
+ const lineTotal = sale.price * sale.quantity;
18
+ MOCK_CART.subtotal -= lineTotal;
19
+ MOCK_CART.total -= lineTotal;
20
+ MOCK_CART.amountToBeCharged = MOCK_CART.total;
21
+ MOCK_CART.remainingBalance = MOCK_CART.total;
22
+ // Publish custom-sale-removed event so cart subscribers refresh
23
+ mockPublishEvent("cart", "custom-sale-removed", { customSale: sale, id });
24
+ return Promise.resolve({
25
+ success: true,
26
+ id,
27
+ timestamp: new Date().toISOString()
28
+ });
29
+ };
@@ -1,8 +1,10 @@
1
- import { MOCK_CART } from "../../demo/database";
1
+ import { MOCK_CART, mockPublishEvent } from "../../demo/database";
2
2
  export const mockRemoveCustomerFromCart = async () => {
3
3
  console.log("[Mock] removeCustomerFromCart called");
4
4
  // Actually remove the customer from the mock cart
5
5
  MOCK_CART.customer = null;
6
+ // Publish customer-removed event so cart subscribers refresh
7
+ mockPublishEvent('cart', 'customer-removed', {});
6
8
  return {
7
9
  success: true,
8
10
  timestamp: new Date().toISOString()
@@ -0,0 +1,2 @@
1
+ import { RemoveOrderNote } from "./types";
2
+ export declare const mockRemoveOrderNote: RemoveOrderNote;
@@ -0,0 +1,12 @@
1
+ import { MOCK_CART, mockPublishEvent } from "../../demo/database";
2
+ export const mockRemoveOrderNote = () => {
3
+ console.log("[Mock] removeOrderNote called");
4
+ // Clear the active cart's note.
5
+ MOCK_CART.orderNotes = undefined;
6
+ // Publish order-note-removed event so cart subscribers refresh.
7
+ mockPublishEvent("cart", "order-note-removed", {});
8
+ return Promise.resolve({
9
+ success: true,
10
+ timestamp: new Date().toISOString()
11
+ });
12
+ };
@@ -0,0 +1,2 @@
1
+ import { RemoveProductDiscount } from "./types";
2
+ export declare const mockRemoveProductDiscount: RemoveProductDiscount;
@@ -0,0 +1,17 @@
1
+ import { MOCK_CART, mockPublishEvent } from "../../demo/database";
2
+ export const mockRemoveProductDiscount = (params) => {
3
+ console.log("[Mock] removeProductDiscount called", params);
4
+ const item = params?.internalId
5
+ ? MOCK_CART.products.find(p => p.internalId === params.internalId)
6
+ : MOCK_CART.products[MOCK_CART.products.length - 1];
7
+ if (item) {
8
+ delete item.discount;
9
+ // Publish so cart subscribers refresh.
10
+ mockPublishEvent("cart", "product-discount-removed", { internalId: params?.internalId });
11
+ }
12
+ return Promise.resolve({
13
+ success: true,
14
+ internalId: params?.internalId,
15
+ timestamp: new Date().toISOString()
16
+ });
17
+ };
@@ -0,0 +1,2 @@
1
+ import { RemoveProductFee } from "./types";
2
+ export declare const mockRemoveProductFee: RemoveProductFee;
@@ -0,0 +1,17 @@
1
+ import { MOCK_CART, mockPublishEvent } from "../../demo/database";
2
+ export const mockRemoveProductFee = (params) => {
3
+ console.log("[Mock] removeProductFee called", params);
4
+ const item = params?.internalId
5
+ ? MOCK_CART.products.find(p => p.internalId === params.internalId)
6
+ : MOCK_CART.products[MOCK_CART.products.length - 1];
7
+ if (item) {
8
+ delete item.fee;
9
+ // Publish so cart subscribers refresh.
10
+ mockPublishEvent("cart", "product-fee-removed", { internalId: params?.internalId });
11
+ }
12
+ return Promise.resolve({
13
+ success: true,
14
+ internalId: params?.internalId,
15
+ timestamp: new Date().toISOString()
16
+ });
17
+ };
@@ -1,4 +1,4 @@
1
- import { MOCK_PARKED_ORDERS, MOCK_CART, resetMockCart } from "../../demo/database";
1
+ import { MOCK_PARKED_ORDERS, MOCK_CART, resetMockCart, mockPublishEvent } from "../../demo/database";
2
2
  export const mockResumeParkedOrder = async (params) => {
3
3
  console.log("[Mock] resumeParkedOrder called", params);
4
4
  const orderId = params?.orderId;
@@ -45,6 +45,9 @@ export const mockResumeParkedOrder = async (params) => {
45
45
  MOCK_CART.remainingBalance = MOCK_CART.total;
46
46
  // Remove from parked
47
47
  MOCK_PARKED_ORDERS.splice(index, 1);
48
+ // Refresh the restored cart and the order lists.
49
+ mockPublishEvent("cart", "parked-order-resumed", { orderId });
50
+ mockPublishEvent("orders", "parked-order-resumed", { orderId });
48
51
  return {
49
52
  success: true,
50
53
  order: orderToResume,
@@ -1,13 +1,15 @@
1
- import { MOCK_ORDERS } from "../../demo/database";
1
+ import { applyMockPayment, MOCK_CART } from "../../demo/database";
2
2
  export const mockTapToPayPayment = async (params) => {
3
3
  console.log("[Mock] tapToPayPayment called", params);
4
4
  // Simulate Tap to Pay interaction
5
5
  window.alert("Demo: Processing Tap to Pay...\n(Please tap card or device on screen)");
6
+ const due = params?.amount ?? MOCK_CART.amountToBeCharged ?? MOCK_CART.total;
7
+ const order = applyMockPayment(due, "card", "tapToPay");
6
8
  return {
7
9
  success: true,
8
- amount: params?.amount || null,
10
+ amount: due,
9
11
  paymentType: "tapToPay",
10
- order: MOCK_ORDERS[0],
12
+ order,
11
13
  timestamp: new Date().toISOString()
12
14
  };
13
15
  };
@@ -1,15 +1,14 @@
1
- import { createOrderFromCart, MOCK_CART } from "../../demo/database";
1
+ import { applyMockPayment, MOCK_CART } from "../../demo/database";
2
2
  export const mockTerminalPayment = async (params) => {
3
3
  console.log("[Mock] terminalPayment called", params);
4
4
  const connectionType = params?.paymentType || "Cloud";
5
5
  // Simulate terminal interaction
6
6
  window.alert(`Demo: Processing ${connectionType} Terminal Payment...\n(Please tap, insert, or swipe card on terminal)`);
7
- const amount = params?.amount || MOCK_CART.total;
8
- // Mocking terminal payment success immediately
9
- const order = createOrderFromCart("card", amount, "stripe_terminal");
7
+ const due = params?.amount ?? MOCK_CART.amountToBeCharged ?? MOCK_CART.total;
8
+ const order = applyMockPayment(due, "card", "stripe_terminal");
10
9
  return {
11
10
  success: true,
12
- amount: amount,
11
+ amount: due,
13
12
  paymentType: "terminal",
14
13
  order,
15
14
  timestamp: new Date().toISOString()
@@ -50,6 +50,9 @@ export declare const MOCK_PRODUCT_HABANERO: CFProduct;
50
50
  export declare const MOCK_PRODUCT_BLACK_GARLIC: CFProduct;
51
51
  export declare const MOCK_ORDER_1: CFActiveOrder;
52
52
  export declare const MOCK_ORDER_2: CFActiveOrder;
53
+ export declare const MOCK_ORDER_3: CFActiveOrder;
54
+ export declare const MOCK_PARKED_ORDER_1: CFActiveOrder;
55
+ export declare const MOCK_PARKED_ORDER_2: CFActiveOrder;
53
56
  export declare const MOCK_USERS: CFActiveUser[];
54
57
  export declare const MOCK_STATIONS: CFActiveStation[];
55
58
  export declare const MOCK_OUTLETS: CFActiveOutlet[];
@@ -76,3 +79,10 @@ type MockEventCallback = (event: any) => void;
76
79
  export declare const mockPublishEvent: (topic: string, eventType: string, data: any) => void;
77
80
  export declare const mockSubscribeToTopic: (topic: string, callback: MockEventCallback) => void;
78
81
  export declare const createOrderFromCart: (paymentType: string, amount: number, processor?: string) => CFActiveOrder;
82
+ /**
83
+ * Apply a (possibly partial) payment of `amount` minor units to the active cart.
84
+ * Decrements the remaining balance; when it reaches zero the sale completes
85
+ * (creates the order, resets the cart, returns it). Otherwise the cart stays
86
+ * open with `amountToBeCharged` reset to what's left, and returns null.
87
+ */
88
+ export declare const applyMockPayment: (amount: number, paymentType: string, processor?: string) => CFActiveOrder | null;
@@ -6,7 +6,7 @@ import { CFProductType, CFUserTypes, CurrencyCode } from "../CommonTypes";
6
6
  export * from "./mocks";
7
7
  // Asset Imports - Using Remote URLs to avoid build complexity with asset copying
8
8
  const ASSETS_BASE_URL = "https://raw.githubusercontent.com/Final-Commerce/command-frame/refs/heads/main/src/demo/assets";
9
- const logo = `${ASSETS_BASE_URL}/logo.png`;
9
+ const logo = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA5OCAxMDQiIGZpbGw9Im5vbmUiPjxwYXRoIGQ9Ik01NS4yOTk4IDUwLjE2NjJMMzcuNzQyMiA0MC4wMjkzTDMxLjcyNzQgNTAuNDQ3MkwzOC45MjQyIDU0LjYwMjJMNTUuMjk5OCA1MC4xNjYyWiIgZmlsbD0iI0E1NjhCQyIvPjxwYXRoIGQ9Ik0zMS45OTYzIDcwLjQ1MDZMNDkuNTU2OCA4MC41ODkxTDU1LjU3MTUgNzAuMTcxM0w0OC4zNzE4IDY2LjAxNDVMMzEuOTk2MyA3MC40NTA2WiIgZmlsbD0iI0E1NjhCQyIvPjxwYXRoIGQ9Ik0zNi4zNTQyIDM0LjI1NzJDMzYuMzU0MiAzNC4yNTcyIDM2LjM1ODMgMzQuMjYzNSAzNi4zNTY3IDM0LjI2NjRDMzcuMTg2OSAzNC4wNjk3IDM4LjA5MTkgMzQuMTU5NiAzOC44ODUxIDM0LjYxNzVMNjIuNjQ4NiA0OC4zMzc0TDU0LjE3MjkgMjkuOTA0OUwzNi4zNDk2IDM0LjI1ODRMMzYuMzU0MiAzNC4yNTcyWiIgZmlsbD0iI0ZDODc1MyIvPjxwYXRoIGQ9Ik0zMy4wOTg4IDE4Ljk1MDdMMzYuMzc3MiAzNC4yNDczTDE4Ljc1MjUgMzkuMzM3M0wyOC45MzcxIDIxLjY5N0MyOS44NDUzIDIwLjEyMzkgMzEuNDE1MiAxOS4xNzYxIDMzLjA5ODggMTguOTUwN1oiIGZpbGw9IiM0MkQzQTkiLz48cGF0aCBkPSJNMTguNzUzMiAzOS4zNDA2TDIwLjY4MjQgNTkuNTE3MUw0LjU3NjA3IDYzLjg5NjJMMTguNzUzMiAzOS4zNDA2WiIgZmlsbD0iIzI3OTdFOCIvPjxwYXRoIGQ9Ik0zNi4zNzc5IDM0LjI0MDNDMzYuMzc3OSAzNC4yNDAzIDM2LjM3NzQgMzQuMjQ3OCAzNi4zODAzIDM0LjI0OTVDMzUuNTYyNCAzNC40OTE5IDM0LjgyMzkgMzUuMDE5NiAzNC4zNjczIDM1LjgxMDVMMjAuNjg2OSA1OS41MDU2TDE4Ljc1NzcgMzkuMzI5MUwzNi4zODI0IDM0LjIzOTFMMzYuMzc3OSAzNC4yNDAzWiIgZmlsbD0iIzFEQkFDQiIvPjxwYXRoIGQ9Ik01NS45MzUyIDEwMS41NDNMNTEuMDgxOSA4Ni42NjU5TDMzLjI1ODcgOTEuMDE5NEw1MC45NDk5IDEwMS4yMzNDNTIuNTI3NiAxMDIuMTQ0IDU0LjM2MzggMTAyLjE4NSA1NS45MzUyIDEwMS41NDNaIiBmaWxsPSIjRkQ1MjYzIi8+PHBhdGggZD0iTTMzLjI1NDkgOTEuMDE0TDI0Ljc4MjEgNzIuNTgzMkw4LjYzMTM1IDc2Ljc5NzVMMzMuMjU0OSA5MS4wMTRaIiBmaWxsPSIjRkZDRjIzIi8+PHBhdGggZD0iTTUxLjA3OSA4Ni42NjM5QzUxLjA3OSA4Ni42NjM5IDUxLjA3NDkgODYuNjU3NiA1MS4wNzY2IDg2LjY1NDdDNTAuMjQ2NCA4Ni44NTE0IDQ5LjM0MTQgODYuNzYxNSA0OC41NDgyIDg2LjMwMzZMMjQuNzg0NiA3Mi41ODM3TDMzLjI2MDQgOTEuMDE2MUw1MS4wODM2IDg2LjY2MjdMNTEuMDc5IDg2LjY2MzlaIiBmaWxsPSIjRkM4NzUzIi8+PHBhdGggZD0iTTU0LjMzNDIgMTAxLjk2OUw1MS4wNTU4IDg2LjY3MjRMNjguNjgwNSA4MS41ODI0TDU4LjQ5NTkgOTkuMjIyN0M1Ny41ODc3IDEwMC43OTYgNTYuMDE3OCAxMDEuNzQ0IDU0LjMzNDIgMTAxLjk2OVoiIGZpbGw9IiM0MkQzQTkiLz48cGF0aCBkPSJNNjguNjgwMSA4MS41ODk5TDY2Ljc0OTMgNjEuNDE2Mkw4Mi44NTU2IDU3LjAzNzJMNjguNjgwMSA4MS41ODk5WiIgZmlsbD0iIzI3OTdFOCIvPjxwYXRoIGQ9Ik01MS4wNTU3IDg2LjY3ODZDNTEuMDU1NyA4Ni42Nzg2IDUxLjA1NjIgODYuNjcxMSA1MS4wNTMyIDg2LjY2OTVDNTEuODcxMSA4Ni40MjcxIDUyLjYwOTcgODUuODk5NCA1My4wNjYzIDg1LjEwODVMNjYuNzQ2NyA2MS40MTMzTDY4LjY3NTkgODEuNTg5OEw1MS4wNTExIDg2LjY3OThMNTEuMDU1NyA4Ni42Nzg2WiIgZmlsbD0iIzFEQkFDQiIvPjxwYXRoIGQ9Ik00Ny44OTQ5IDI2LjI3NjhDNDcuODc4IDI2LjM1OTcgNDcuODUyIDI2LjQ0NTEgNDcuODA2OCAyNi41MjM0TDQ3LjYyNzggMjYuODMzM0M0Ni43ODY1IDI4LjI5MDYgNDQuOTIzMiAyOC43ODY5IDQzLjQ2MTggMjcuOTQzMkM0Mi4wMDA0IDI3LjA5OTQgNDEuNDk4NiAyNS4yMzc2IDQyLjMzOTkgMjMuNzgwNEw0Mi41MTg5IDIzLjQ3MDRDNDIuNTY0MSAyMy4zOTIyIDQyLjYyNSAyMy4zMjY5IDQyLjY4ODQgMjMuMjcwOEwzNi40ODUzIDE5LjY4OTRDMzQuOTA3NiAxOC43Nzg2IDMzLjA3MTQgMTguNzM4MiAzMS41IDE5LjM3OTlMMzYuMzUzMyAzNC4yNTY5TDU0LjE3NjUgMjkuOTAzNUw0Ny44OTQ5IDI2LjI3NjhaIiBmaWxsPSIjRkQ1MjYzIi8+PHBhdGggZD0iTTc1LjYzODYgNDIuMzAyN0M3NS42MjE3IDQyLjM4NTcgNzUuNTk1NiA0Mi40NzExIDc1LjU1MDUgNDIuNTQ5M0w3NS4zNzE1IDQyLjg1OTNDNzQuNTMwMiA0NC4zMTY1IDcyLjY2NjkgNDQuODEyOSA3MS4yMDU0IDQzLjk2OTFDNjkuNzQ0IDQzLjEyNTQgNjkuMjQyMyA0MS4yNjM2IDcwLjA4MzYgMzkuODA2M0w3MC4yNjI2IDM5LjQ5NjNDNzAuMzA3NyAzOS40MTgxIDcwLjM2ODYgMzkuMzUyOSA3MC40MzIgMzkuMjk2N0w1NC4xNzYxIDI5LjkxMTRMNjIuNjUxOSA0OC4zNDM5TDc4LjgwMjYgNDQuMTI5NUw3NS42NDE1IDQyLjMwNDRMNzUuNjM4NiA0Mi4zMDI3WiIgZmlsbD0iI0ZGQ0YyMyIvPjxwYXRoIGQ9Ik03NS42MDU4IDQwLjgyMTlMNzUuNjQ0MiA0MC43NTUyQzc4LjE0ODEgMzYuNDE4MyA3OC42NDY1IDMxLjQ0ODggNzcuNDMwOSAyNi45MzgzQzc2LjIxNTMgMjIuNDI3OCA3My4yODQ3IDE4LjM3MTggNjguOTM1MiAxNS44NjA3QzY0LjU4NTggMTMuMzQ5NSA1OS42MDc5IDEyLjgzOTUgNTUuMDkzOSAxNC4wNDJDNTAuNTc5OSAxNS4yNDQ2IDQ2LjUyNTMgMTguMTYwOSA0NC4wMjE0IDIyLjQ5NzhMNDIuOTkxMSAyNC4yODI0QzQyLjM3MzkgMjUuMzUxNCA0Mi43NDQyIDI2LjcxNjMgNDMuODEzNCAyNy4zMzM2QzQ0Ljg4MjYgMjcuOTUwOSA0Ni4yNTE0IDI3LjU4NjIgNDYuODY2OSAyNi41MjAxTDQ3Ljg5NzMgMjQuNzM1NUM0OS43ODU3IDIxLjQ2NDcgNTIuODQ0MSAxOS4yNjczIDU2LjI1NDggMTguMzU4N0M1OS42NjU0IDE3LjQ1MDEgNjMuNDE5MSAxNy44MzI3IDY2LjY5OTQgMTkuNzI2NkM2OS45Nzk2IDIxLjYyMDQgNzIuMTg3OSAyNC42Nzk5IDczLjEwNjMgMjguMDg3OUM3NC4wMjQ4IDMxLjQ5NiA3My42NTEgMzUuMjQzNCA3MS43NjI2IDM4LjUxNDFMNzEuNzI0MSAzOC41ODA4TDcwLjczMzkgNDAuMjk1OEM3MC4xMTY3IDQxLjM2NDkgNzAuNDg3IDQyLjcyOTcgNzEuNTU2MiA0My4zNDdDNzIuNjI1NCA0My45NjQ0IDczLjk5NDIgNDMuNTk5NyA3NC42MDk4IDQyLjUzMzZMNzUuNTk5OSA0MC44MTg1TDc1LjYwNTggNDAuODIxOVoiIGZpbGw9IiMzRDRDNjYiLz48L3N2Zz4="; // crisp vector mark (was logo.png — pixelated when scaled)
10
10
  const basilAlmondImg = `${ASSETS_BASE_URL}/basil-almond-paste.png`;
11
11
  const beerImg = `${ASSETS_BASE_URL}/beer-paste.png`;
12
12
  const beetImg = `${ASSETS_BASE_URL}/beet-paste.png`;
@@ -427,6 +427,140 @@ export const MOCK_ORDER_2 = {
427
427
  station: MOCK_STATION_2,
428
428
  createdAt: new Date(Date.now() - 3600000).toISOString()
429
429
  };
430
+ export const MOCK_ORDER_3 = {
431
+ _id: "order_1003",
432
+ currency: CurrencyCode.USD,
433
+ minorUnits: 2,
434
+ receiptId: "1001-0003",
435
+ companyId: MOCK_COMPANY.id,
436
+ externalId: null,
437
+ status: "refunded",
438
+ paymentState: "refunded",
439
+ fulfillmentState: "fulfilled",
440
+ displayState: "Refunded",
441
+ customer: MOCK_CUSTOMER_3,
442
+ summary: {
443
+ total: 1500,
444
+ subTotal: 1500,
445
+ discountTotal: 0,
446
+ shippingTotal: 0,
447
+ totalTaxes: 0,
448
+ taxes: [],
449
+ isTaxInclusive: false
450
+ },
451
+ cartDiscount: null,
452
+ cartFees: [],
453
+ paymentMethods: [
454
+ {
455
+ transactionId: "trans_card_2",
456
+ paymentType: "credit_card",
457
+ amount: 1500,
458
+ timestamp: new Date(Date.now() - 7200000).toISOString(),
459
+ processor: "stripe"
460
+ }
461
+ ],
462
+ source: "pos",
463
+ posData: {
464
+ outlet: MOCK_OUTLET_MAIN.id,
465
+ station: MOCK_STATION_1._id,
466
+ employee: MOCK_USER_MARIO.id
467
+ },
468
+ sessionId: "sess_3",
469
+ metadata: [],
470
+ billing: null,
471
+ shipping: null,
472
+ lineItems: [createLineItem(MOCK_PRODUCT_BEET, 0, 1)],
473
+ customSales: [],
474
+ balance: 0,
475
+ user: MOCK_USER_MARIO,
476
+ outlet: MOCK_OUTLET_MAIN,
477
+ station: MOCK_STATION_1,
478
+ createdAt: new Date(Date.now() - 7200000).toISOString()
479
+ };
480
+ export const MOCK_PARKED_ORDER_1 = {
481
+ _id: "parked_2001",
482
+ currency: CurrencyCode.USD,
483
+ minorUnits: 2,
484
+ receiptId: "PARK-0001",
485
+ companyId: MOCK_COMPANY.id,
486
+ externalId: null,
487
+ status: "parked",
488
+ paymentState: "unpaid",
489
+ fulfillmentState: "unfulfilled",
490
+ displayState: "Parked",
491
+ customer: MOCK_CUSTOMER_4,
492
+ summary: {
493
+ total: 2100,
494
+ subTotal: 2100,
495
+ discountTotal: 0,
496
+ shippingTotal: 0,
497
+ totalTaxes: 0,
498
+ taxes: [],
499
+ isTaxInclusive: false
500
+ },
501
+ cartDiscount: null,
502
+ cartFees: [],
503
+ paymentMethods: [],
504
+ source: "pos",
505
+ posData: {
506
+ outlet: MOCK_OUTLET_MAIN.id,
507
+ station: MOCK_STATION_1._id,
508
+ employee: MOCK_USER_LUIGI.id
509
+ },
510
+ sessionId: "sess_park_1",
511
+ metadata: [],
512
+ billing: null,
513
+ shipping: null,
514
+ lineItems: [createLineItem(MOCK_PRODUCT_LEMON, 0, 2)],
515
+ customSales: [],
516
+ balance: 2100,
517
+ user: MOCK_USER_LUIGI,
518
+ outlet: MOCK_OUTLET_MAIN,
519
+ station: MOCK_STATION_1,
520
+ createdAt: new Date(Date.now() - 1800000).toISOString()
521
+ };
522
+ export const MOCK_PARKED_ORDER_2 = {
523
+ _id: "parked_2002",
524
+ currency: CurrencyCode.USD,
525
+ minorUnits: 2,
526
+ receiptId: "PARK-0002",
527
+ companyId: MOCK_COMPANY.id,
528
+ externalId: null,
529
+ status: "parked",
530
+ paymentState: "unpaid",
531
+ fulfillmentState: "unfulfilled",
532
+ displayState: "Parked",
533
+ customer: null,
534
+ summary: {
535
+ total: 2500,
536
+ subTotal: 2500,
537
+ discountTotal: 0,
538
+ shippingTotal: 0,
539
+ totalTaxes: 0,
540
+ taxes: [],
541
+ isTaxInclusive: false
542
+ },
543
+ cartDiscount: null,
544
+ cartFees: [],
545
+ paymentMethods: [],
546
+ source: "pos",
547
+ posData: {
548
+ outlet: MOCK_OUTLET_MAIN.id,
549
+ station: MOCK_STATION_2._id,
550
+ employee: MOCK_USER_MARIO.id
551
+ },
552
+ sessionId: "sess_park_2",
553
+ metadata: [],
554
+ billing: null,
555
+ shipping: null,
556
+ lineItems: [createLineItem(MOCK_PRODUCT_CARAMELIZED, 0, 1), createLineItem(MOCK_PRODUCT_GINGER_LIME, 0, 1)],
557
+ customSales: [],
558
+ balance: 2500,
559
+ user: MOCK_USER_MARIO,
560
+ outlet: MOCK_OUTLET_MAIN,
561
+ station: MOCK_STATION_2,
562
+ createdAt: new Date(Date.now() - 900000).toISOString()
563
+ };
430
564
  // --- EXPORT COLLECTIONS ---
431
565
  export const MOCK_USERS = [MOCK_USER_MARIO, MOCK_USER_LUIGI];
432
566
  export const MOCK_STATIONS = [MOCK_STATION_1, MOCK_STATION_2];
@@ -449,8 +583,8 @@ export const MOCK_PRODUCTS = [
449
583
  MOCK_PRODUCT_HABANERO,
450
584
  MOCK_PRODUCT_BLACK_GARLIC
451
585
  ];
452
- export const MOCK_ORDERS = [MOCK_ORDER_1, MOCK_ORDER_2];
453
- export const MOCK_PARKED_ORDERS = [];
586
+ export const MOCK_ORDERS = [MOCK_ORDER_1, MOCK_ORDER_2, MOCK_ORDER_3];
587
+ export const MOCK_PARKED_ORDERS = [MOCK_PARKED_ORDER_1, MOCK_PARKED_ORDER_2];
454
588
  // Compatibility Exports (reassigned by setMockDatabase)
455
589
  export let MOCK_USER = MOCK_USERS[0];
456
590
  export let MOCK_STATION = MOCK_STATIONS[0];
@@ -661,3 +795,26 @@ export const createOrderFromCart = (paymentType, amount, processor = "cash") =>
661
795
  mockPublishEvent("cart", "cart-created", {});
662
796
  return newOrder;
663
797
  };
798
+ /**
799
+ * Apply a (possibly partial) payment of `amount` minor units to the active cart.
800
+ * Decrements the remaining balance; when it reaches zero the sale completes
801
+ * (creates the order, resets the cart, returns it). Otherwise the cart stays
802
+ * open with `amountToBeCharged` reset to what's left, and returns null.
803
+ */
804
+ export const applyMockPayment = (amount, paymentType, processor = "cash") => {
805
+ const remainingBefore = MOCK_CART.remainingBalance ?? MOCK_CART.total;
806
+ const charge = Math.min(Math.max(0, amount || remainingBefore), remainingBefore);
807
+ const remainingAfter = Math.max(0, remainingBefore - charge);
808
+ if (remainingAfter > 0) {
809
+ // Partial payment — keep the cart open, queue the rest for the next tender.
810
+ MOCK_CART.remainingBalance = remainingAfter;
811
+ MOCK_CART.amountToBeCharged = remainingAfter;
812
+ mockPublishEvent("cart", "partial-payment-applied", { charged: charge, remaining: remainingAfter });
813
+ return null;
814
+ }
815
+ // Fully paid — create the completed order (this also resets the cart).
816
+ const orderTotal = MOCK_CART.total;
817
+ const order = createOrderFromCart(paymentType, orderTotal, processor);
818
+ mockPublishEvent("payments", "payment-done", { order });
819
+ return order;
820
+ };
@@ -2,13 +2,17 @@ import { mockAddCartDiscount } from "../../actions/add-cart-discount/mock";
2
2
  import { mockAddCartFee } from "../../actions/add-cart-fee/mock";
3
3
  import { mockRemoveCartFee } from "../../actions/remove-cart-fee/mock";
4
4
  import { mockAddCustomSale } from "../../actions/add-custom-sale/mock";
5
+ import { mockRemoveCustomSale } from "../../actions/remove-custom-sale/mock";
5
6
  import { mockAddCustomer } from "../../actions/add-customer/mock";
6
7
  import { mockAddCustomerNote } from "../../actions/add-customer-note/mock";
7
8
  import { mockRemoveCustomerNote } from "../../actions/remove-customer-note/mock";
8
9
  import { mockEditCustomer } from "../../actions/edit-customer/mock";
9
10
  import { mockAddOrderNote } from "../../actions/add-order-note/mock";
11
+ import { mockRemoveOrderNote } from "../../actions/remove-order-note/mock";
10
12
  import { mockAddProductDiscount } from "../../actions/add-product-discount/mock";
11
13
  import { mockAddProductFee } from "../../actions/add-product-fee/mock";
14
+ import { mockRemoveProductDiscount } from "../../actions/remove-product-discount/mock";
15
+ import { mockRemoveProductFee } from "../../actions/remove-product-fee/mock";
12
16
  import { mockSetActiveProductFee } from "../../actions/set-active-product-fee/mock";
13
17
  import { mockSetActiveProductDiscount } from "../../actions/set-active-product-discount/mock";
14
18
  import { mockGetActiveProduct } from "../../actions/get-active-product/mock";
@@ -182,12 +186,12 @@ export const RENDER_MOCKS = {
182
186
  getActiveUser: mockGetActiveUser,
183
187
  setActiveUser: mockSetActiveUser,
184
188
  setActiveRefund: mockSetActiveRefund,
185
- removeProductDiscount: () => Promise.resolve({ success: true, timestamp: new Date().toISOString() }),
186
- removeProductFee: () => Promise.resolve({ success: true, timestamp: new Date().toISOString() }),
189
+ removeProductDiscount: mockRemoveProductDiscount,
190
+ removeProductFee: mockRemoveProductFee,
187
191
  removeProductNote: () => Promise.resolve({ success: true, timestamp: new Date().toISOString() }),
188
192
  removeCartFee: mockRemoveCartFee,
189
- removeOrderNote: () => Promise.resolve({ success: true, timestamp: new Date().toISOString() }),
190
- removeCustomSale: params => Promise.resolve({ success: true, id: params.id, timestamp: new Date().toISOString() }),
193
+ removeOrderNote: mockRemoveOrderNote,
194
+ removeCustomSale: mockRemoveCustomSale,
191
195
  removeNonRevenueItem: params => Promise.resolve({ success: true, externalId: params.externalId, timestamp: new Date().toISOString() }),
192
196
  canTransition: canTransitionMock,
193
197
  getAvailableTransitions: getAvailableTransitionsMock,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@final-commerce/command-frame",
3
- "version": "0.1.74",
3
+ "version": "0.1.75",
4
4
  "description": "Commands Frame library",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",