@final-commerce/command-frame 0.5.0 → 0.6.0-staging.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.
Files changed (69) hide show
  1. package/README.md +5 -5
  2. package/dist/CommonTypes.d.ts +6 -6
  3. package/dist/CommonTypes.js +2 -2
  4. package/dist/actions/add-cart-discount/mock.d.ts +1 -1
  5. package/dist/actions/add-cart-discount/mock.js +8 -8
  6. package/dist/actions/add-cart-fee/mock.d.ts +1 -1
  7. package/dist/actions/add-cart-fee/mock.js +8 -8
  8. package/dist/actions/add-product-to-cart/mock.d.ts +1 -1
  9. package/dist/actions/add-product-to-cart/mock.js +24 -10
  10. package/dist/actions/add-product-to-cart/types.d.ts +13 -1
  11. package/dist/actions/adjust-inventory/mock.d.ts +1 -1
  12. package/dist/actions/adjust-inventory/mock.js +6 -6
  13. package/dist/actions/apply-transition/mock.d.ts +1 -1
  14. package/dist/actions/apply-transition/mock.js +8 -8
  15. package/dist/actions/apply-transition/types.d.ts +1 -1
  16. package/dist/actions/authenticate-user/mock.d.ts +1 -1
  17. package/dist/actions/authenticate-user/mock.js +3 -3
  18. package/dist/actions/calculate-refund-total/action.d.ts +1 -1
  19. package/dist/actions/calculate-refund-total/action.js +2 -2
  20. package/dist/actions/can-transition/types.d.ts +1 -1
  21. package/dist/actions/cash-payment/mock.d.ts +1 -1
  22. package/dist/actions/cash-payment/mock.js +10 -10
  23. package/dist/actions/cash-payment/types.d.ts +1 -1
  24. package/dist/actions/extension-payment/mock.d.ts +1 -1
  25. package/dist/actions/extension-payment/mock.js +3 -3
  26. package/dist/actions/extension-payment/types.d.ts +1 -1
  27. package/dist/actions/get-available-transitions/mock.d.ts +1 -1
  28. package/dist/actions/get-available-transitions/mock.js +19 -19
  29. package/dist/actions/get-final-context/mock.d.ts +1 -1
  30. package/dist/actions/get-final-context/mock.js +2 -2
  31. package/dist/actions/get-products/mock.d.ts +1 -1
  32. package/dist/actions/get-products/mock.js +26 -9
  33. package/dist/actions/get-refunds/types.d.ts +1 -1
  34. package/dist/actions/get-remaining-refundable-quantities/action.d.ts +1 -1
  35. package/dist/actions/get-remaining-refundable-quantities/action.js +2 -2
  36. package/dist/actions/get-remaining-refundable-quantities/mock.d.ts +1 -1
  37. package/dist/actions/get-remaining-refundable-quantities/mock.js +2 -2
  38. package/dist/actions/integration-payment/types.d.ts +1 -1
  39. package/dist/actions/park-order/types.d.ts +1 -1
  40. package/dist/actions/partial-payment/mock.d.ts +1 -1
  41. package/dist/actions/partial-payment/mock.js +8 -8
  42. package/dist/actions/partial-payment/types.d.ts +1 -1
  43. package/dist/actions/print/mock.d.ts +1 -1
  44. package/dist/actions/print/mock.js +9 -9
  45. package/dist/actions/print/types.d.ts +6 -6
  46. package/dist/actions/redeem-payment/types.d.ts +1 -1
  47. package/dist/actions/remove-cart-fee/mock.d.ts +1 -1
  48. package/dist/actions/remove-cart-fee/mock.js +7 -7
  49. package/dist/actions/remove-product-from-cart/mock.d.ts +1 -1
  50. package/dist/actions/remove-product-from-cart/mock.js +11 -8
  51. package/dist/actions/resume-parked-order/types.d.ts +1 -1
  52. package/dist/actions/select-all-refund-items/action.d.ts +1 -1
  53. package/dist/actions/select-all-refund-items/action.js +2 -2
  54. package/dist/actions/set-active-product/mock.d.ts +1 -1
  55. package/dist/actions/set-active-product/mock.js +19 -14
  56. package/dist/actions/set-active-refund/types.d.ts +1 -1
  57. package/dist/actions/tap-to-pay-payment/mock.d.ts +1 -1
  58. package/dist/actions/tap-to-pay-payment/mock.js +6 -6
  59. package/dist/actions/tap-to-pay-payment/types.d.ts +1 -1
  60. package/dist/actions/terminal-payment/mock.d.ts +1 -1
  61. package/dist/actions/terminal-payment/mock.js +6 -6
  62. package/dist/actions/terminal-payment/types.d.ts +2 -2
  63. package/dist/actions/update-cart-item-quantity/mock.d.ts +1 -1
  64. package/dist/actions/update-cart-item-quantity/mock.js +23 -15
  65. package/dist/actions/update-cart-item-quantity/types.d.ts +8 -1
  66. package/dist/actions/upsert-custom-table-data/mock.d.ts +1 -1
  67. package/dist/actions/upsert-custom-table-data/mock.js +3 -3
  68. package/dist/demo/database.js +30 -4
  69. package/package.json +2 -2
