@final-commerce/command-frame 0.9.0 → 0.10.0-staging.2

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 (57) hide show
  1. package/dist/CommonTypes.d.ts +36 -3
  2. package/dist/actions/add-product-fee/mock.d.ts +1 -1
  3. package/dist/actions/add-product-fee/mock.js +15 -9
  4. package/dist/actions/add-product-to-cart/mock.d.ts +23 -0
  5. package/dist/actions/add-product-to-cart/mock.js +321 -1
  6. package/dist/actions/add-product-to-cart/types.d.ts +28 -2
  7. package/dist/actions/adjust-inventory/mock.js +0 -20
  8. package/dist/actions/adjust-inventory/types.d.ts +0 -13
  9. package/dist/actions/adjust-inventory/types.js +0 -1
  10. package/dist/actions/get-composite-availability/action.d.ts +6 -0
  11. package/dist/actions/get-composite-availability/action.js +8 -0
  12. package/dist/actions/get-composite-availability/mock.d.ts +2 -0
  13. package/dist/actions/get-composite-availability/mock.js +45 -0
  14. package/dist/actions/get-composite-availability/types.d.ts +29 -0
  15. package/dist/actions/get-composite-availability/types.js +1 -0
  16. package/dist/actions/get-composite-price/action.d.ts +6 -0
  17. package/dist/actions/get-composite-price/action.js +8 -0
  18. package/dist/actions/get-composite-price/mock.d.ts +2 -0
  19. package/dist/actions/get-composite-price/mock.js +37 -0
  20. package/dist/actions/get-composite-price/types.d.ts +31 -0
  21. package/dist/actions/get-composite-price/types.js +1 -0
  22. package/dist/actions/get-current-cart/mock.d.ts +1 -1
  23. package/dist/actions/get-current-cart/mock.js +13 -3
  24. package/dist/actions/get-current-cart/types.d.ts +7 -1
  25. package/dist/actions/get-product-modifier-selections/mock.js +12 -0
  26. package/dist/actions/get-products/mock.js +7 -3
  27. package/dist/actions/get-products/types.d.ts +1 -1
  28. package/dist/actions/set-product-modifier-selections/mock.js +12 -0
  29. package/dist/actions/set-variant-unavailable/action.d.ts +2 -0
  30. package/dist/actions/set-variant-unavailable/action.js +4 -0
  31. package/dist/actions/set-variant-unavailable/mock.d.ts +2 -0
  32. package/dist/actions/set-variant-unavailable/mock.js +12 -0
  33. package/dist/actions/set-variant-unavailable/types.d.ts +17 -0
  34. package/dist/actions/set-variant-unavailable/types.js +1 -0
  35. package/dist/demo/database.d.ts +54 -16
  36. package/dist/demo/database.js +219 -1
  37. package/dist/index.d.ts +7 -3
  38. package/dist/index.js +6 -2
  39. package/dist/projects/render/mocks.js +6 -2
  40. package/dist/projects/render/types.d.ts +4 -2
  41. package/dist/pubsub/topics/products/catalog-visibility-changed/types.d.ts +17 -0
  42. package/dist/pubsub/topics/products/catalog-visibility-changed/types.js +1 -0
  43. package/dist/pubsub/topics/products/composite-changed/types.d.ts +17 -0
  44. package/dist/pubsub/topics/products/composite-changed/types.js +1 -0
  45. package/dist/pubsub/topics/products/index.d.ts +2 -2
  46. package/dist/pubsub/topics/products/index.js +33 -18
  47. package/dist/pubsub/topics/products/inventory-changed/types.d.ts +18 -0
  48. package/dist/pubsub/topics/products/inventory-changed/types.js +1 -0
  49. package/dist/pubsub/topics/products/types.d.ts +16 -10
  50. package/dist/pubsub/topics/products/types.js +7 -4
  51. package/package.json +2 -2
  52. package/dist/actions/get-custom-stock-actions/action.d.ts +0 -5
  53. package/dist/actions/get-custom-stock-actions/action.js +0 -7
  54. package/dist/actions/get-custom-stock-actions/mock.d.ts +0 -2
  55. package/dist/actions/get-custom-stock-actions/mock.js +0 -11
  56. package/dist/actions/get-custom-stock-actions/types.d.ts +0 -12
  57. package/dist/actions/get-custom-stock-actions/types.js +0 -2
@@ -3,7 +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
+ import { cheapestCompositeItem, cheapestCompositePrice, extendPrice, resolveUnit, toBase, } from '@final-commerce/common';
7
7
  import { BookingRatePeriod, BookingResourceKind, BookingType, ReservationStatus } from '@final-commerce/common';
8
8
  export * from './mocks';
9
9
  // Asset Imports - Using Remote URLs to avoid build complexity with asset copying
@@ -228,6 +228,7 @@ const createSimpleProduct = (id, name, price, image, categories, description) =>
228
228
  maxPrice: price,
229
229
  status: 'active',
230
230
  productType: CFProductType.SIMPLE,
231
+ composite: null,
231
232
  taxTable: 'tax_standard',
232
233
  description,
233
234
  images: [image],
@@ -241,6 +242,7 @@ const createSimpleProduct = (id, name, price, image, categories, description) =>
241
242
  salePrice: 0,
242
243
  isOnSale: false,
243
244
  manageStock: true,
245
+ unavailable: false,
244
246
  externalId: `ext_${id}_var`,
245
247
  attributes: [],
246
248
  inventory: createInventory(100),
