@fayz-ai/shop 0.17.0 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -589,7 +589,11 @@ var T = {
589
589
  productOptions: "plg_shop_product_options",
590
590
  productVariants: "plg_shop_product_variants",
591
591
  categories: "plg_shop_categories",
592
- orders: "plg_shop_orders",
592
+ // The sale is a Venda (public.orders, kind='sale', channel='shop') since
593
+ // 0105; v_shop_orders is it, joined to its extension and its buyer, in the
594
+ // shop's own vocabulary. Writes go through the RPCs.
595
+ orders: "v_shop_orders",
596
+ orderExtensions: "plg_shop_order_extensions",
593
597
  /** 0059. Read-only here: a session pointing at an order is what marks that
594
598
  * order as born on the storefront rather than typed into the console. */
595
599
  checkoutSessions: "plg_shop_checkout_sessions",
@@ -620,9 +624,18 @@ var T = {
620
624
  stockReasonCodes: "plg_shop_stock_reason_codes",
621
625
  stockLocations: "plg_shop_stock_locations",
622
626
  stockLots: "plg_shop_stock_lots",
623
- stockMovements: "plg_shop_stock_movements",
627
+ // A stock movement is NOT a shop concept (ADR 0024). It is the inventory
628
+ // plugin's ledger, and the shop writes into it through the column translation
629
+ // in inventory/supabase-stock-store.ts. `plg_shop_stock_movements` is left
630
+ // behind holding the rows written before the move; it takes no new ones.
631
+ stockMovements: "plg_inventory_stock_movements",
624
632
  stockReservations: "plg_shop_stock_reservations",
625
- stockBalances: "plg_shop_stock_balances",
633
+ // The archetype's book, maintained by stock_apply from BOTH ledgers. It has
634
+ // to move with the movements: `plg_shop_stock_balances` was kept up to date
635
+ // by a trigger on the shop's movements table, so the moment writes left that
636
+ // table its balances would have frozen at whatever they last read — the worst
637
+ // failure shape, since a stale number looks exactly like a correct one.
638
+ stockBalances: "stock_balances",
626
639
  // Allocation and the reservation lifecycle (0038). The strategies and TTLs are
627
640
  // VOCABULARY — rows, not enums (I9) — so an agency widens either with an
628
641
  // INSERT. stockLevels is a VIEW: our (variant, lot, location) balances
@@ -637,13 +650,14 @@ var T = {
637
650
  // Tenant-level shop settings (0029). stock_ownership = 'external' means an
638
651
  // ERP owns the numbers and our ledger is not authoritative for that store.
639
652
  settings: "plg_shop_settings",
640
- // Core, not plg_shop_*: a delivery zone is a fact about the business, and
641
- // fulfilment and financial read it too. Same reasoning as addresses and
642
- // orders — see migration 0021.
643
- shippingZones: "shipping_zones",
644
- // The store-wide delivery fallback (0015). Core for the same reason zones
645
- // are: what a business charges to deliver is not a fact about a plugin.
646
- shippingSettings: "shipping_settings",
653
+ // plg_shop_*, and 0021's reasoning no longer applies: spine 148 and shop
654
+ // 0087 moved both names under the prefix, because the prefix is the
655
+ // OWNERSHIP boundary (ADR 0011) and these rows are the shop's. The
656
+ // unprefixed names survive only as compat views 141 left behind.
657
+ shippingZones: "plg_shop_shipping_zones",
658
+ // The store-wide delivery fallback (0015), moved by the same migration for
659
+ // the same reason.
660
+ shippingSettings: "plg_shop_shipping_settings",
647
661
  shippingProfiles: "plg_shop_shipping_profiles",
648
662
  // plg_*, unlike the two above: which boxes a store keeps on the shelf is a
649
663
  // fact about running this shop, not about the business. Nothing outside the
@@ -730,18 +744,19 @@ function rowToMovement(row) {
730
744
  tenantId: row.tenant_id,
731
745
  variantId: row.variant_id ?? null,
732
746
  productId: row.product_id ?? null,
733
- locationId: row.location_id,
747
+ // Reading back through the same translation as the write.
748
+ locationId: row.source_location_id ?? row.location_id,
734
749
  lotId: row.lot_id ?? null,
735
- kind: row.kind,
736
- reasonCode: row.reason_code ?? null,
750
+ kind: row.movement_type ?? row.kind,
751
+ reasonCode: row.reason ?? row.reason_code ?? null,
737
752
  quantity: Number(row.quantity),
738
753
  unitCost: row.unit_cost == null ? null : Number(row.unit_cost),
739
- orderId: row.order_id ?? null,
754
+ orderId: row.source_item_type === "order" ? row.source_item_id ?? null : row.order_id ?? null,
740
755
  reservationId: row.reservation_id ?? null,
741
756
  idempotencyKey: row.idempotency_key ?? null,
742
- occurredAt: row.occurred_at,
743
- createdBy: row.created_by ?? null,
744
- note: row.note ?? null,
757
+ occurredAt: row.movement_date ?? row.occurred_at,
758
+ createdBy: row.user_id ?? row.created_by ?? null,
759
+ note: row.notes ?? row.note ?? null,
745
760
  createdAt: row.created_at
746
761
  };
747
762
  }
@@ -752,7 +767,11 @@ function rowToBalance(row) {
752
767
  id: row.id,
753
768
  tenantId: row.tenant_id,
754
769
  variantId: row.variant_id,
755
- locationId: row.location_id,
770
+ // The archetype's book is keyed on (tenant, variant, unit). It carries no
771
+ // lot, and `unit_id` is the BRANCH rather than a shelf — so a balance read
772
+ // here answers "how many in total", and WHERE they sit stays a question for
773
+ // the inventory positions.
774
+ locationId: row.unit_id ?? row.location_id ?? null,
756
775
  lotId: row.lot_id ?? null,
757
776
  onHand,
758
777
  reserved,
@@ -837,23 +856,20 @@ var SupabaseStockStore = class {
837
856
  if (tenantId) q = q.eq("tenant_id", tenantId);
838
857
  if (options.variantId) q = q.eq("variant_id", options.variantId);
839
858
  if (options.variantIds) q = q.in("variant_id", options.variantIds);
840
- if (options.locationId) q = q.eq("location_id", options.locationId);
841
- if (options.lotId) q = q.eq("lot_id", options.lotId);
842
- if (options.sellableOnly) {
843
- const sellable = await this.sellableLocationIds();
844
- if (sellable.size === 0) return [];
845
- q = q.in("location_id", [...sellable]);
859
+ if (options.locationId || options.lotId || options.sellableOnly) {
860
+ throw new Error(
861
+ "listBalances: the shared stock book is keyed on (tenant, variant) only. Ask plg_inventory_stock_positions for per-location or per-lot detail."
862
+ );
846
863
  }
847
864
  const { data, error } = await q;
848
865
  if (error) throw error;
849
866
  return (data ?? []).map(rowToBalance);
850
867
  }
851
868
  async getAvailable(variantId) {
852
- const [balances, sellable] = await Promise.all([
853
- this.listBalances({ variantId }),
854
- this.sellableLocationIds()
855
- ]);
856
- return mirroredInventoryCount(balances, sellable);
869
+ const balances = await this.listBalances({ variantId });
870
+ let total = 0;
871
+ for (const balance of balances) total += Math.max(balance.onHand - balance.reserved, 0);
872
+ return Math.max(total, 0);
857
873
  }
858
874
  async listLots(options = {}) {
859
875
  const db = getDb();
@@ -877,12 +893,18 @@ var SupabaseStockStore = class {
877
893
  const tenantId = this.tenantId();
878
894
  if (tenantId) q = q.eq("tenant_id", tenantId);
879
895
  if (options.variantId) q = q.eq("variant_id", options.variantId);
880
- if (options.lotId) q = q.eq("lot_id", options.lotId);
881
- if (options.orderId) q = q.eq("order_id", options.orderId);
896
+ if (options.lotId) {
897
+ const lot = await this.readLot(options.lotId);
898
+ if (!lot?.serial) return [];
899
+ q = q.eq("batch_number", lot.serial);
900
+ }
901
+ if (options.orderId) {
902
+ q = q.eq("source_item_type", "order").eq("source_item_id", options.orderId);
903
+ }
882
904
  if (options.kind) q = q.eq("kind", options.kind);
883
- if (options.reasonCode) q = q.eq("reason_code", options.reasonCode);
905
+ if (options.reasonCode) q = q.eq("reason", options.reasonCode);
884
906
  const ascending = options.newestFirst === false;
885
- q = q.order("occurred_at", { ascending }).order("created_at", { ascending });
907
+ q = q.order("movement_date", { ascending }).order("created_at", { ascending });
886
908
  if (options.limit != null) q = q.limit(options.limit);
887
909
  const { data, error } = await q;
888
910
  if (error) throw error;
@@ -959,26 +981,44 @@ var SupabaseStockStore = class {
959
981
  );
960
982
  }
961
983
  } else {
962
- locationId = (await this.getDefaultLocation())?.id ?? null;
984
+ const { data, error: error2 } = await db.rpc("shop_default_inventory_location", {
985
+ p_tenant_id: tenantId
986
+ });
987
+ if (error2) throw new StockMovementError(error2.message, input);
988
+ locationId = data ?? null;
989
+ }
990
+ if (!locationId) {
991
+ throw new StockMovementError(
992
+ `stock movement: store ${tenantId} has no default stock location to record this against`,
993
+ input
994
+ );
963
995
  }
964
- const payload = {
965
- tenant_id: tenantId,
996
+ const line = {
997
+ product_id: variant.productId,
966
998
  variant_id: input.variantId,
967
- location_id: locationId,
968
- lot_id: input.lotId ?? null,
969
- kind: input.kind,
970
- reason_code: input.reasonCode ?? null,
971
- quantity: input.quantity,
972
- unit_cost: input.unitCost ?? null,
973
- order_id: input.orderId ?? null,
974
- reservation_id: input.reservationId ?? null,
975
- idempotency_key: input.idempotencyKey ?? null,
976
- ...input.occurredAt !== void 0 ? { occurred_at: input.occurredAt } : {},
977
- note: input.note ?? null
978
- // product_id is denormalised by the validator trigger from the variant,
979
- // so the ledger row still names its product after an ON DELETE SET NULL.
999
+ quantity: input.kind === "receipt" ? Math.abs(input.quantity) : input.quantity,
1000
+ ...input.unitCost != null ? { unit_cost: input.unitCost } : {},
1001
+ ...lot?.serial ? { batch_number: lot.serial } : {},
1002
+ ...input.note ? { notes: input.note } : {}
980
1003
  };
981
- const { data, error } = await db.from(T.stockMovements).insert(payload).select().single();
1004
+ const { data: opResult, error } = input.kind === "receipt" ? await db.rpc("inventory_receive", {
1005
+ p_location: locationId,
1006
+ p_lines: [line],
1007
+ p_document: {
1008
+ type: "shop",
1009
+ ...input.orderId ? { ref: input.orderId } : {},
1010
+ ...input.occurredAt ? { date: input.occurredAt.slice(0, 10) } : {}
1011
+ },
1012
+ p_idempotency_key: input.idempotencyKey ?? null
1013
+ }) : await db.rpc("inventory_adjust", {
1014
+ p_location: locationId,
1015
+ p_lines: [line],
1016
+ // The ledger requires a reason for an adjustment, and the shop's reason
1017
+ // code is one. Falling back to the verb keeps the movement legible
1018
+ // rather than refusing it over a blank field.
1019
+ p_reason: input.reasonCode ?? input.kind,
1020
+ p_idempotency_key: input.idempotencyKey ?? null
1021
+ });
982
1022
  if (error) {
983
1023
  if (error.code === UNIQUE_VIOLATION && input.idempotencyKey) {
984
1024
  const winner = await this.findByIdempotencyKey(input.idempotencyKey);
@@ -986,7 +1026,15 @@ var SupabaseStockStore = class {
986
1026
  }
987
1027
  throw new StockMovementError(error.message ?? String(error), input);
988
1028
  }
989
- return rowToMovement(data);
1029
+ const movements = opResult?.movements ?? [];
1030
+ const written = movements[0];
1031
+ if (!written) {
1032
+ throw new StockMovementError(
1033
+ "stock movement: the inventory ledger accepted the operation but returned no movement",
1034
+ input
1035
+ );
1036
+ }
1037
+ return rowToMovement(written);
990
1038
  }
991
1039
  // --- allocation and the reservation lifecycle (§9 step 4) ---------------------
992
1040
  //
@@ -1463,5 +1511,5 @@ function createMockStockStore(seed = {}) {
1463
1511
  }
1464
1512
 
1465
1513
  export { BEYOND_SHOPIFY, CORE_RESERVATION_TTL_MINUTES, CORE_STOCK_ALLOCATION_STRATEGIES, CORE_STOCK_MOVEMENT_KINDS, CORE_STOCK_REASON_CODES, DEFAULT_STOCK_TRACKING, LEGACY_WRITE_REASON, MockStockStore, OPENING_BALANCE_REASON, OWN_FULFILLMENT, SHOPIFY_ADJUSTMENT_REASONS, SHOPIFY_DEFAULT_OPTION_NAME, SHOPIFY_DEFAULT_VARIANT_TITLE, SHOPIFY_QUANTITY_STATES, STOCK_LOCATION_KINDS, STOCK_LOT_STATUSES, STOCK_MOVEMENT_DIRECTIONS, STOCK_RESERVATION_STATUSES, STOCK_TRACKING_MODES, StockAllocationError, StockMovementError, SupabaseStockStore, T, TRACKING_MODES_BEYOND_SHOPIFY, V, allocateStock, clearStockAllocationStrategies, clearStockStores, clearStockVocabulary, compareStockAllocationCandidates, createMockStockStore, createSupabaseStockStore, getRegisteredStockStore, listStockAllocationStrategies, listStockAllocationStrategyKeys, listStockMovementKindKeys, listStockMovementKinds, listStockReasonCodeKeys, listStockReasonCodes, mirroredInventoryCount, registerCoreStockAllocationStrategies, registerCoreStockVocabulary, registerStockAllocationStrategy, registerStockMovementKind, registerStockReasonCode, registerStockStore, replayStockLedger, resolveAllocationStrategyKey, resolveReservationTtlMinutes, resolveStockAllocationStrategy, resolveStockMovementKind, resolveStockReasonCode, resolveStockStore, rowToStockLocation, shopifyDefaultOptions, shopifyReasonToStockReason, stockAllocationStrategyRegistry, stockBalanceKey, stockMovementKindRegistry, stockReasonCodeRegistry, toShopifyVariantTitle, validateStockMovement };
1466
- //# sourceMappingURL=chunk-26YK5V6P.js.map
1467
- //# sourceMappingURL=chunk-26YK5V6P.js.map
1514
+ //# sourceMappingURL=chunk-AKUDWFGY.js.map
1515
+ //# sourceMappingURL=chunk-AKUDWFGY.js.map