@open-mercato/core 0.9.1-develop.7519.1.38f51f8dbb → 0.9.1-develop.7532.1.8eac7e198f

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 (82) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/modules/auth/api/logout.js +6 -1
  3. package/dist/modules/auth/api/logout.js.map +2 -2
  4. package/dist/modules/auth/api/session/refresh.js +6 -1
  5. package/dist/modules/auth/api/session/refresh.js.map +2 -2
  6. package/dist/modules/catalog/ai-tools/mutation-pack.js +10 -5
  7. package/dist/modules/catalog/ai-tools/mutation-pack.js.map +2 -2
  8. package/dist/modules/catalog/ai-tools/types.js.map +2 -2
  9. package/dist/modules/customer_accounts/api/login.js +14 -7
  10. package/dist/modules/customer_accounts/api/login.js.map +2 -2
  11. package/dist/modules/customer_accounts/services/customerUserService.js +10 -10
  12. package/dist/modules/customer_accounts/services/customerUserService.js.map +2 -2
  13. package/dist/modules/forms/api/distributions/[distributionId]/invitations/[invitationId]/route.js +1 -1
  14. package/dist/modules/forms/api/distributions/[distributionId]/invitations/[invitationId]/route.js.map +1 -1
  15. package/dist/modules/forms/api/form-submissions/[id]/submit/route.js +12 -2
  16. package/dist/modules/forms/api/form-submissions/[id]/submit/route.js.map +2 -2
  17. package/dist/modules/forms/api/form-submissions/route.js +16 -1
  18. package/dist/modules/forms/api/form-submissions/route.js.map +2 -2
  19. package/dist/modules/forms/api/public/submissions/[id]/submit/route.js +6 -1
  20. package/dist/modules/forms/api/public/submissions/[id]/submit/route.js.map +2 -2
  21. package/dist/modules/forms/api/runtime-helpers.js +16 -0
  22. package/dist/modules/forms/api/runtime-helpers.js.map +2 -2
  23. package/dist/modules/forms/data/validators.js.map +2 -2
  24. package/dist/modules/forms/lib/portal-subject-policy.js +17 -0
  25. package/dist/modules/forms/lib/portal-subject-policy.js.map +7 -0
  26. package/dist/modules/forms/lib/runtime-principal.js +27 -10
  27. package/dist/modules/forms/lib/runtime-principal.js.map +2 -2
  28. package/dist/modules/forms/services/distribution-service.js +35 -13
  29. package/dist/modules/forms/services/distribution-service.js.map +2 -2
  30. package/dist/modules/forms/services/submission-service.js +4 -0
  31. package/dist/modules/forms/services/submission-service.js.map +2 -2
  32. package/dist/modules/staff/commands/leave-requests.js +54 -24
  33. package/dist/modules/staff/commands/leave-requests.js.map +2 -2
  34. package/dist/modules/warranty_claims/api/portal/attachments/route.js +19 -7
  35. package/dist/modules/warranty_claims/api/portal/attachments/route.js.map +2 -2
  36. package/dist/modules/warranty_claims/commands/claims.js +1 -1
  37. package/dist/modules/warranty_claims/commands/claims.js.map +2 -2
  38. package/dist/modules/warranty_claims/subscribers/return-shipment-tracking.js +6 -6
  39. package/dist/modules/warranty_claims/subscribers/return-shipment-tracking.js.map +2 -2
  40. package/dist/modules/wms/commands/inventory-actions.js +19 -3
  41. package/dist/modules/wms/commands/inventory-actions.js.map +2 -2
  42. package/dist/modules/workflows/lib/async-activity-wait.js +43 -0
  43. package/dist/modules/workflows/lib/async-activity-wait.js.map +7 -0
  44. package/dist/modules/workflows/lib/parallel-handler.js +20 -14
  45. package/dist/modules/workflows/lib/parallel-handler.js.map +2 -2
  46. package/dist/modules/workflows/lib/workflow-executor.js +21 -23
  47. package/dist/modules/workflows/lib/workflow-executor.js.map +2 -2
  48. package/package.json +7 -7
  49. package/src/modules/auth/api/logout.ts +6 -1
  50. package/src/modules/auth/api/session/refresh.ts +6 -1
  51. package/src/modules/catalog/ai-tools/mutation-pack.ts +14 -5
  52. package/src/modules/catalog/ai-tools/types.ts +1 -0
  53. package/src/modules/customer_accounts/api/login.ts +15 -7
  54. package/src/modules/customer_accounts/services/customerUserService.ts +34 -10
  55. package/src/modules/forms/api/distributions/[distributionId]/invitations/[invitationId]/route.ts +1 -1
  56. package/src/modules/forms/api/form-submissions/[id]/submit/route.ts +16 -3
  57. package/src/modules/forms/api/form-submissions/route.ts +21 -2
  58. package/src/modules/forms/api/public/submissions/[id]/submit/route.ts +7 -0
  59. package/src/modules/forms/api/runtime-helpers.ts +16 -0
  60. package/src/modules/forms/data/validators.ts +2 -1
  61. package/src/modules/forms/i18n/de.json +1 -0
  62. package/src/modules/forms/i18n/en.json +1 -0
  63. package/src/modules/forms/i18n/es.json +1 -0
  64. package/src/modules/forms/i18n/ko.json +1 -0
  65. package/src/modules/forms/i18n/pl.json +1 -0
  66. package/src/modules/forms/lib/portal-subject-policy.ts +42 -0
  67. package/src/modules/forms/lib/runtime-principal.ts +35 -11
  68. package/src/modules/forms/services/distribution-service.ts +35 -13
  69. package/src/modules/forms/services/submission-service.ts +12 -0
  70. package/src/modules/staff/commands/leave-requests.ts +54 -23
  71. package/src/modules/staff/i18n/de.json +2 -0
  72. package/src/modules/staff/i18n/en.json +2 -0
  73. package/src/modules/staff/i18n/es.json +2 -0
  74. package/src/modules/staff/i18n/ko.json +2 -0
  75. package/src/modules/staff/i18n/pl.json +2 -0
  76. package/src/modules/warranty_claims/api/portal/attachments/route.ts +20 -8
  77. package/src/modules/warranty_claims/commands/claims.ts +1 -1
  78. package/src/modules/warranty_claims/subscribers/return-shipment-tracking.ts +5 -5
  79. package/src/modules/wms/commands/inventory-actions.ts +30 -10
  80. package/src/modules/workflows/lib/async-activity-wait.ts +71 -0
  81. package/src/modules/workflows/lib/parallel-handler.ts +24 -15
  82. package/src/modules/workflows/lib/workflow-executor.ts +24 -27
