@final-commerce/command-frame 0.10.0-staging.4 → 0.10.0-staging.6

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 (27) hide show
  1. package/dist/actions/add-product-to-cart/mock.d.ts +3 -0
  2. package/dist/actions/add-product-to-cart/mock.js +7 -5
  3. package/dist/actions/adjust-inventory/mock.js +20 -0
  4. package/dist/actions/adjust-inventory/types.d.ts +13 -0
  5. package/dist/actions/adjust-inventory/types.js +1 -0
  6. package/dist/actions/get-composite-availability/mock.js +4 -2
  7. package/dist/actions/get-composite-price/mock.js +11 -7
  8. package/dist/actions/get-composite-price/types.d.ts +13 -4
  9. package/dist/actions/get-custom-stock-actions/action.d.ts +5 -0
  10. package/dist/actions/get-custom-stock-actions/action.js +7 -0
  11. package/dist/actions/get-custom-stock-actions/mock.d.ts +2 -0
  12. package/dist/actions/get-custom-stock-actions/mock.js +11 -0
  13. package/dist/actions/get-custom-stock-actions/types.d.ts +12 -0
  14. package/dist/actions/get-custom-stock-actions/types.js +2 -0
  15. package/dist/actions/set-variant-unavailable/mock.js +14 -4
  16. package/dist/actions/set-variant-unavailable/types.d.ts +2 -0
  17. package/dist/actions/update-cart-item-quantity/mock.js +18 -2
  18. package/dist/index.d.ts +3 -1
  19. package/dist/index.js +2 -0
  20. package/dist/projects/render/mocks.js +2 -0
  21. package/dist/projects/render/types.d.ts +2 -1
  22. package/dist/pubsub/topics/products/index.js +5 -0
  23. package/dist/pubsub/topics/products/types.d.ts +4 -2
  24. package/dist/pubsub/topics/products/types.js +1 -0
  25. package/dist/pubsub/topics/products/variant-unavailability-changed/types.d.ts +19 -0
  26. package/dist/pubsub/topics/products/variant-unavailability-changed/types.js +1 -0
  27. package/package.json +2 -2