@@ -1,60 +1,68 @@
1
- import { MOCK_CART, mockPublishEvent } from "../../demo/database";
1
+ import { MOCK_CART, mockPublishEvent } from '../../demo/database';
2
+ import { extendPrice, isValidQuantity } from '@final-commerce/common';
2
3
  export const mockUpdateCartItemQuantity = (params) => {
3
- console.log("[Mock] updateCartItemQuantity called", params);
4
+ console.log('[Mock] updateCartItemQuantity called', params);
4
5
  if (!params?.internalId) {
5
- throw new Error("internalId is required");
6
+ throw new Error('internalId is required');
6
7
  }
7
8
  if (params.quantity === undefined || params.quantity === null) {
8
- throw new Error("quantity is required");
9
+ throw new Error('quantity is required');
9
10
  }
10
11
  const { internalId, quantity } = params;
11
12
  // Find the product in the cart
12
- const productIndex = MOCK_CART.products.findIndex(p => p.internalId === internalId);
13
+ const productIndex = MOCK_CART.products.findIndex((p) => p.internalId === internalId);
13
14
  if (productIndex === -1) {
14
15
  throw new Error(`Cart item with internalId ${internalId} not found`);
15
16
  }
16
17
  const product = MOCK_CART.products[productIndex];
17
18
  const previousQuantity = product.quantity;
19
+ // The line resolved its unit when it was added; a piece-sold line has none and stays whole.
20
+ if (quantity !== 0 &&
21
+ !isValidQuantity(quantity, (product.unit ?? { unitId: 'piece', ratioToBase: 1, precision: 0 }))) {
22
+ throw new Error(product.unit
23
+ ? `${quantity} is finer than ${product.unit.abbreviation} can express (${product.unit.precision} decimals)`
24
+ : `${product.name} is sold by the piece, so ${quantity} is not a quantity it can sell in`);
25
+ }
18
26
  // If quantity is 0, remove the item
19
27
  if (quantity === 0) {
20
28
  MOCK_CART.products.splice(productIndex, 1);
21
29
  // Recalculate totals
22
- const lineTotal = product.price * previousQuantity;
30
+ const lineTotal = extendPrice(product.price, previousQuantity);
23
31
  MOCK_CART.subtotal -= lineTotal;
24
32
  MOCK_CART.total -= lineTotal;
25
33
  MOCK_CART.amountToBeCharged = MOCK_CART.total;
26
34
  MOCK_CART.remainingBalance = MOCK_CART.total;
27
35
  // Publish product-deleted event
28
- mockPublishEvent("cart", "product-deleted", {
36
+ mockPublishEvent('cart', 'product-deleted', {
29
37
  product: product,
30
- internalId: internalId
38
+ internalId: internalId,
31
39
  });
32
40
  return Promise.resolve({
33
41
  success: true,
34
42
  internalId: internalId,
35
43
  quantity: 0,
36
- timestamp: new Date().toISOString()
44
+ timestamp: new Date().toISOString(),
37
45
  });
38
46
  }
39
47
  // Update quantity
40
- const quantityDelta = quantity - previousQuantity;
41
48
  product.quantity = quantity;
42
- // Recalculate totals
43
- const lineTotalDelta = product.price * quantityDelta;
49
+ // Difference of the two ROUNDED extensions — rounding the delta itself would let the
50
+ // line's running total drift from what extendPrice(price, quantity) says it is.
51
+ const lineTotalDelta = extendPrice(product.price, quantity) - extendPrice(product.price, previousQuantity);
44
52
  MOCK_CART.subtotal += lineTotalDelta;
45
53
  MOCK_CART.total += lineTotalDelta;
46
54
  MOCK_CART.amountToBeCharged = MOCK_CART.total;
47
55
  MOCK_CART.remainingBalance = MOCK_CART.total;
48
56
  // Publish product-updated event
49
- mockPublishEvent("cart", "product-updated", {
57
+ mockPublishEvent('cart', 'product-updated', {
50
58
  product: product,
51
59
  previousQuantity: previousQuantity,
52
- newQuantity: quantity
60
+ newQuantity: quantity,
53
61
  });
54
62
  return Promise.resolve({
55
63
  success: true,
56
64
  internalId: internalId,
57
65
  quantity: quantity,
58
- timestamp: new Date().toISOString()
66
+ timestamp: new Date().toISOString(),
59
67
  });
60
68
  };
@@ -1,7 +1,14 @@
1
1
  export interface UpdateCartItemQuantityParams {
2
2
  /** The unique identifier for the specific cart item to update. */
3
3
  internalId: string;
4
- /** The new quantity. If set to 0, the item will be removed from the cart. */
4
+ /**
5
+ * The new quantity. If set to 0, the item will be removed from the cart.
6
+ *
7
+ * **May be fractional** for a variant sold by measure; the number of decimals allowed comes
8
+ * from `variant.unit.precision`. The engine refuses anything finer and names the unit in the
9
+ * error. Do not round or clamp before sending — a silently altered quantity is charged and
10
+ * deducted differently than the one the cashier typed.
11
+ */
5
12
  quantity: number;
6
13
  }
7
14
  export interface UpdateCartItemQuantityResponse {
@@ -1,2 +1,2 @@
1
- import { UpsertCustomTableData } from "./types";
1
+ import { UpsertCustomTableData } from './types';
2
2
  export declare const mockUpsertCustomTableData: UpsertCustomTableData;
@@ -1,5 +1,5 @@
1
1
  export const mockUpsertCustomTableData = async (params) => {
2
- console.log("[Mock] upsertCustomTableData called", params);
2
+ console.log('[Mock] upsertCustomTableData called', params);
3
3
  const data = params?.data;
4
4
  const now = new Date().toISOString();
5
5
  // Return the data with _id and timestamps added
@@ -7,11 +7,11 @@ export const mockUpsertCustomTableData = async (params) => {
7
7
  ...data,
8
8
  _id: data?._id || `mock_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
9
9
  createdAt: data?.createdAt || now,
10
- updatedAt: now
10
+ updatedAt: now,
11
11
  };
12
12
  return {
13
13
  success: true,
14
14
  data: resultData,
15
- timestamp: now
15
+ timestamp: now,
16
16
  };
17
17
  };
@@ -3,6 +3,7 @@
3
3
  * Stores mock data that mimics the Render environment
4
4
  */
5
5
  import { CFProductType, CFUserTypes, CurrencyCode, } from '../CommonTypes';
6
+ import { extendPrice, resolveUnit, toBase } from '@final-commerce/common';
6
7
  export * from './mocks';
7
8
  // Asset Imports - Using Remote URLs to avoid build complexity with asset copying
8
9
  const ASSETS_BASE_URL = 'https://raw.githubusercontent.com/Final-Commerce/command-frame/refs/heads/main/src/demo/assets';
@@ -314,12 +315,25 @@ export const MOCK_PRODUCT_BLACK_GARLIC = createSimpleProduct('prod_black_garlic'
314
315
  // Helper to create line item
315
316
  const createLineItem = (product, variantIndex = 0, quantity = 1) => {
316
317
  const variant = product.variants[variantIndex];
318
+ // Same contract as a real order line: the unit is FROZEN on the line (a re-rated unit must
319
+ // not restate old orders), and the money is extendPrice — price × quantity may carry a
320
+ // fractional quantity, and the raw multiply would put fractional cents on the order.
321
+ const unit = variant.unitId ? resolveUnit(variant.unitId) : undefined;
322
+ const total = extendPrice(variant.price, quantity);
317
323
  return {
318
324
  productId: product._id,
319
325
  variantId: variant._id,
320
326
  name: product.name,
321
327
  quantity,
322
328
  price: variant.price,
329
+ ...(unit
330
+ ? {
331
+ unitId: unit.unitId,
332
+ unitAbbreviation: unit.abbreviation,
333
+ unitRatioToBase: unit.ratioToBase,
334
+ stockQuantity: toBase(quantity, unit),
335
+ }
336
+ : {}),
323
337
  taxes: [],
324
338
  discount: {
325
339
  itemDiscounts: [],
@@ -327,8 +341,8 @@ const createLineItem = (product, variantIndex = 0, quantity = 1) => {
327
341
  },
328
342
  fee: { itemFees: [] },
329
343
  totalTax: 0,
330
- total: variant.price * quantity,
331
- lineNetWithFees: variant.price * quantity,
344
+ total,
345
+ lineNetWithFees: total,
332
346
  metadata: [],
333
347
  image: product.images?.[0] || '',
334
348
  sku: variant.sku,
@@ -784,12 +798,24 @@ export const createOrderFromCart = (paymentType, amount, processor = 'cash') =>
784
798
  const receiptId = `receipt_${Date.now()}`;
785
799
  // Map cart products to line items
786
800
  const lineItems = MOCK_CART.products.map((p) => {
801
+ // The cart line resolved its unit when it was added; the ORDER line freezes it, exactly
802
+ // as production does. extendPrice for the same reason as everywhere: fractional
803
+ // quantities must not leave fractional cents on an order.
804
+ const lineTotal = extendPrice(p.price, p.quantity);
787
805
  return {
788
806
  productId: p.id,
789
807
  variantId: p.variantId,
790
808
  name: p.name,
791
809
  quantity: p.quantity,
792
810
  price: p.price,
811
+ ...(p.unit
812
+ ? {
813
+ unitId: p.unit.unitId,
814
+ unitAbbreviation: p.unit.abbreviation,
815
+ unitRatioToBase: p.unit.ratioToBase,
816
+ stockQuantity: toBase(p.quantity, p.unit),
817
+ }
818
+ : {}),
793
819
  taxes: [],
794
820
  discount: {
795
821
  itemDiscounts: [],
@@ -797,8 +823,8 @@ export const createOrderFromCart = (paymentType, amount, processor = 'cash') =>
797
823
  },
798
824
  fee: { itemFees: [] },
799
825
  totalTax: 0,
800
- total: p.price * p.quantity,
801
- lineNetWithFees: p.price * p.quantity,
826
+ total: lineTotal,
827
+ lineNetWithFees: lineTotal,
802
828
  metadata: [],
803
829
  image: p.images?.[0] || '',
804
830
  sku: p.sku || '',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@final-commerce/command-frame",
3
- "version": "0.5.0",
3
+ "version": "0.6.0-staging.1",
4
4
  "description": "Commands Frame library",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -57,7 +57,7 @@
57
57
  "ignoreBranchesMissingTickets": true
58
58
  },
59
59
  "dependencies": {
60
- "@final-commerce/common": "^2.0.2"
60
+ "@final-commerce/common": "2.2.0-staging.2"
61
61
  },
62
62
  "devDependencies": {
63
63
  "@commitlint/cli": "^19.0.0",