@@ -1,7 +1,6 @@
1
1
  import { isCrudHttpError } from "@open-mercato/shared/lib/crud/errors";
2
2
  import { createLogger } from "@open-mercato/shared/lib/logger";
3
3
  const logger = createLogger("warranty_claims");
4
- const SYSTEM_USER_ID = "00000000-0000-0000-0000-000000000000";
5
4
  const metadata = {
6
5
  event: "shipping_carriers.shipment.delivered",
7
6
  persistent: true,
@@ -116,12 +115,13 @@ async function handle(payload, ctx) {
116
115
  }
117
116
  const commandCtx = {
118
117
  container,
119
- auth: {
120
- sub: SYSTEM_USER_ID,
121
- tenantId,
122
- orgId: organizationId
118
+ auth: null,
119
+ organizationScope: {
120
+ selectedId: organizationId,
121
+ filterIds: null,
122
+ allowedIds: null,
123
+ tenantId
123
124
  },
124
- organizationScope: null,
125
125
  selectedOrganizationId: organizationId,
126
126
  organizationIds: [organizationId],
127
127
  systemActor: true
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/warranty_claims/subscribers/return-shipment-tracking.ts"],
4
- "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport type { AwilixContainer } from 'awilix'\nimport type { Kysely } from 'kysely'\nimport type { CommandBus, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport type { TransitionClaimInput } from '../data/validators'\n\nconst logger = createLogger('warranty_claims')\nconst SYSTEM_USER_ID = '00000000-0000-0000-0000-000000000000'\n\nexport const metadata = {\n event: 'shipping_carriers.shipment.delivered',\n persistent: true,\n id: 'warranty_claims:return-shipment-tracking',\n}\n\ntype ResolverContainer = {\n resolve: <T = unknown>(name: string) => T\n}\n\ntype SubscriberContext = ResolverContainer & {\n container?: ResolverContainer\n tenantId?: string | null\n organizationId?: string | null\n}\n\ntype ReturnTrackingDb = {\n carrier_shipments: {\n id: string\n tracking_number: string | null\n tenant_id: string | null\n organization_id: string | null\n deleted_at: Date | null\n }\n warranty_claims: {\n id: string\n claim_number: string\n updated_at: Date | null\n status: string\n return_tracking_number: string | null\n tenant_id: string\n organization_id: string\n deleted_at: Date | null\n }\n}\n\nfunction toRecord(value: unknown): Record<string, unknown> {\n return value && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : {}\n}\n\nfunction readString(record: Record<string, unknown>, key: string): string | null {\n const value = record[key]\n return typeof value === 'string' && value.trim().length > 0 ? value.trim() : null\n}\n\nfunction resolveContainer(ctx: SubscriberContext): ResolverContainer {\n return ctx.container ?? { resolve: ctx.resolve }\n}\n\nfunction isMissingReferenceTableError(error: unknown): boolean {\n const candidate = toRecord(error)\n return candidate.code === '42P01'\n || (typeof candidate.message === 'string' && /relation \"[^\"]*\" does not exist/.test(candidate.message))\n}\n\nfunction isInvalidTransitionError(error: unknown): boolean {\n if (isCrudHttpError(error)) {\n return error.status === 400 && error.body.error === 'warranty_claims.errors.invalidTransition'\n }\n const candidate = toRecord(error)\n const body = toRecord(candidate.body)\n return candidate.status === 400 && body.error === 'warranty_claims.errors.invalidTransition'\n}\n\nexport default async function handle(payload: unknown, ctx: SubscriberContext): Promise<void> {\n const record = toRecord(payload)\n const shipmentId = readString(record, 'shipmentId') ?? readString(record, 'shipment_id')\n const tenantId = ctx.tenantId ?? null\n const organizationId = ctx.organizationId ?? null\n\n if (!shipmentId || !tenantId || !organizationId) {\n logger.debug('[warranty_claims:return-shipment-tracking] skipped incomplete delivered payload', {\n hasShipmentId: Boolean(shipmentId),\n hasTrustedTenantId: Boolean(tenantId),\n hasTrustedOrganizationId: Boolean(organizationId),\n })\n return\n }\n\n const container = resolveContainer(ctx)\n let db: Kysely<ReturnTrackingDb>\n try {\n const em = container.resolve<EntityManager>('em').fork()\n db = em.getKysely<ReturnTrackingDb>()\n } catch (error) {\n logger.error('[warranty_claims:return-shipment-tracking] failed to initialize persistence', {\n err: error,\n shipmentId,\n tenantId,\n organizationId,\n })\n throw error\n }\n let shipment: { tracking_number: string | null } | undefined\n try {\n shipment = await db\n .selectFrom('carrier_shipments')\n .select('tracking_number')\n .where('id', '=', shipmentId)\n .where('tenant_id', '=', tenantId)\n .where('organization_id', '=', organizationId)\n .where('deleted_at', 'is', null)\n .executeTakeFirst()\n } catch (error) {\n if (isMissingReferenceTableError(error)) {\n logger.debug('[warranty_claims:return-shipment-tracking] carrier shipment table unavailable', {\n shipmentId,\n tenantId,\n organizationId,\n })\n return\n }\n logger.error('[warranty_claims:return-shipment-tracking] failed to load carrier shipment', {\n err: error,\n shipmentId,\n tenantId,\n organizationId,\n })\n throw error\n }\n\n const trackingNumber = readString(toRecord(shipment), 'tracking_number')\n if (!shipment || !trackingNumber) {\n logger.debug('[warranty_claims:return-shipment-tracking] shipment or tracking number not found', {\n shipmentId,\n tenantId,\n organizationId,\n })\n return\n }\n\n let claims: Array<{ id: string; claim_number: string; updated_at: Date | null }>\n try {\n claims = await db\n .selectFrom('warranty_claims')\n .select(['id', 'claim_number', 'updated_at'])\n .where('tenant_id', '=', tenantId)\n .where('organization_id', '=', organizationId)\n .where('deleted_at', 'is', null)\n .where('status', '=', 'awaiting_return')\n .where('return_tracking_number', '=', trackingNumber)\n .limit(2)\n .execute()\n } catch (error) {\n logger.error('[warranty_claims:return-shipment-tracking] failed to find matching warranty claims', {\n err: error,\n shipmentId,\n tenantId,\n organizationId,\n })\n throw error\n }\n\n if (claims.length === 0) {\n logger.debug('[warranty_claims:return-shipment-tracking] no awaiting-return claim matched shipment', {\n shipmentId,\n tenantId,\n organizationId,\n })\n return\n }\n if (claims.length > 1) {\n logger.warn('[warranty_claims:return-shipment-tracking] multiple claims matched return shipment', {\n shipmentId,\n tenantId,\n organizationId,\n claimNumbers: claims.map((claim) => claim.claim_number),\n })\n return\n }\n\n const commandCtx: CommandRuntimeContext = {\n container: container as unknown as AwilixContainer,\n auth: {\n sub: SYSTEM_USER_ID,\n tenantId,\n orgId: organizationId,\n },\n organizationScope: null,\n selectedOrganizationId: organizationId,\n organizationIds: [organizationId],\n systemActor: true,\n }\n const input: TransitionClaimInput = {\n id: claims[0].id,\n toStatus: 'received',\n systemNote: 'warranty_claims.timeline.autoReceivedFromTracking',\n }\n\n try {\n const commandBus = container.resolve<CommandBus>('commandBus')\n await commandBus.execute<TransitionClaimInput, { claimId: string }>(\n 'warranty_claims.claim.transition',\n { input, ctx: commandCtx },\n )\n } catch (error) {\n if (isInvalidTransitionError(error)) {\n logger.debug('[warranty_claims:return-shipment-tracking] claim no longer accepts received transition', {\n claimId: claims[0].id,\n shipmentId,\n tenantId,\n organizationId,\n })\n return\n }\n logger.error('[warranty_claims:return-shipment-tracking] failed to transition matching warranty claim', {\n err: error,\n claimId: claims[0].id,\n shipmentId,\n tenantId,\n organizationId,\n })\n throw error\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport type { AwilixContainer } from 'awilix'\nimport type { Kysely } from 'kysely'\nimport type { CommandBus, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport type { TransitionClaimInput } from '../data/validators'\n\nconst logger = createLogger('warranty_claims')\n\nexport const metadata = {\n event: 'shipping_carriers.shipment.delivered',\n persistent: true,\n id: 'warranty_claims:return-shipment-tracking',\n}\n\ntype ResolverContainer = {\n resolve: <T = unknown>(name: string) => T\n}\n\ntype SubscriberContext = ResolverContainer & {\n container?: ResolverContainer\n tenantId?: string | null\n organizationId?: string | null\n}\n\ntype ReturnTrackingDb = {\n carrier_shipments: {\n id: string\n tracking_number: string | null\n tenant_id: string | null\n organization_id: string | null\n deleted_at: Date | null\n }\n warranty_claims: {\n id: string\n claim_number: string\n updated_at: Date | null\n status: string\n return_tracking_number: string | null\n tenant_id: string\n organization_id: string\n deleted_at: Date | null\n }\n}\n\nfunction toRecord(value: unknown): Record<string, unknown> {\n return value && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : {}\n}\n\nfunction readString(record: Record<string, unknown>, key: string): string | null {\n const value = record[key]\n return typeof value === 'string' && value.trim().length > 0 ? value.trim() : null\n}\n\nfunction resolveContainer(ctx: SubscriberContext): ResolverContainer {\n return ctx.container ?? { resolve: ctx.resolve }\n}\n\nfunction isMissingReferenceTableError(error: unknown): boolean {\n const candidate = toRecord(error)\n return candidate.code === '42P01'\n || (typeof candidate.message === 'string' && /relation \"[^\"]*\" does not exist/.test(candidate.message))\n}\n\nfunction isInvalidTransitionError(error: unknown): boolean {\n if (isCrudHttpError(error)) {\n return error.status === 400 && error.body.error === 'warranty_claims.errors.invalidTransition'\n }\n const candidate = toRecord(error)\n const body = toRecord(candidate.body)\n return candidate.status === 400 && body.error === 'warranty_claims.errors.invalidTransition'\n}\n\nexport default async function handle(payload: unknown, ctx: SubscriberContext): Promise<void> {\n const record = toRecord(payload)\n const shipmentId = readString(record, 'shipmentId') ?? readString(record, 'shipment_id')\n const tenantId = ctx.tenantId ?? null\n const organizationId = ctx.organizationId ?? null\n\n if (!shipmentId || !tenantId || !organizationId) {\n logger.debug('[warranty_claims:return-shipment-tracking] skipped incomplete delivered payload', {\n hasShipmentId: Boolean(shipmentId),\n hasTrustedTenantId: Boolean(tenantId),\n hasTrustedOrganizationId: Boolean(organizationId),\n })\n return\n }\n\n const container = resolveContainer(ctx)\n let db: Kysely<ReturnTrackingDb>\n try {\n const em = container.resolve<EntityManager>('em').fork()\n db = em.getKysely<ReturnTrackingDb>()\n } catch (error) {\n logger.error('[warranty_claims:return-shipment-tracking] failed to initialize persistence', {\n err: error,\n shipmentId,\n tenantId,\n organizationId,\n })\n throw error\n }\n let shipment: { tracking_number: string | null } | undefined\n try {\n shipment = await db\n .selectFrom('carrier_shipments')\n .select('tracking_number')\n .where('id', '=', shipmentId)\n .where('tenant_id', '=', tenantId)\n .where('organization_id', '=', organizationId)\n .where('deleted_at', 'is', null)\n .executeTakeFirst()\n } catch (error) {\n if (isMissingReferenceTableError(error)) {\n logger.debug('[warranty_claims:return-shipment-tracking] carrier shipment table unavailable', {\n shipmentId,\n tenantId,\n organizationId,\n })\n return\n }\n logger.error('[warranty_claims:return-shipment-tracking] failed to load carrier shipment', {\n err: error,\n shipmentId,\n tenantId,\n organizationId,\n })\n throw error\n }\n\n const trackingNumber = readString(toRecord(shipment), 'tracking_number')\n if (!shipment || !trackingNumber) {\n logger.debug('[warranty_claims:return-shipment-tracking] shipment or tracking number not found', {\n shipmentId,\n tenantId,\n organizationId,\n })\n return\n }\n\n let claims: Array<{ id: string; claim_number: string; updated_at: Date | null }>\n try {\n claims = await db\n .selectFrom('warranty_claims')\n .select(['id', 'claim_number', 'updated_at'])\n .where('tenant_id', '=', tenantId)\n .where('organization_id', '=', organizationId)\n .where('deleted_at', 'is', null)\n .where('status', '=', 'awaiting_return')\n .where('return_tracking_number', '=', trackingNumber)\n .limit(2)\n .execute()\n } catch (error) {\n logger.error('[warranty_claims:return-shipment-tracking] failed to find matching warranty claims', {\n err: error,\n shipmentId,\n tenantId,\n organizationId,\n })\n throw error\n }\n\n if (claims.length === 0) {\n logger.debug('[warranty_claims:return-shipment-tracking] no awaiting-return claim matched shipment', {\n shipmentId,\n tenantId,\n organizationId,\n })\n return\n }\n if (claims.length > 1) {\n logger.warn('[warranty_claims:return-shipment-tracking] multiple claims matched return shipment', {\n shipmentId,\n tenantId,\n organizationId,\n claimNumbers: claims.map((claim) => claim.claim_number),\n })\n return\n }\n\n const commandCtx: CommandRuntimeContext = {\n container: container as unknown as AwilixContainer,\n auth: null,\n organizationScope: {\n selectedId: organizationId,\n filterIds: null,\n allowedIds: null,\n tenantId,\n },\n selectedOrganizationId: organizationId,\n organizationIds: [organizationId],\n systemActor: true,\n }\n const input: TransitionClaimInput = {\n id: claims[0].id,\n toStatus: 'received',\n systemNote: 'warranty_claims.timeline.autoReceivedFromTracking',\n }\n\n try {\n const commandBus = container.resolve<CommandBus>('commandBus')\n await commandBus.execute<TransitionClaimInput, { claimId: string }>(\n 'warranty_claims.claim.transition',\n { input, ctx: commandCtx },\n )\n } catch (error) {\n if (isInvalidTransitionError(error)) {\n logger.debug('[warranty_claims:return-shipment-tracking] claim no longer accepts received transition', {\n claimId: claims[0].id,\n shipmentId,\n tenantId,\n organizationId,\n })\n return\n }\n logger.error('[warranty_claims:return-shipment-tracking] failed to transition matching warranty claim', {\n err: error,\n claimId: claims[0].id,\n shipmentId,\n tenantId,\n organizationId,\n })\n throw error\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -512,9 +512,23 @@ async function findReservationBucketBalance(em, scope, reservation, bucket) {
512
512
  serialNumber: bucket.serialNumber ?? void 0
513
513
  });
514
514
  }
