@porulle/plugin-channel-connector 0.62.0 → 0.64.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.
package/dist/service.d.ts CHANGED
@@ -539,6 +539,9 @@ export declare class ChannelConnectorService {
539
539
  private redactShopData;
540
540
  private resolveOrderId;
541
541
  private setMappedInventory;
542
+ private setInventoryLevel;
543
+ /** This store's mapped variant whose import recorded `metadata.inventoryItemId`; null when none, or when two claim it. */
544
+ private variantForInventoryItem;
542
545
  private convergeCatalogItem;
543
546
  private createRefundRequest;
544
547
  private executeRefund;
package/dist/service.js CHANGED
@@ -3737,9 +3737,20 @@ export class ChannelConnectorService {
3737
3737
  }
3738
3738
  }
3739
3739
  else if (event.type === "inventory_levels/update") {
3740
- const externalId = String(data.inventory_item_id ?? data.variation_id ?? data.product_id ?? "");
3741
3740
  const available = Number(data.available ?? data.stock_quantity ?? 0);
3742
- await this.setMappedInventory(orgId, storeId, externalId, available, actor);
3741
+ // Shopify names an INVENTORY ITEM, whose id is not the variant id the channel map is keyed by;
3742
+ // looked up by it, every Shopify stock webhook found no mapping and was dropped. The variant is
3743
+ // resolved through the inventory item id its import recorded; a provider that sends a variant
3744
+ // id keeps the plain lookup.
3745
+ const inventoryItemId = data.inventory_item_id !== undefined && data.inventory_item_id !== null ? String(data.inventory_item_id) : null;
3746
+ const byInventoryItem = inventoryItemId === null ? null : await this.variantForInventoryItem(orgId, storeId, inventoryItemId);
3747
+ if (byInventoryItem !== null) {
3748
+ await this.setInventoryLevel(byInventoryItem.entityId, byInventoryItem.variantId, available, actor);
3749
+ }
3750
+ else {
3751
+ const externalId = String(data.variation_id ?? data.product_id ?? inventoryItemId ?? "");
3752
+ await this.setMappedInventory(orgId, storeId, externalId, available, actor);
3753
+ }
3743
3754
  }