@@ -263,6 +265,7 @@ const createVariableProduct = (id, name, basePrice, largePrice, image, categorie
263
265
  maxPrice: largePrice,
264
266
  status: 'active',
265
267
  productType: CFProductType.VARIABLE,
268
+ composite: null,
266
269
  taxTable: 'tax_standard',
267
270
  description,
268
271
  images: [image],
@@ -276,6 +279,7 @@ const createVariableProduct = (id, name, basePrice, largePrice, image, categorie
276
279
  salePrice: 0,
277
280
  isOnSale: false,
278
281
  manageStock: true,
282
+ unavailable: false,
279
283
  externalId: `ext_${id}_var_s`,
280
284
  attributes: [{ name: 'Size', value: 'Small' }],
281
285
  inventory: createInventory(50),
@@ -287,6 +291,7 @@ const createVariableProduct = (id, name, basePrice, largePrice, image, categorie
287
291
  salePrice: 0,
288
292
  isOnSale: false,
289
293
  manageStock: true,
294
+ unavailable: false,
290
295
  externalId: `ext_${id}_var_l`,
291
296
  attributes: [{ name: 'Size', value: 'Large' }],
292
297
  inventory: createInventory(30),
@@ -296,6 +301,8 @@ const createVariableProduct = (id, name, basePrice, largePrice, image, categorie
296
301
  };
297
302
  export const MOCK_PRODUCT_BASIL_ALMOND = createSimpleProduct('prod_basil_almond', 'Basil Almond Paste', 1200, basilAlmondImg, [MOCK_CATEGORY_PASTES, MOCK_CATEGORY_SPECIALTY, MOCK_CATEGORY_VEGAN], 'A rich blend of fresh basil and roasted almonds.');
298
303
  export const MOCK_PRODUCT_BEER = createVariableProduct('prod_beer', 'Beer Paste', 1500, 2500, beerImg, [MOCK_CATEGORY_PASTES, MOCK_CATEGORY_SPECIALTY], 'Unique paste infused with dark lager.');
304
+ // Demo of `setVariantUnavailable`: the large jar is marked unavailable at the outlet, so `addProductToCart` refuses it.
305
+ MOCK_PRODUCT_BEER.variants[1].unavailable = true;
299
306
  export const MOCK_PRODUCT_BEET = createSimpleProduct('prod_beet', 'Beet Paste', 1000, beetImg, [MOCK_CATEGORY_PASTES, MOCK_CATEGORY_BASIC, MOCK_CATEGORY_VEGAN], 'Earthy and sweet beet paste, perfect for salads.');
300
307
  export const MOCK_PRODUCT_CARAMELIZED = createVariableProduct('prod_caramelized', 'Caramelized Paste', 1400, 2200, caramelizedImg, [MOCK_CATEGORY_PASTES, MOCK_CATEGORY_SPECIALTY, MOCK_CATEGORY_VEGAN], 'Slow-cooked caramelized onion paste.');
301
308
  export const MOCK_PRODUCT_GARLIC_ONION = createVariableProduct('prod_garlic_onion', 'Garlic Onion Paste', 1100, 1800, garlicOnionImg, [MOCK_CATEGORY_PASTES, MOCK_CATEGORY_BASIC, MOCK_CATEGORY_VEGAN], 'Classic savory base for any dish.');
@@ -658,7 +665,106 @@ export const MOCK_PRODUCT_HAIRCUT = {
658
665
  productType: CFProductType.BOOKING,
659
666
  bookingRulesId: 'rule_salon_30',
660
667
  };
668
+ // A composite (FT-83) as the host serves it: the merchant's price 27.00, items add only their upcharges (B31); each item
669
+ // keeps its own tax (`taxTable` empty). One item points at a deleted variant (unavailable), one is a whole category.
670
+ export const MOCK_PRODUCT_PASTE_TRIO = {
671
+ ...createSimpleProduct('prod_paste_trio', 'Paste Trio', 2700, basilAlmondImg, [MOCK_CATEGORY_PASTES], 'Pick a paste and a spicy one.'),
672
+ productType: CFProductType.COMPOSITE,
673
+ taxTable: '',
674
+ // B43: a composite has no modifiers of its own; its items carry theirs.
675
+ modifiers: [],
676
+ composite: {
677
+ available: true,
678
+ basePrice: 2700,
679
+ fromPrice: 2800,
680
+ parts: [
681
+ {
682
+ _id: 'part_paste',
683
+ name: 'Paste',
684
+ required: true,
685
+ min: 1,
686
+ max: 1,
687
+ defaultPick: { itemId: 'item_basil', variantId: 'prod_basil_almond_var_main' },
688
+ items: [
689
+ {
690
+ _id: 'item_basil',
691
+ variantId: 'prod_basil_almond_var_main',
692
+ categoryId: null,
693
+ name: 'Basil Almond Paste',
694
+ extraCharge: 0,
695
+ quantity: 1,
696
+ cost: 0,
697
+ unavailable: null,
698
+ choices: [
699
+ {
700
+ productId: 'prod_basil_almond',
701
+ variantId: 'prod_basil_almond_var_main',
702
+ name: 'Basil Almond Paste',
703
+ attributes: [],
704
+ // The picked product's own modifiers (B28): taxed at its table, sent in the pick.
705
+ modifiers: [
706
+ {
707
+ _id: 'mod_texture',
708
+ name: 'Texture',
709
+ selectionType: 'single',
710
+ required: false,
711
+ sortOrder: 0,
712
+ choices: [
713
+ { _id: 'choice_smooth', name: 'Smooth', price: 0, sortOrder: 0 },
714
+ { _id: 'choice_chunky', name: 'Chunky', price: 50, sortOrder: 1 },
715
+ ],
716
+ },
717
+ ],
718
+ },
719
+ ],
720
+ },
721
+ {
722
+ _id: 'item_gone',
723
+ variantId: 'prod_gone_var_main',
724
+ categoryId: null,
725
+ name: '',
726
+ extraCharge: 0,
727
+ quantity: 1,
728
+ cost: 0,
729
+ unavailable: 'deleted',
730
+ choices: [],
731
+ },
732
+ ],
733
+ },
734
+ {
735
+ _id: 'part_spicy',
736
+ name: null,
737
+ required: true,
738
+ min: 1,
739
+ max: 2,
740
+ // "Pick up to 2": no preselected pick (B29 = D36) — the till shows how many are still needed.
741
+ defaultPick: null,
742
+ items: [
743
+ {
744
+ _id: 'item_spicy',
745
+ variantId: null,
746
+ categoryId: MOCK_CATEGORY_SPICY.id,
747
+ name: 'Spicy',
748
+ extraCharge: 100,
749
+ quantity: 1,
750
+ cost: 100,
751
+ unavailable: null,
752
+ choices: [
753
+ {
754
+ productId: 'prod_habanero',
755
+ variantId: 'prod_habanero_var_main',
756
+ name: 'Habanero Paste',
757
+ attributes: [],
758
+ },
759
+ ],
760
+ },
761
+ ],
762
+ },
763
+ ],
764
+ },
765
+ };
661
766
  export const MOCK_PRODUCTS = [
767
+ MOCK_PRODUCT_PASTE_TRIO,
662
768
  MOCK_PRODUCT_BASIL_ALMOND,
663
769
  MOCK_PRODUCT_BEER,
664
770
  MOCK_PRODUCT_BEET,
@@ -676,6 +782,114 @@ export const MOCK_PRODUCTS = [
676
782
  MOCK_PRODUCT_HAIRCUT,
677
783
  ];
678
784
  export const MOCK_ORDERS = [MOCK_ORDER_1, MOCK_ORDER_2, MOCK_ORDER_3, MOCK_ORDER_4];
785
+ /** Nothing hidden by default; `setMockDatabase({ catalogVisibility })` hides products at an outlet. */
786
+ export const MOCK_CATALOG_VISIBILITY = [];
787
+ /**
788
+ * The host's cart stock check for composites (kaching `compositeStockRefusal`), over the demo shelves — a copy: what the
789
+ * cart takes of each component's variant (plain lines and components × their line quantity) against its shelf here.
790
+ * The demo has no pools or units, so it counts per variant; untracked and backordered variants are never short.
791
+ */
792
+ export function mockCompositeStockRefusal(products) {
793
+ const components = products.flatMap((product) => product.components ?? []);
794
+ const demand = new Map();
795
+ for (const product of products) {
796
+ if (product.components)
797
+ for (const component of product.components)
798
+ demand.set(component.variantId, (demand.get(component.variantId) ?? 0) + component.quantity * product.quantity);
799
+ else
800
+ demand.set(product.variantId, (demand.get(product.variantId) ?? 0) + product.quantity);
801
+ }
802
+ for (const component of components) {
803
+ const variant = MOCK_PRODUCTS.flatMap((product) => product.variants).find((candidate) => candidate._id === component.variantId);
804
+ if (!variant?.manageStock || variant.allowBackorder)
805
+ continue;
806
+ const left = variant.inventory?.find((row) => row.outletId === MOCK_OUTLET.id)?.stock ?? 0;
807
+ if ((demand.get(component.variantId) ?? 0) > left)
808
+ return `${component.name}: only ${left} left`;
809
+ }
810
+ return null;
811
+ }
812
+ /** Product ids hidden at the demo's active outlet. */
813
+ export const mockHiddenProductIds = () => new Set(MOCK_CATALOG_VISIBILITY.filter((row) => row.outletId === MOCK_OUTLET.id).map((row) => row.productId));
814
+ /**
815
+ * The host's outlet rules for a composite (kaching `resolveComposite`), over the demo's static view — a copy, change both
816
+ * in one step: an item whose product is hidden here is left out, a category item keeps only products sold here (none →
817
+ * `'empty'`), an Optional part with nothing to pick is not offered, a required one with nothing makes it unavailable,
818
+ * and the D36 default and "from" price are read off what is left.
819
+ */
820
+ /** B41: the seats a bookable demo product has free in the slot starting at `startAt` — as the host reads them. */
821
+ export function mockBookableWindow(productId, startAt) {
822
+ const at = new Date(startAt);
823
+ const slot = mockBookingAvailability(productId, at, new Date(+at + 86400000)).slots.find((candidate) => +new Date(candidate.startAt) === +at && candidate.canStart) ?? null;
824
+ return { free: slot?.free ?? 0, slot };
825
+ }
826
+ export const mockIsBookable = (productId) => MOCK_PRODUCTS.find((product) => product._id === productId)?.productType === CFProductType.BOOKING;
827
+ export function mockCompositeAtOutlet(composite, hidden = mockHiddenProductIds(),
828
+ /** B41: the window bookable items are judged in; absent = `by_date`. */
829
+ slot) {
830
+ // B41: a bookable item is judged by seats in the window, never by stock; with no window it waits for a date.
831
+ const bookable = (item) => !!item.variantId && item.choices.some((choice) => mockIsBookable(choice.productId));
832
+ const seatsFor = (item) => !slot
833
+ ? 'by_date'
834
+ : mockBookableWindow(item.choices[0].productId, slot.startAt).free >= item.quantity
835
+ ? null
836
+ : 'fully_booked';
837
+ const offered = (item) => !item.unavailable || item.unavailable === 'by_date';
838
+ // B35: a tracked variant without `quantity` on the demo shelf here (and no backorder) is sold out.
839
+ const soldOut = (variantId, quantity) => {
840
+ const variant = MOCK_PRODUCTS.flatMap((product) => product.variants).find((candidate) => candidate._id === variantId);
841
+ if (!variant?.manageStock || variant.allowBackorder)
842
+ return false;
843
+ return (variant.inventory?.find((row) => row.outletId === MOCK_OUTLET.id)?.stock ?? 0) < quantity;
844
+ };
845
+ // B36e: "N left" on a unit-sold tracked choice — the demo shelf already reads in the variant's own unit.
846
+ const withStockLeft = (choice) => {
847
+ const variant = MOCK_PRODUCTS.flatMap((product) => product.variants).find((candidate) => candidate._id === choice.variantId);
848
+ const onHand = variant?.inventory?.find((row) => row.outletId === MOCK_OUTLET.id)?.stock;
849
+ return variant?.unitId && variant.manageStock && typeof onHand === 'number'
850
+ ? { ...choice, stockLeft: onHand }
851
+ : choice;
852
+ };
853
+ const upcharge = (item) => ({
854
+ extraCharge: item.extraCharge,
855
+ quantity: item.quantity,
856
+ available: offered(item),
857
+ });
858
+ const parts = composite.parts
859
+ .map((part) => {
860
+ const items = part.items.flatMap((item) => {
861
+ if (item.variantId) {
862
+ if (item.choices.some((choice) => hidden.has(choice.productId)))
863
+ return [];
864
+ if (!item.unavailable && bookable(item))
865
+ return [{ ...item, unavailable: seatsFor(item) }];
866
+ return item.unavailable || !soldOut(item.variantId, item.quantity)
867
+ ? [{ ...item, choices: item.choices.map(withStockLeft) }]
868
+ : [{ ...item, unavailable: 'out_of_stock', choices: [] }];
869
+ }
870
+ const sold = item.choices.filter((choice) => !hidden.has(choice.productId));
871
+ const choices = sold.filter((choice) => !soldOut(choice.variantId, item.quantity));
872
+ const unavailable = item.unavailable ?? (!sold.length ? 'empty' : !choices.length ? 'out_of_stock' : null);
873
+ return [{ ...item, choices: unavailable ? [] : choices.map(withStockLeft), unavailable }];
874
+ });
875
+ const index = part.min === 1 && part.max === 1 ? cheapestCompositeItem(items.map(upcharge)) : undefined;
876
+ const cheapest = index === undefined ? undefined : items[index];
877
+ return {
878
+ ...part,
879
+ items,
880
+ defaultPick: cheapest?.variantId ? { itemId: cheapest._id, variantId: cheapest.variantId } : null,
881
+ };
882
+ })
883
+ .filter((part) => part.min > 0 || part.items.some(offered));
884
+ const cheapest = cheapestCompositePrice(parts.map((part) => ({ min: part.min, items: part.items.map(upcharge) })));
885
+ return {
886
+ ...composite,
887
+ available: composite.available && parts.every((part) => part.min === 0 || part.items.some(offered)),
888
+ needsDate: parts.some((part) => part.items.some(bookable)),
889
+ fromPrice: composite.basePrice === null || cheapest === undefined ? null : composite.basePrice + cheapest,
890
+ parts,
891
+ };
892
+ }
679
893
  export const MOCK_PARKED_ORDERS = [MOCK_PARKED_ORDER_1, MOCK_PARKED_ORDER_2];
680
894
  // Compatibility Exports (reassigned by setMockDatabase)
681
895
  export let MOCK_USER = MOCK_USERS[0];
@@ -760,6 +974,9 @@ export function setMockDatabase(config) {
760
974
  if (config.bookings !== undefined) {
761
975
  MOCK_BOOKINGS.splice(0, MOCK_BOOKINGS.length, ...config.bookings);
762
976
  }
977
+ if (config.catalogVisibility !== undefined) {
978
+ MOCK_CATALOG_VISIBILITY.splice(0, MOCK_CATALOG_VISIBILITY.length, ...config.catalogVisibility);
979
+ }
763
980
  if (MOCK_OUTLETS.length > 0) {
764
981
  Object.assign(MOCK_OUTLET_MAIN, MOCK_OUTLETS[0]);
765
982
  if (MOCK_OUTLET_MAIN.id === undefined && MOCK_OUTLET_MAIN._id !== undefined) {
@@ -1132,6 +1349,7 @@ export const buildModifierRows = (modifiers, lineQuantity) => {
1132
1349
  amount: extendPrice(modifier.unitPrice * modifier.quantity, quantity),
1133
1350
  tax: 0,
1134
1351
  ...(modifier.taxTableId ? { taxTableId: modifier.taxTableId } : {}),
1352
+ ...(modifier.componentIndex !== undefined ? { componentIndex: modifier.componentIndex } : {}),
1135
1353
  }));
1136
1354
  return { rows, modifiersTotal: rows.reduce((sum, row) => sum + row.amount, 0) };
1137
1355
  };
package/dist/index.d.ts CHANGED
@@ -29,13 +29,15 @@ export declare const command: {
29
29
  readonly addProductNote: import(".").AddProductNote;
30
30
  readonly addProductFee: import(".").AddProductFee;
31
31
  readonly getProductModifierSelections: import(".").GetProductModifierSelections;
32
+ readonly getCompositePrice: import(".").GetCompositePrice;
33
+ readonly getCompositeAvailability: import(".").GetCompositeAvailability;
32
34
  readonly setProductModifierSelections: import(".").SetProductModifierSelections;
33
35
  readonly setActiveProductFee: import(".").SetActiveProductFee;
34
36
  readonly setActiveProductDiscount: import(".").SetActiveProductDiscount;
35
37
  readonly getActiveProduct: import(".").GetActiveProduct;
36
38
  readonly setActiveProduct: import(".").SetActiveProduct;
37
39
  readonly adjustInventory: import(".").AdjustInventory;
38
- readonly getCustomStockActions: import(".").GetCustomStockActions;
40
+ readonly setVariantUnavailable: import(".").SetVariantUnavailable;
39
41
  readonly addOrderNote: import(".").AddOrderNote;
40
42
  readonly addCartFee: import(".").AddCartFee;
41
43
  readonly getCurrentCart: import(".").GetCurrentCart;
@@ -175,13 +177,15 @@ export type { GetFinalContext, GetFinalContextResponse } from './actions/get-fin
175
177
  export type { AddProductNote, AddProductNoteParams, AddProductNoteResponse } from './actions/add-product-note/types';
176
178
  export type { AddProductFee, AddProductFeeParams, AddProductFeeResponse } from './actions/add-product-fee/types';
177
179
  export type { GetProductModifierSelections, GetProductModifierSelectionsParams, GetProductModifierSelectionsResponse, ModifierSelection, ModifierChoiceSelection, } from './actions/get-product-modifier-selections/types';
180
+ export type { GetCompositePrice, GetCompositePriceParams, GetCompositePriceResponse, } from './actions/get-composite-price/types';
181
+ export type { GetCompositeAvailability, GetCompositeAvailabilityParams, GetCompositeAvailabilityResponse, CFCompositeSlot, } from './actions/get-composite-availability/types';
178
182
  export type { SetProductModifierSelections, SetProductModifierSelectionsParams, SetProductModifierSelectionsResponse, } from './actions/set-product-modifier-selections/types';
179
183
  export type { SetActiveProductFee, SetActiveProductFeeParams, SetActiveProductFeeResponse, } from './actions/set-active-product-fee/types';
180
184
  export type { SetActiveProductDiscount, SetActiveProductDiscountParams, SetActiveProductDiscountResponse, } from './actions/set-active-product-discount/types';
181
185
  export type { GetActiveProduct, GetActiveProductResponse } from './actions/get-active-product/types';
182
186
  export type { SetActiveProduct, SetActiveProductParams, SetActiveProductResponse, } from './actions/set-active-product/types';
183
- export type { AdjustInventory, AdjustInventoryParams, AdjustInventoryResponse, ManualStockReason, } from './actions/adjust-inventory/types';
184
- export type { CustomStockActionPayload, GetCustomStockActions, GetCustomStockActionsResponse, } from './actions/get-custom-stock-actions/types';
187
+ export type { AdjustInventory, AdjustInventoryParams, AdjustInventoryResponse } from './actions/adjust-inventory/types';
188
+ export type { SetVariantUnavailable, SetVariantUnavailableParams, SetVariantUnavailableResponse, } from './actions/set-variant-unavailable/types';
185
189
  export type { AddOrderNote, AddOrderNoteParams, AddOrderNoteResponse } from './actions/add-order-note/types';
186
190
  export type { AddCartFee, AddCartFeeParams, AddCartFeeResponse } from './actions/add-cart-fee/types';
187
191
  export type { ClearCart, ClearCartResponse } from './actions/clear-cart/types';
package/dist/index.js CHANGED
@@ -28,13 +28,15 @@ import { updateCartItemQuantity } from './actions/update-cart-item-quantity/acti
28
28
  import { addProductNote } from './actions/add-product-note/action';
29
29
  import { addProductFee } from './actions/add-product-fee/action';
30
30
  import { getProductModifierSelections } from './actions/get-product-modifier-selections/action';
31
+ import { getCompositePrice } from './actions/get-composite-price/action';
32
+ import { getCompositeAvailability } from './actions/get-composite-availability/action';
31
33
  import { setProductModifierSelections } from './actions/set-product-modifier-selections/action';
32
34
  import { setActiveProductFee } from './actions/set-active-product-fee/action';
33
35
  import { setActiveProductDiscount } from './actions/set-active-product-discount/action';
34
36
  import { getActiveProduct } from './actions/get-active-product/action';
35
37
  import { setActiveProduct } from './actions/set-active-product/action';
36
38
  import { adjustInventory } from './actions/adjust-inventory/action';
37
- import { getCustomStockActions } from './actions/get-custom-stock-actions/action';
39
+ import { setVariantUnavailable } from './actions/set-variant-unavailable/action';
38
40
  // Order Actions
39
41
  import { addOrderNote } from './actions/add-order-note/action';
40
42
  import { addCartFee } from './actions/add-cart-fee/action';
@@ -175,13 +177,15 @@ export const command = {
175
177
  addProductNote,
176
178
  addProductFee,
177
179
  getProductModifierSelections,
180
+ getCompositePrice,
181
+ getCompositeAvailability,
178
182
  setProductModifierSelections,
179
183
  setActiveProductFee,
180
184
  setActiveProductDiscount,
181
185
  getActiveProduct,
182
186
  setActiveProduct,
183
187
  adjustInventory,
184
- getCustomStockActions,
188
+ setVariantUnavailable,
185
189
  // Order Actions
186
190
  addOrderNote,
187
191
  addCartFee,
@@ -13,6 +13,8 @@ import { mockRemoveOrderNote } from '../../actions/remove-order-note/mock';
13
13
  import { mockAddProductDiscount } from '../../actions/add-product-discount/mock';
14
14
  import { mockAddProductFee } from '../../actions/add-product-fee/mock';
15
15
  import { mockGetProductModifierSelections } from '../../actions/get-product-modifier-selections/mock';
16
+ import { mockGetCompositePrice } from '../../actions/get-composite-price/mock';
17
+ import { mockGetCompositeAvailability } from '../../actions/get-composite-availability/mock';
16
18
  import { mockSetProductModifierSelections } from '../../actions/set-product-modifier-selections/mock';
17
19
  import { mockRemoveProductDiscount } from '../../actions/remove-product-discount/mock';
18
20
  import { mockRemoveProductFee } from '../../actions/remove-product-fee/mock';
@@ -26,7 +28,7 @@ import { mockRemoveProductFromCart } from '../../actions/remove-product-from-car
26
28
  import { mockRemoveCartDiscount } from '../../actions/remove-cart-discount/mock';
27
29
  import { mockUpdateCartItemQuantity } from '../../actions/update-cart-item-quantity/mock';
28
30
  import { mockAdjustInventory } from '../../actions/adjust-inventory/mock';
29
- import { mockGetCustomStockActions } from '../../actions/get-custom-stock-actions/mock';
31
+ import { mockSetVariantUnavailable } from '../../actions/set-variant-unavailable/mock';
30
32
  import { mockAssignCustomer } from '../../actions/assign-customer/mock';
31
33
  import { mockAuthenticateUser } from '../../actions/authenticate-user/mock';
32
34
  import { mockCalculateRefundTotal } from '../../actions/calculate-refund-total/mock';
@@ -123,6 +125,8 @@ export const RENDER_MOCKS = {
123
125
  addProductDiscount: mockAddProductDiscount,
124
126
  addProductFee: mockAddProductFee,
125
127
  getProductModifierSelections: mockGetProductModifierSelections,
128
+ getCompositePrice: mockGetCompositePrice,
129
+ getCompositeAvailability: mockGetCompositeAvailability,
126
130
  setProductModifierSelections: mockSetProductModifierSelections,
127
131
  setActiveProductFee: mockSetActiveProductFee,
128
132
  setActiveProductDiscount: mockSetActiveProductDiscount,
@@ -133,7 +137,7 @@ export const RENDER_MOCKS = {
133
137
  removeProductFromCart: mockRemoveProductFromCart,
134
138
  updateCartItemQuantity: mockUpdateCartItemQuantity,
135
139
  adjustInventory: mockAdjustInventory,
136
- getCustomStockActions: mockGetCustomStockActions,
140
+ setVariantUnavailable: mockSetVariantUnavailable,
137
141
  assignCustomer: mockAssignCustomer,
138
142
  authenticateUser: mockAuthenticateUser,
139
143
  calculateRefundTotal: mockCalculateRefundTotal,
@@ -1,4 +1,4 @@
1
- import type { ExampleFunction, GetProducts, AddCustomSale, EditCustomSale, GetCustomers, AssignCustomer, AddCustomer, EditCustomer, GetCategories, GetOrders, GetRefunds, GetTaxTables, GetCustomStockActions, AddProductDiscount, AddProductToCart, RemoveProductFromCart, UpdateCartItemQuantity, AddCartDiscount, GetContext, GetFinalContext, AddProductNote, AddProductFee, GetProductModifierSelections, SetProductModifierSelections, SetActiveProductFee, SetActiveProductDiscount, GetActiveProduct, SetActiveProduct, AdjustInventory, AddOrderNote, AddCartFee, ClearCart, ParkOrder, ResumeParkedOrder, DeleteParkedOrder, VoidOrder, InitiateRefund, CashPayment, GetCashRoundingAmount, TapToPayPayment, TerminalPayment, ExtensionPayment, RedeemPayment, AddNonRevenueItem, AddCustomerNote, RemoveCustomerNote, RemoveCustomerFromCart, GoToStationHome, OpenCashDrawer, ShowNotification, ShowConfirmation, AuthenticateUser, PartialPayment, SwitchUser, SetRefundStockAction, SelectAllRefundItems, ResetRefundDetails, CalculateRefundTotal, GetRemainingRefundableQuantities, ProcessPartialRefund, RedeemRefund, GetRefundPlan, CheckPermission, GetCurrentCart, Print, SetActiveOrder, GetCustomTables, GetCustomTableData, UpsertCustomTableData, DeleteCustomTableData, GetCustomExtensions, GetCurrentCompanyCustomExtensions, GetCustomExtensionCustomTables, GetCustomTableFields, GetSecretsKeys, GetSecretVal, SetSecretVal, GetUsers, GetRoles, RemoveCartDiscount, GetActiveOrder, GetActiveCustomer, SetActiveCustomer, GetActiveOutlet, GetActiveStation, GetActiveSession, GetActiveUser, SetActiveUser, SetActiveRefund, RemoveProductDiscount, RemoveProductFee, RemoveProductNote, RemoveCartFee, RemoveOrderNote, RemoveCustomSale, RemoveNonRevenueItem, CanTransition, GetAvailableTransitions, ApplyTransition, IntegrationPayment, GetSmartGridLayout, SaveSmartGridLayout, SendEmail, SendSms, GetBookingResources, GetBookingAvailability, GetBookings, HoldBooking, AddBookingToCart, RemoveBookingFromCart, CancelBooking, CreatePaymentLink, ChargeMoto, GetTimeClockStatus } from '../../index';
1
+ import type { ExampleFunction, GetProducts, AddCustomSale, EditCustomSale, GetCustomers, AssignCustomer, AddCustomer, EditCustomer, GetCategories, GetOrders, GetRefunds, GetTaxTables, AddProductDiscount, AddProductToCart, RemoveProductFromCart, UpdateCartItemQuantity, AddCartDiscount, GetContext, GetFinalContext, AddProductNote, AddProductFee, GetProductModifierSelections, GetCompositePrice, GetCompositeAvailability, SetProductModifierSelections, SetActiveProductFee, SetActiveProductDiscount, GetActiveProduct, SetActiveProduct, AdjustInventory, SetVariantUnavailable, AddOrderNote, AddCartFee, ClearCart, ParkOrder, ResumeParkedOrder, DeleteParkedOrder, VoidOrder, InitiateRefund, CashPayment, GetCashRoundingAmount, TapToPayPayment, TerminalPayment, ExtensionPayment, RedeemPayment, AddNonRevenueItem, AddCustomerNote, RemoveCustomerNote, RemoveCustomerFromCart, GoToStationHome, OpenCashDrawer, ShowNotification, ShowConfirmation, AuthenticateUser, PartialPayment, SwitchUser, SetRefundStockAction, SelectAllRefundItems, ResetRefundDetails, CalculateRefundTotal, GetRemainingRefundableQuantities, ProcessPartialRefund, RedeemRefund, GetRefundPlan, CheckPermission, GetCurrentCart, Print, SetActiveOrder, GetCustomTables, GetCustomTableData, UpsertCustomTableData, DeleteCustomTableData, GetCustomExtensions, GetCurrentCompanyCustomExtensions, GetCustomExtensionCustomTables, GetCustomTableFields, GetSecretsKeys, GetSecretVal, SetSecretVal, GetUsers, GetRoles, RemoveCartDiscount, GetActiveOrder, GetActiveCustomer, SetActiveCustomer, GetActiveOutlet, GetActiveStation, GetActiveSession, GetActiveUser, SetActiveUser, SetActiveRefund, RemoveProductDiscount, RemoveProductFee, RemoveProductNote, RemoveCartFee, RemoveOrderNote, RemoveCustomSale, RemoveNonRevenueItem, CanTransition, GetAvailableTransitions, ApplyTransition, IntegrationPayment, GetSmartGridLayout, SaveSmartGridLayout, SendEmail, SendSms, GetBookingResources, GetBookingAvailability, GetBookings, HoldBooking, AddBookingToCart, RemoveBookingFromCart, CancelBooking, CreatePaymentLink, ChargeMoto, GetTimeClockStatus } from '../../index';
2
2
  export interface RenderProviderActions {
3
3
  exampleFunction: ExampleFunction;
4
4
  getProducts: GetProducts;
@@ -29,13 +29,15 @@ export interface RenderProviderActions {
29
29
  addProductNote: AddProductNote;
30
30
  addProductFee: AddProductFee;
31
31
  getProductModifierSelections: GetProductModifierSelections;
32
+ getCompositePrice: GetCompositePrice;
33
+ getCompositeAvailability: GetCompositeAvailability;
32
34
  setProductModifierSelections: SetProductModifierSelections;
33
35
  setActiveProductFee: SetActiveProductFee;
34
36
  setActiveProductDiscount: SetActiveProductDiscount;
35
37
  getActiveProduct: GetActiveProduct;
36
38
  setActiveProduct: SetActiveProduct;
37
39
  adjustInventory: AdjustInventory;
38
- getCustomStockActions: GetCustomStockActions;
40
+ setVariantUnavailable: SetVariantUnavailable;
39
41
  addOrderNote: AddOrderNote;
40
42
  addCartFee: AddCartFee;
41
43
  clearCart: ClearCart;
@@ -0,0 +1,17 @@
1
+ import type { TopicEvent } from '../../../types';
2
+ /**
3
+ * Payload for catalog-visibility-changed: a product was hidden or shown at an outlet. `getProducts` leaves hidden
4
+ * products out and a composite offers only items sold at the till's outlet — debounce, then re-ask `getProducts`.
5
+ */
6
+ export interface CatalogVisibilityChangedPayload {
7
+ visibility: {
8
+ _id: string;
9
+ productId: string;
10
+ outletId: string;
11
+ isDeleted?: boolean;
12
+ };
13
+ }
14
+ /**
15
+ * Typed event for catalog-visibility-changed
16
+ */
17
+ export type CatalogVisibilityChangedEvent = TopicEvent<CatalogVisibilityChangedPayload>;
@@ -0,0 +1,17 @@
1
+ import type { TopicEvent } from '../../../types';
2
+ /**
3
+ * Payload for composite-changed (FT-83): a part or item of a composite arrived. A save replaces all of a composite's
4
+ * parts and items at once (old rows come as `isDeleted: true`, new ones with new ids), so one save fires this many
5
+ * times — debounce, then re-ask `getProducts`; never patch `product.composite` from the row.
6
+ */
7
+ export interface CompositeChangedPayload {
8
+ compositeRow: {
9
+ _id: string;
10
+ compositeProductId: string;
11
+ isDeleted?: boolean;
12
+ };
13
+ }
14
+ /**
15
+ * Typed event for composite-changed
16
+ */
17
+ export type CompositeChangedEvent = TopicEvent<CompositeChangedPayload>;
@@ -2,6 +2,6 @@
2
2
  * Products Topic Definition
3
3
  * Defines the products topic and its available event types
4
4
  */
5
- import type { TopicDefinition } from "../../types";
5
+ import type { TopicDefinition } from '../../types';
6
6
  export declare const productsTopic: TopicDefinition;
7
- export * from "./types";
7
+ export * from './types';
@@ -3,31 +3,46 @@
3
3
  * Defines the products topic and its available event types
4
4
  */
5
5
  export const productsTopic = {
6
- id: "products",
7
- name: "Products",
8
- description: "Topic for product-related events",
6
+ id: 'products',
7
+ name: 'Products',
8
+ description: 'Topic for product-related events',
9
9
  eventTypes: [
10
10
  {
11
- id: "product-created",
12
- name: "Product Created",
13
- description: "Published when a new product is synced/created"
11
+ id: 'product-created',
12
+ name: 'Product Created',
13
+ description: 'Published when a new product is synced/created',
14
14
  },
15
15
  {
16
- id: "product-updated",
17
- name: "Product Updated",
18
- description: "Published when a product is synced/updated"
16
+ id: 'product-updated',
17
+ name: 'Product Updated',
18
+ description: 'Published when a product is synced/updated',
19
19
  },
20
20
  {
21
- id: "set-active-product",
22
- name: "Set Active Product",
23
- description: "Published when a product is set as the active product"
21
+ id: 'composite-changed',
22
+ name: 'Composite Changed',
23
+ description: "Published when a composite's part or item is synced; re-ask getProducts",
24
24
  },
25
25
  {
26
- id: "get-active-product",
27
- name: "Get Active Product",
28
- description: "Published when a product is retrieved as the active product"
29
- }
30
- ]
26
+ id: 'catalog-visibility-changed',
27
+ name: 'Catalog Visibility Changed',
28
+ description: 'Published when a product is hidden or shown at an outlet; re-ask getProducts',
29
+ },
30
+ {
31
+ id: 'inventory-changed',
32
+ name: 'Inventory Changed',
33
+ description: "Published when a variant's stock at an outlet syncs; re-ask getProducts",
34
+ },
35
+ {
36
+ id: 'set-active-product',
37
+ name: 'Set Active Product',
38
+ description: 'Published when a product is set as the active product',
39
+ },
40
+ {
41
+ id: 'get-active-product',
42
+ name: 'Get Active Product',
43
+ description: 'Published when a product is retrieved as the active product',
44
+ },
45
+ ],
31
46
  };
32
47
  // Re-export types
33
- export * from "./types";
48
+ export * from './types';
@@ -0,0 +1,18 @@
1
+ import type { TopicEvent } from '../../../types';
2
+ /**
3
+ * Payload for inventory-changed (B35): a variant's stock at an outlet moved. A composite may turn Sold out or
4
+ * Unavailable by its components' shelves — debounce, then re-ask `getProducts`.
5
+ */
6
+ export interface InventoryChangedPayload {
7
+ inventory: {
8
+ _id: string;
9
+ variantId: string;
10
+ outletId: string;
11
+ quantity?: number | null;
12
+ manageStock?: boolean;
13
+ };
14
+ }
15
+ /**
16
+ * Typed event for inventory-changed
17
+ */
18
+ export type InventoryChangedEvent = TopicEvent<InventoryChangedPayload>;
@@ -2,13 +2,19 @@
2
2
  * Products Topic Types
3
3
  * Aggregated types for all product-related events
4
4
  */
5
- export * from "./product-created/types";
6
- export * from "./product-updated/types";
7
- export * from "./product-set-active/types";
8
- export * from "./product-get-active/types";
9
- import type { ProductCreatedPayload } from "./product-created/types";
10
- import type { ProductUpdatedPayload } from "./product-updated/types";
11
- import type { ProductSetActivePayload } from "./product-set-active/types";
12
- import type { ProductGetActivePayload } from "./product-get-active/types";
13
- export type ProductsEventPayload = ProductCreatedPayload | ProductUpdatedPayload | ProductSetActivePayload | ProductGetActivePayload;
14
- export type ProductsEventType = "product-created" | "product-updated" | "set-active-product" | "get-active-product";
5
+ export * from './product-created/types';
6
+ export * from './product-updated/types';
7
+ export * from './composite-changed/types';
8
+ export * from './catalog-visibility-changed/types';
9
+ export * from './inventory-changed/types';
10
+ export * from './product-set-active/types';
11
+ export * from './product-get-active/types';
12
+ import type { ProductCreatedPayload } from './product-created/types';
13
+ import type { ProductUpdatedPayload } from './product-updated/types';
14
+ import type { CompositeChangedPayload } from './composite-changed/types';
15
+ import type { CatalogVisibilityChangedPayload } from './catalog-visibility-changed/types';
16
+ import type { InventoryChangedPayload } from './inventory-changed/types';
17
+ import type { ProductSetActivePayload } from './product-set-active/types';
18
+ import type { ProductGetActivePayload } from './product-get-active/types';
19
+ export type ProductsEventPayload = ProductCreatedPayload | ProductUpdatedPayload | CompositeChangedPayload | CatalogVisibilityChangedPayload | InventoryChangedPayload | ProductSetActivePayload | ProductGetActivePayload;
20
+ export type ProductsEventType = 'product-created' | 'product-updated' | 'composite-changed' | 'catalog-visibility-changed' | 'inventory-changed' | 'set-active-product' | 'get-active-product';
@@ -3,7 +3,10 @@
3
3
  * Aggregated types for all product-related events
4
4
  */
5
5
  // Re-export all event types
6
- export * from "./product-created/types";
7
- export * from "./product-updated/types";
8
- export * from "./product-set-active/types";
9
- export * from "./product-get-active/types";
6
+ export * from './product-created/types';
7
+ export * from './product-updated/types';
8
+ export * from './composite-changed/types';
9
+ export * from './catalog-visibility-changed/types';
10
+ export * from './inventory-changed/types';
11
+ export * from './product-set-active/types';
12
+ export * from './product-get-active/types';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@final-commerce/command-frame",
3
- "version": "0.9.0",
3
+ "version": "0.10.0-staging.2",
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.5.0"
60
+ "@final-commerce/common": "2.6.0-staging.1"
61
61
  },
62
62
  "devDependencies": {
63
63
  "@commitlint/cli": "^19.0.0",