515
+ const BALANCE_BUCKET_CREATE_LOCK_SQL = "select pg_advisory_xact_lock(hashtextextended(?, 0))";
516
+ function balanceBucketCreateLockKey(input) {
517
+ return [
518
+ "wms:inventory-balance-bucket",
519
+ input.warehouseId.toLowerCase(),
520
+ input.locationId.toLowerCase(),
521
+ input.catalogVariantId.toLowerCase(),
522
+ input.lotId?.toLowerCase() ?? "",
523
+ input.serialNumber ?? ""
524
+ ].join(":");
525
+ }
515
526
  async function upsertBalanceBucket(em, scope, input) {
516
527
  const existing = await findExactBalanceForUpdate(em, scope, input);
517
528
  if (existing) return { balance: existing, created: false };
529
+ await em.execute(BALANCE_BUCKET_CREATE_LOCK_SQL, [balanceBucketCreateLockKey(input)]);
530
+ const createdConcurrently = await findExactBalanceForUpdate(em, scope, input);
531
+ if (createdConcurrently) return { balance: createdConcurrently, created: false };
518
532
  const balance = em.create(InventoryBalance, {
519
533
  organizationId: scope.organizationId,
520
534
  tenantId: scope.tenantId,
@@ -1284,15 +1298,17 @@ const moveInventoryCommand = {
1284
1298
  if (sourceWarehouseId !== input.warehouseId || targetWarehouseId !== input.warehouseId) {
1285
1299
  throw new CrudHttpError(422, { error: "invalid_location" });
1286
1300
  }
1287
- const sourceResult = await upsertBalanceBucket(trx, scope, {
1301
+ const sourceBalance = await findExactBalanceForUpdate(trx, scope, {
1288
1302
  warehouseId: input.warehouseId,
1289
1303
  locationId: input.fromLocationId,
1290
1304
  catalogVariantId: input.catalogVariantId,
1291
1305
  lotId: input.lotId,
1292
1306
  serialNumber: input.serialNumber
1293
1307
  });
1294
- const sourceBalance = sourceResult.balance;
1295
- const sourceBalanceAction = sourceResult.created ? "created" : "updated";
1308
+ if (!sourceBalance) {
1309
+ throw new CrudHttpError(409, { error: "insufficient_stock" });
1310
+ }
1311
+ const sourceBalanceAction = "updated";
1296
1312
  const targetResult = await upsertBalanceBucket(trx, scope, {
1297
1313
  warehouseId: input.warehouseId,
1298
1314
  locationId: input.toLocationId,