3744
3755
  else if (event.type === "orders/fulfilled" || event.type === "orders/cancelled") {
3745
3756
  const orderId = await this.resolveOrderId(orgId, storeId, data);
@@ -3849,8 +3860,20 @@ export class ChannelConnectorService {
3849
3860
  const [mapping] = await this.db.select().from(channelEntityMap).where(and(eq(channelEntityMap.organizationId, orgId), eq(channelEntityMap.storeId, storeId), eq(channelEntityMap.externalId, externalId)));
3850
3861
  if (!mapping)
3851
3862
  return;
3863
+ await this.setInventoryLevel(mapping.entityId, mapping.variantId, quantity, actor);
3864
+ }
3865
+ async setInventoryLevel(entityId, variantId, quantity, actor) {
3852
3866
  const inventory = this.services.inventory;
3853
- await inventory.setAbsolute({ entityId: mapping.entityId, ...(mapping.variantId ? { variantId: mapping.variantId } : {}), quantity: Math.max(0, Math.floor(quantity)), reason: "Inventory webhook sync" }, actor);
3867
+ await inventory.setAbsolute({ entityId, ...(variantId ? { variantId } : {}), quantity: Math.max(0, Math.floor(quantity)), reason: "Inventory webhook sync" }, actor);
3868
+ }
3869
+ /** This store's mapped variant whose import recorded `metadata.inventoryItemId`; null when none, or when two claim it. */
3870
+ async variantForInventoryItem(orgId, storeId, inventoryItemId) {
3871
+ const rows = await this.db.select({ entityId: channelEntityMap.entityId, variantId: channelEntityMap.variantId }).from(channelEntityMap)
3872
+ .innerJoin(variants, eq(variants.id, channelEntityMap.variantId))
3873
+ .where(and(eq(channelEntityMap.organizationId, orgId), eq(channelEntityMap.storeId, storeId), eq(channelEntityMap.kind, "variant"), sql `${variants.metadata}->>'inventoryItemId' = ${inventoryItemId}`))
3874
+ .limit(2);
3875
+ const [only] = rows;
3876
+ return rows.length === 1 && only !== undefined && only.variantId !== null ? { entityId: only.entityId, variantId: only.variantId } : null;
3854
3877
  }
3855
3878
  async convergeCatalogItem(orgId, storeId, entityId, data, actor) {
3856
3879
  const product = data.product && typeof data.product === "object" ? data.product : data;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@porulle/plugin-channel-connector",
3
- "version": "0.62.0",
3
+ "version": "0.64.0",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "exports": {
@@ -19,7 +19,7 @@
19
19
  "dependencies": {
20
20
  "@hono/zod-openapi": "^1.2.2",
21
21
  "hono": "^4.12.5",
22
- "@porulle/core": "0.62.0"
22
+ "@porulle/core": "0.64.0"
23
23
  },
24
24
  "devDependencies": {
25
25
  "@types/node": "^24.5.2",
package/src/service.ts CHANGED
@@ -4798,9 +4798,19 @@ export class ChannelConnectorService {
4798
4798
  }
4799
4799
  }
4800
4800
  } else if (event.type === "inventory_levels/update") {
4801
- const externalId = String(data.inventory_item_id ?? data.variation_id ?? data.product_id ?? "");
4802
4801
  const available = Number(data.available ?? data.stock_quantity ?? 0);
4803
- await this.setMappedInventory(orgId, storeId, externalId, available, actor);
4802
+ // Shopify names an INVENTORY ITEM, whose id is not the variant id the channel map is keyed by;
4803
+ // looked up by it, every Shopify stock webhook found no mapping and was dropped. The variant is
4804
+ // resolved through the inventory item id its import recorded; a provider that sends a variant
4805
+ // id keeps the plain lookup.
4806
+ const inventoryItemId = data.inventory_item_id !== undefined && data.inventory_item_id !== null ? String(data.inventory_item_id) : null;
4807
+ const byInventoryItem = inventoryItemId === null ? null : await this.variantForInventoryItem(orgId, storeId, inventoryItemId);
4808
+ if (byInventoryItem !== null) {
4809
+ await this.setInventoryLevel(byInventoryItem.entityId, byInventoryItem.variantId, available, actor);
4810
+ } else {
4811
+ const externalId = String(data.variation_id ?? data.product_id ?? inventoryItemId ?? "");
4812
+ await this.setMappedInventory(orgId, storeId, externalId, available, actor);
4813
+ }
4804
4814
  } else if (event.type === "orders/fulfilled" || event.type === "orders/cancelled") {
4805
4815
  const orderId = await this.resolveOrderId(orgId, storeId, data);
4806
4816
  if (orderId) {
@@ -4913,8 +4923,27 @@ export class ChannelConnectorService {
4913
4923
  private async setMappedInventory(orgId: string, storeId: string, externalId: string, quantity: number, actor: Actor): Promise<void> {
4914
4924
  const [mapping] = await this.db.select().from(channelEntityMap).where(and(eq(channelEntityMap.organizationId, orgId), eq(channelEntityMap.storeId, storeId), eq(channelEntityMap.externalId, externalId)));
4915
4925
  if (!mapping) return;
4926
+ await this.setInventoryLevel(mapping.entityId, mapping.variantId, quantity, actor);
4927
+ }
4928
+
4929
+ private async setInventoryLevel(entityId: string, variantId: string | null, quantity: number, actor: Actor): Promise<void> {
4916
4930
  const inventory = this.services.inventory as { setAbsolute(input: { entityId: string; variantId?: string; quantity: number; reason?: string }, actor: Actor): Promise<{ ok: boolean }> };
4917
- await inventory.setAbsolute({ entityId: mapping.entityId, ...(mapping.variantId ? { variantId: mapping.variantId } : {}), quantity: Math.max(0, Math.floor(quantity)), reason: "Inventory webhook sync" }, actor);
4931
+ await inventory.setAbsolute({ entityId, ...(variantId ? { variantId } : {}), quantity: Math.max(0, Math.floor(quantity)), reason: "Inventory webhook sync" }, actor);
4932
+ }
4933
+
4934
+ /** This store's mapped variant whose import recorded `metadata.inventoryItemId`; null when none, or when two claim it. */
4935
+ private async variantForInventoryItem(orgId: string, storeId: string, inventoryItemId: string): Promise<{ entityId: string; variantId: string } | null> {
4936
+ const rows = await this.db.select({ entityId: channelEntityMap.entityId, variantId: channelEntityMap.variantId }).from(channelEntityMap)
4937
+ .innerJoin(variants, eq(variants.id, channelEntityMap.variantId))
4938
+ .where(and(
4939
+ eq(channelEntityMap.organizationId, orgId),
4940
+ eq(channelEntityMap.storeId, storeId),
4941
+ eq(channelEntityMap.kind, "variant"),
4942
+ sql`${variants.metadata}->>'inventoryItemId' = ${inventoryItemId}`,
4943
+ ))
4944
+ .limit(2);
4945
+ const [only] = rows;
4946
+ return rows.length === 1 && only !== undefined && only.variantId !== null ? { entityId: only.entityId, variantId: only.variantId } : null;
4918
4947
  }
4919
4948
 
4920
4949
  private async convergeCatalogItem(