@@ -7,8 +7,11 @@ export declare function compositePicksRefusal(composite: CFComposite, picks: CFC
7
7
  * left empty is not listed. A copy — change both in one step. */
8
8
  export declare function mockPicksStillNeeded(composite: CFComposite, picks: CFCompositePick[]): {
9
9
  partId: string;
10
+ name: string | null;
10
11
  needed: number;
11
12
  }[];
13
+ /** The parts whose picks already reach their `max` — a picker disables "+" on them. A copy of the host's. */
14
+ export declare const mockFullPartIds: (composite: CFComposite, picks: CFCompositePick[]) => string[];
12
15
  type MockCompositeLine = {
13
16
  price: number;
14
17
  components: CFCartLineComponent[];
@@ -275,17 +275,19 @@ function mockSelectionsRefusal(modifiers, selections) {
275
275
  }
276
276
  return null;
277
277
  }
278
+ const mockPartPicks = (part, picks) => picks
279
+ .filter((pick) => part.items.some((item) => item._id === pick.itemId))
280
+ .reduce((sum, pick) => sum + (pick.quantity ?? 1), 0);
278
281
  /** The host's `picksStillNeeded` (kaching `resolveComposite.ts`): per part, what is left of its min; an Optional part
279
282
  * left empty is not listed. A copy — change both in one step. */
280
283
  export function mockPicksStillNeeded(composite, picks) {
281
284
  return composite.parts.flatMap((part) => {
282
- const count = picks
283
- .filter((pick) => part.items.some((item) => item._id === pick.itemId))
284
- .reduce((sum, pick) => sum + (pick.quantity ?? 1), 0);
285
- const needed = Math.max(0, part.min - count);
286
- return needed > 0 ? [{ partId: part._id, needed }] : [];
285
+ const needed = Math.max(0, part.min - mockPartPicks(part, picks));
286
+ return needed > 0 ? [{ partId: part._id, name: part.name, needed }] : [];
287
287
  });
288
288
  }
289
+ /** The parts whose picks already reach their `max` — a picker disables "+" on them. A copy of the host's. */
290
+ export const mockFullPartIds = (composite, picks) => composite.parts.filter((part) => mockPartPicks(part, picks) >= part.max).map((part) => part._id);
289
291
  /** kaching `buildCompositeLine` (B31): a component's share of ONE composite = the composite price split by the picked
290
292
  * variant's price × units (all 0 → units) + its own upcharge; Σ = price. Item modifiers flat with `componentIndex`
291
293
  * and quantity × the component's units. A copy — change both in one step. */
@@ -1,6 +1,26 @@
1
1
  import { MOCK_PRODUCTS } from '../../demo/database';
2
+ // Same rules and messages as the kaching handler.
3
+ const STOCK_TYPE_BY_REASON = {
4
+ STOCK_RECEIVED: 'add',
5
+ RESTOCK_RETURN: 'add',
6
+ DAMAGE: 'subtract',
7
+ THEFT: 'subtract',
8
+ LOSS: 'subtract',
9
+ INVENTORY_RECOUNT: 'set',
10
+ };
2
11
  export const mockAdjustInventory = async (params) => {
3
12
  console.log('[Mock] adjustInventory called', params);
13
+ if (params?.specificAction != null) {
14
+ const { specificAction, stockType, customActionId } = params;
15
+ if (customActionId)
16
+ throw new Error('Pass either customActionId or specificAction, not both');
17
+ const reasonStockType = STOCK_TYPE_BY_REASON[specificAction];
18
+ if (!reasonStockType)
19
+ throw new Error(`Unknown specificAction: ${specificAction}`);
20
+ if (reasonStockType !== stockType) {
21
+ throw new Error(`specificAction ${specificAction} requires stockType '${reasonStockType}', got '${stockType}'`);
22
+ }
23
+ }
4
24
  let newStock = 0;
5
25
  // Real handler keys off variantId (falling back to the active product's
6
26
  // variant); mirror that here by finding the owning product for the variant.
@@ -1,3 +1,5 @@
1
+ /** The manual reasons a person can pick, as hub-api's InventorySpecificActionType names them. */
2
+ export type ManualStockReason = 'STOCK_RECEIVED' | 'RESTOCK_RETURN' | 'DAMAGE' | 'THEFT' | 'LOSS' | 'INVENTORY_RECOUNT';
1
3
  export interface AdjustInventoryParams {
2
4
  /** String to preserve precision. */
3
5
  amount: string;
@@ -5,6 +7,17 @@ export interface AdjustInventoryParams {
5
7
  stockType: 'add' | 'subtract' | 'set';
6
8
  /** Variant to adjust. Omit to use the active product's selected variant. */
7
9
  variantId?: string;
10
+ /**
11
+ * A company action from getCustomStockActions. The movement is recorded under its name;
12
+ * `stockType` must match its baseAction (ADD → 'add', REMOVE → 'subtract', RECOUNT → 'set').
13
+ */
14
+ customActionId?: string;
15
+ /**
16
+ * A built-in reason to record instead of the host's default for `stockType`. Must match it:
17
+ * STOCK_RECEIVED / RESTOCK_RETURN → 'add', DAMAGE / THEFT / LOSS → 'subtract', INVENTORY_RECOUNT → 'set'.
18
+ * Rejected together with `customActionId`.
19
+ */
20
+ specificAction?: ManualStockReason;
8
21
  }
9
22
  export interface AdjustInventoryResponse {
10
23
  success: boolean;
@@ -1 +1,2 @@
1
+ // Adjust Inventory Types
1
2
  export {};
@@ -5,9 +5,11 @@ export const mockGetCompositeAvailability = async (params) => {
5
5
  console.log('[Mock] getCompositeAvailability called', params);
6
6
  const timestamp = new Date().toISOString();
7
7
  const refuse = (reason) => ({ success: false, reason, slots: [], timeZone: null, timestamp });
8
- const product = MOCK_PRODUCTS.find((candidate) => candidate.variants.some((v) => v._id === params?.variantId));
8
+ if (!params?.variantId)
9
+ return refuse('variantId is required');
10
+ const product = MOCK_PRODUCTS.find((candidate) => candidate.variants.some((v) => v._id === params.variantId));
9
11
  if (!product)
10
- throw new Error(`Variant with ID ${params?.variantId} not found`);
12
+ return refuse(`Variant with ID ${params.variantId} not found`);
11
13
  if (!product.composite)
12
14
  return refuse(`${product.name} is not a composite: it takes no composite picks`);
13
15
  const composite = mockCompositeAtOutlet(product.composite);
@@ -1,6 +1,6 @@
1
1
  import { MOCK_CART, MOCK_PRODUCTS, buildModifierRows, mockCompositeAtOutlet, mockCompositeStockRefusal, mockHiddenProductIds, } from '../../demo/database';
2
- import { compositePicksRefusal, mockCompositeLine, mockNeedsDateRefusal, mockPicksStillNeeded, } from '../add-product-to-cart/mock';
3
- // The same line the mock addProductToCart builds, priced for ONE composite and added to nothing.
2
+ import { compositePicksRefusal, mockCompositeLine, mockFullPartIds, mockNeedsDateRefusal, mockPicksStillNeeded, } from '../add-product-to-cart/mock';
3
+ // The same line the mock addProductToCart builds, priced for `quantity` composites and added to nothing.
4
4
  export const mockGetCompositePrice = async (params) => {
5
5
  console.log('[Mock] getCompositePrice called', params);
6
6
  const timestamp = new Date().toISOString();
@@ -12,6 +12,7 @@ export const mockGetCompositePrice = async (params) => {
12
12
  success: false,
13
13
  reason: `${product.name} is not a composite: it takes no composite picks`,
14
14
  missing: [],
15
+ fullPartIds: [],
15
16
  timestamp,
16
17
  };
17
18
  }
@@ -19,19 +20,22 @@ export const mockGetCompositePrice = async (params) => {
19
20
  // What this outlet sells (the host's catalog-visibility rules).
20
21
  const composite = mockCompositeAtOutlet(product.composite, undefined, params.compositeSlot);
21
22
  const missing = mockPicksStillNeeded(composite, picks);
23
+ const fullPartIds = mockFullPartIds(composite, picks);
24
+ const quantity = params.quantity ?? 1;
22
25
  if (mockHiddenProductIds().has(product._id)) {
23
- return { success: false, reason: `${product.name} is not sold at this outlet`, missing, timestamp };
26
+ return { success: false, reason: `${product.name} is not sold at this outlet`, missing, fullPartIds, timestamp };
24
27
  }
25
28
  const line = mockNeedsDateRefusal(product.name, composite, params.compositeSlot) ??
26
29
  compositePicksRefusal(composite, picks) ??
27
30
  mockCompositeLine(composite, picks, !product.taxTable);
28
31
  if (typeof line === 'string')
29
- return { success: false, reason: line, missing, timestamp };
32
+ return { success: false, reason: line, missing, fullPartIds, timestamp };
30
33
  const shortOfStock = mockCompositeStockRefusal([
31
34
  ...MOCK_CART.products,
32
- { variantId: params.variantId, quantity: 1, components: line.components },
35
+ { variantId: params.variantId, quantity, components: line.components },
33
36
  ]);
34
37
  if (shortOfStock)
35
- return { success: false, reason: shortOfStock, missing, timestamp };
36
- return { success: true, total: line.price + buildModifierRows(line.modifiers, 1).modifiersTotal, missing, timestamp };
38
+ return { success: false, reason: shortOfStock, missing, fullPartIds, timestamp };
39
+ const unitTotal = line.price + buildModifierRows(line.modifiers, 1).modifiersTotal;
40
+ return { success: true, total: unitTotal * quantity, unitTotal, missing, fullPartIds, timestamp };
37
41
  };
@@ -8,24 +8,33 @@ export interface GetCompositePriceParams {
8
8
  compositeSlot?: {
9
9
  startAt: string;
10
10
  };
11
+ /** How many of this composite, as `addProductToCart({ quantity })` takes it. Defaults to 1. */
12
+ quantity?: number;
11
13
  }
12
14
  export interface GetCompositePriceResponse {
13
15
  success: boolean;
14
16
  /** Why these picks are refused — the same refusal and text `addProductToCart` would give. */
15
17
  reason?: string;
16
18
  /**
17
- * What ONE composite with these picks adds to the cart, minor units: the line price the host would build plus the
18
- * picked items' modifiers. Before discounts, fees and tax. Show it; do not recompute it. Absent when refused.
19
+ * What `quantity` composites with these picks add to the cart, minor units: `unitTotal` × `quantity`. Before
20
+ * discounts, fees and tax. Show it; do not recompute it. Absent when refused.
19
21
  */
20
22
  total?: number;
23
+ /** What ONE composite with these picks adds: the line price the host would build plus the picked items' modifiers,
24
+ * minor units. Absent when refused. */
25
+ unitTotal?: number;
21
26
  /**
22
- * The parts that still need picks before the composite can be added, and how many ("Pick 2 more"); empty when every
23
- * required part is filled. Decided by the host — keep Add to cart disabled while this is not empty.
27
+ * The parts that still need picks before the composite can be added, with the part's `name` (null = untitled) and
28
+ * how many ("Pick 2 more side"); empty when every required part is filled. Decided by the host — keep Add to cart
29
+ * disabled while this is not empty.
24
30
  */
25
31
  missing: {
26
32
  partId: string;
33
+ name: string | null;
27
34
  needed: number;
28
35
  }[];
36
+ /** The parts whose picks already reach their `max` — disable "+" on them. Decided by the host. */
37
+ fullPartIds: string[];
29
38
  timestamp: string;
30
39
  }
31
40
  export type GetCompositePrice = (params: GetCompositePriceParams) => Promise<GetCompositePriceResponse>;
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Get custom stock actions action — the company's own stock adjustment actions.
3
+ */
4
+ import type { GetCustomStockActions } from './types';
5
+ export declare const getCustomStockActions: GetCustomStockActions;
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Get custom stock actions action — the company's own stock adjustment actions.
3
+ */
4
+ import { commandFrameClient } from '../../client';
5
+ export const getCustomStockActions = async () => {
6
+ return await commandFrameClient.call('getCustomStockActions');
7
+ };
@@ -0,0 +1,2 @@
1
+ import { GetCustomStockActions } from './types';
2
+ export declare const mockGetCustomStockActions: GetCustomStockActions;
@@ -0,0 +1,11 @@
1
+ export const mockGetCustomStockActions = async () => {
2
+ console.log('[Mock] getCustomStockActions called');
3
+ return {
4
+ customStockActions: [
5
+ { _id: 'mock_custom_action_1', name: 'Opening stock', baseAction: 'ADD' },
6
+ { _id: 'mock_custom_action_2', name: 'Exhibition loan', baseAction: 'REMOVE' },
7
+ { _id: 'mock_custom_action_3', name: 'Year-end count', baseAction: 'RECOUNT' },
8
+ ],
9
+ timestamp: new Date().toISOString(),
10
+ };
11
+ };
@@ -0,0 +1,12 @@
1
+ export interface CustomStockActionPayload {
2
+ _id: string;
3
+ /** Name the company gave the action, shown in pickers and stock history. */
4
+ name: string;
5
+ /** What the action does to stock — pass the matching `stockType` to adjustInventory. */
6
+ baseAction: 'ADD' | 'REMOVE' | 'RECOUNT';
7
+ }
8
+ export interface GetCustomStockActionsResponse {
9
+ customStockActions: CustomStockActionPayload[];
10
+ timestamp: string;
11
+ }
12
+ export type GetCustomStockActions = () => Promise<GetCustomStockActionsResponse>;
@@ -0,0 +1,2 @@
1
+ // Get Custom Stock Actions Types
2
+ export {};
@@ -1,12 +1,22 @@
1
1
  import { MOCK_OUTLET, MOCK_PRODUCTS } from '../../demo/database';
2
2
  export const mockSetVariantUnavailable = async (params) => {
3
3
  console.log('[Mock] setVariantUnavailable called', params);
4
- if (!params?.variantId)
5
- throw new Error('variantId is required');
4
+ const answer = (success, reason) => ({
5
+ success,
6
+ reason,
7
+ variantId: params?.variantId ?? '',
8
+ outletId: MOCK_OUTLET.id,
9
+ unavailable: !!params?.unavailable,
10
+ timestamp: new Date().toISOString(),
11
+ });
12
+ // Same refusals and texts as the host, resolved instead of thrown.
13
+ if (!params?.variantId || typeof params.unavailable !== 'boolean') {
14
+ return answer(false, 'variantId and unavailable are required');
15
+ }
6
16
  const { variantId, unavailable } = params;
7
17
  const variant = MOCK_PRODUCTS.flatMap((product) => product.variants).find((v) => v._id === variantId);
8
18
  if (!variant)
9
- throw new Error(`Variant with ID ${variantId} not found`);
19
+ return answer(false, `Variant with ID ${variantId} not found`);
10
20
  variant.unavailable = unavailable;
11
- return { success: true, variantId, outletId: MOCK_OUTLET.id, unavailable, timestamp: new Date().toISOString() };
21
+ return answer(true);
12
22
  };
@@ -8,7 +8,9 @@ export interface SetVariantUnavailableParams {
8
8
  unavailable: boolean;
9
9
  }
10
10
  export interface SetVariantUnavailableResponse {
11
+ /** False when the host refused: missing params, no active outlet or company, unknown variant, offline. */
11
12
  success: boolean;
13
+ reason?: string;
12
14
  variantId: string;
13
15
  outletId: string;
14
16
  unavailable: boolean;
@@ -1,5 +1,5 @@
1
- import { MOCK_CART, MOCK_PRODUCTS, mockPublishEvent } from '../../demo/database';
2
- import { extendPrice, isValidQuantity } from '@final-commerce/common';
1
+ import { MOCK_BOOKINGS, MOCK_CART, MOCK_PRODUCTS, mockCompositeStockRefusal, mockPublishEvent, } from '../../demo/database';
2
+ import { ReservationStatus, extendPrice, isValidQuantity } from '@final-commerce/common';
3
3
  export const mockUpdateCartItemQuantity = (params) => {
4
4
  console.log('[Mock] updateCartItemQuantity called', params);
5
5
  if (!params?.internalId) {
@@ -16,6 +16,10 @@ export const mockUpdateCartItemQuantity = (params) => {
16
16
  }
17
17
  const product = MOCK_CART.products[productIndex];
18
18
  const previousQuantity = product.quantity;
19
+ // Same refusal and text as the host: a line holding bookings keeps its quantity — its holds are that many seats.
20
+ if (quantity !== 0 && MOCK_CART.reservations?.some((reservation) => reservation.lineItemInternalId === internalId)) {
21
+ throw new Error(`${product.name} holds a booking — its quantity is the seats held: remove the line and add it again with the new quantity (addProductToCart with compositeSlot)`);
22
+ }
19
23
  // The line resolved its unit when it was added; a piece-sold line has none and stays whole.
20
24
  if (quantity !== 0 &&
21
25
  !isValidQuantity(quantity, (product.unit ?? { unitId: 'piece', ratioToBase: 1, precision: 0 }))) {
@@ -26,6 +30,12 @@ export const mockUpdateCartItemQuantity = (params) => {
26
30
  // If quantity is 0, remove the item
27
31
  if (quantity === 0) {
28
32
  MOCK_CART.products.splice(productIndex, 1);
33
+ // Like the host's `dropCompositeLine`: the line's reservations leave with it and their holds are released.
34
+ const linked = (MOCK_CART.reservations ?? []).filter((reservation) => reservation.lineItemInternalId === internalId);
35
+ MOCK_CART.reservations = (MOCK_CART.reservations ?? []).filter((reservation) => !linked.includes(reservation));
36
+ for (const booking of MOCK_BOOKINGS.filter(({ id }) => linked.some(({ bookingId }) => bookingId === id))) {
37
+ booking.status = ReservationStatus.CANCELLED;
38
+ }
29
39
  // Recalculate totals
30
40
  const lineTotal = extendPrice(product.price, previousQuantity);
31
41
  MOCK_CART.subtotal -= lineTotal;
@@ -50,6 +60,12 @@ export const mockUpdateCartItemQuantity = (params) => {
50
60
  if (quantity > previousQuantity && (source?.notSoldInPos || variant?.notSoldInPos)) {
51
61
  throw new Error(`${source?.name} is not sold in POS`);
52
62
  }
63
+ // A raised composite line draws on its components' shelves, like the host's `compositeStockRefusal`.
64
+ if (quantity > previousQuantity && product.components?.length) {
65
+ const refusal = mockCompositeStockRefusal(MOCK_CART.products.map((line) => (line.internalId === internalId ? { ...line, quantity } : line)));
66
+ if (refusal)
67
+ throw new Error(refusal);
68
+ }
53
69
  // Update quantity
54
70
  product.quantity = quantity;
55
71
  // Difference of the two ROUNDED extensions — rounding the delta itself would let the
package/dist/index.d.ts CHANGED
@@ -38,6 +38,7 @@ export declare const command: {
38
38
  readonly setActiveProduct: import(".").SetActiveProduct;
39
39
  readonly adjustInventory: import(".").AdjustInventory;
40
40
  readonly setVariantUnavailable: import(".").SetVariantUnavailable;
41
+ readonly getCustomStockActions: import(".").GetCustomStockActions;
41
42
  readonly addOrderNote: import(".").AddOrderNote;
42
43
  readonly addCartFee: import(".").AddCartFee;
43
44
  readonly getCurrentCart: import(".").GetCurrentCart;
@@ -184,8 +185,9 @@ export type { SetActiveProductFee, SetActiveProductFeeParams, SetActiveProductFe
184
185
  export type { SetActiveProductDiscount, SetActiveProductDiscountParams, SetActiveProductDiscountResponse, } from './actions/set-active-product-discount/types';
185
186
  export type { GetActiveProduct, GetActiveProductResponse } from './actions/get-active-product/types';
186
187
  export type { SetActiveProduct, SetActiveProductParams, SetActiveProductResponse, } from './actions/set-active-product/types';
187
- export type { AdjustInventory, AdjustInventoryParams, AdjustInventoryResponse } from './actions/adjust-inventory/types';
188
188
  export type { SetVariantUnavailable, SetVariantUnavailableParams, SetVariantUnavailableResponse, } from './actions/set-variant-unavailable/types';
189
+ export type { AdjustInventory, AdjustInventoryParams, AdjustInventoryResponse, ManualStockReason, } from './actions/adjust-inventory/types';
190
+ export type { CustomStockActionPayload, GetCustomStockActions, GetCustomStockActionsResponse, } from './actions/get-custom-stock-actions/types';
189
191
  export type { AddOrderNote, AddOrderNoteParams, AddOrderNoteResponse } from './actions/add-order-note/types';
190
192
  export type { AddCartFee, AddCartFeeParams, AddCartFeeResponse } from './actions/add-cart-fee/types';
191
193
  export type { ClearCart, ClearCartResponse } from './actions/clear-cart/types';
package/dist/index.js CHANGED
@@ -37,6 +37,7 @@ import { getActiveProduct } from './actions/get-active-product/action';
37
37
  import { setActiveProduct } from './actions/set-active-product/action';
38
38
  import { adjustInventory } from './actions/adjust-inventory/action';
39
39
  import { setVariantUnavailable } from './actions/set-variant-unavailable/action';
40
+ import { getCustomStockActions } from './actions/get-custom-stock-actions/action';
40
41
  // Order Actions
41
42
  import { addOrderNote } from './actions/add-order-note/action';
42
43
  import { addCartFee } from './actions/add-cart-fee/action';
@@ -186,6 +187,7 @@ export const command = {
186
187
  setActiveProduct,
187
188
  adjustInventory,
188
189
  setVariantUnavailable,
190
+ getCustomStockActions,
189
191
  // Order Actions
190
192
  addOrderNote,
191
193
  addCartFee,
@@ -29,6 +29,7 @@ import { mockRemoveCartDiscount } from '../../actions/remove-cart-discount/mock'
29
29
  import { mockUpdateCartItemQuantity } from '../../actions/update-cart-item-quantity/mock';
30
30
  import { mockAdjustInventory } from '../../actions/adjust-inventory/mock';
31
31
  import { mockSetVariantUnavailable } from '../../actions/set-variant-unavailable/mock';
32
+ import { mockGetCustomStockActions } from '../../actions/get-custom-stock-actions/mock';
32
33
  import { mockAssignCustomer } from '../../actions/assign-customer/mock';
33
34
  import { mockAuthenticateUser } from '../../actions/authenticate-user/mock';
34
35
  import { mockCalculateRefundTotal } from '../../actions/calculate-refund-total/mock';
@@ -138,6 +139,7 @@ export const RENDER_MOCKS = {
138
139
  updateCartItemQuantity: mockUpdateCartItemQuantity,
139
140
  adjustInventory: mockAdjustInventory,
140
141
  setVariantUnavailable: mockSetVariantUnavailable,
142
+ getCustomStockActions: mockGetCustomStockActions,
141
143
  assignCustomer: mockAssignCustomer,
142
144
  authenticateUser: mockAuthenticateUser,
143
145
  calculateRefundTotal: mockCalculateRefundTotal,
@@ -1,4 +1,4 @@
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';
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, 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;
@@ -38,6 +38,7 @@ export interface RenderProviderActions {
38
38
  setActiveProduct: SetActiveProduct;
39
39
  adjustInventory: AdjustInventory;
40
40
  setVariantUnavailable: SetVariantUnavailable;
41
+ getCustomStockActions: GetCustomStockActions;
41
42
  addOrderNote: AddOrderNote;
42
43
  addCartFee: AddCartFee;
43
44
  clearCart: ClearCart;
@@ -27,6 +27,11 @@ export const productsTopic = {
27
27
  name: 'Catalog Visibility Changed',
28
28
  description: 'Published when a product is hidden or shown at an outlet; re-ask getProducts',
29
29
  },
30
+ {
31
+ id: 'variant-unavailability-changed',
32
+ name: 'Variant Unavailability Changed',
33
+ description: 'Published when a variant is marked sold out at an outlet or unmarked; re-ask getProducts',
34
+ },
30
35
  {
31
36
  id: 'inventory-changed',
32
37
  name: 'Inventory Changed',
@@ -6,6 +6,7 @@ export * from './product-created/types';
6
6
  export * from './product-updated/types';
7
7
  export * from './composite-changed/types';
8
8
  export * from './catalog-visibility-changed/types';
9
+ export * from './variant-unavailability-changed/types';
9
10
  export * from './inventory-changed/types';
10
11
  export * from './product-set-active/types';
11
12
  export * from './product-get-active/types';
@@ -13,8 +14,9 @@ import type { ProductCreatedPayload } from './product-created/types';
13
14
  import type { ProductUpdatedPayload } from './product-updated/types';
14
15
  import type { CompositeChangedPayload } from './composite-changed/types';
15
16
  import type { CatalogVisibilityChangedPayload } from './catalog-visibility-changed/types';
17
+ import type { VariantUnavailabilityChangedPayload } from './variant-unavailability-changed/types';
16
18
  import type { InventoryChangedPayload } from './inventory-changed/types';
17
19
  import type { ProductSetActivePayload } from './product-set-active/types';
18
20
  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';
21
+ export type ProductsEventPayload = ProductCreatedPayload | ProductUpdatedPayload | CompositeChangedPayload | CatalogVisibilityChangedPayload | VariantUnavailabilityChangedPayload | InventoryChangedPayload | ProductSetActivePayload | ProductGetActivePayload;
22
+ export type ProductsEventType = 'product-created' | 'product-updated' | 'composite-changed' | 'catalog-visibility-changed' | 'variant-unavailability-changed' | 'inventory-changed' | 'set-active-product' | 'get-active-product';
@@ -7,6 +7,7 @@ export * from './product-created/types';
7
7
  export * from './product-updated/types';
8
8
  export * from './composite-changed/types';
9
9
  export * from './catalog-visibility-changed/types';
10
+ export * from './variant-unavailability-changed/types';
10
11
  export * from './inventory-changed/types';
11
12
  export * from './product-set-active/types';
12
13
  export * from './product-get-active/types';
@@ -0,0 +1,19 @@
1
+ import type { TopicEvent } from '../../../types';
2
+ /**
3
+ * Payload for variant-unavailability-changed: a variant was marked sold out at an outlet, or the mark was removed
4
+ * (`isDeleted`). `getProducts` reports the mark as `variant.unavailable` and a composite refuses a marked item —
5
+ * debounce, then re-ask `getProducts`.
6
+ */
7
+ export interface VariantUnavailabilityChangedPayload {
8
+ unavailability: {
9
+ _id: string;
10
+ productId: string;
11
+ variantId: string;
12
+ outletId: string;
13
+ isDeleted?: boolean;
14
+ };
15
+ }
16
+ /**
17
+ * Typed event for variant-unavailability-changed
18
+ */
19
+ export type VariantUnavailabilityChangedEvent = TopicEvent<VariantUnavailabilityChangedPayload>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@final-commerce/command-frame",
3
- "version": "0.10.0-staging.4",
3
+ "version": "0.10.0-staging.6",
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.6.0-staging.2"
60
+ "@final-commerce/common": "2.6.0-staging.4"
61
61
  },
62
62
  "devDependencies": {
63
63
  "@commitlint/cli": "^19.0.0",