@open-mercato/core 0.6.6-develop.6257.1.6d0af84d26 → 0.6.6-develop.6290.1.4bb5a8ba3f

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 (220) hide show
  1. package/.turbo/turbo-build.log +2 -2
  2. package/dist/generated/entities/carrier_shipment_idempotency_key/index.js +19 -0
  3. package/dist/generated/entities/carrier_shipment_idempotency_key/index.js.map +7 -0
  4. package/dist/generated/entities.ids.generated.js +2 -1
  5. package/dist/generated/entities.ids.generated.js.map +2 -2
  6. package/dist/generated/entity-fields-registry.js +10 -0
  7. package/dist/generated/entity-fields-registry.js.map +2 -2
  8. package/dist/modules/catalog/lib/productResolution.js +45 -0
  9. package/dist/modules/catalog/lib/productResolution.js.map +7 -0
  10. package/dist/modules/communication_channels/workers/channel-import-history.js +1 -1
  11. package/dist/modules/communication_channels/workers/channel-import-history.js.map +2 -2
  12. package/dist/modules/currencies/api/fetch-configs/route.js +34 -0
  13. package/dist/modules/currencies/api/fetch-configs/route.js.map +2 -2
  14. package/dist/modules/currencies/components/CurrencyFetchingConfig.js +17 -15
  15. package/dist/modules/currencies/components/CurrencyFetchingConfig.js.map +2 -2
  16. package/dist/modules/customers/api/companies/[id]/route.js +176 -161
  17. package/dist/modules/customers/api/companies/[id]/route.js.map +2 -2
  18. package/dist/modules/customers/api/people/[id]/route.js +75 -59
  19. package/dist/modules/customers/api/people/[id]/route.js.map +2 -2
  20. package/dist/modules/customers/backend/config/customers/pipeline-stages/page.js +17 -10
  21. package/dist/modules/customers/backend/config/customers/pipeline-stages/page.js.map +2 -2
  22. package/dist/modules/customers/components/detail/ActivitiesAddNewMenu.js +6 -4
  23. package/dist/modules/customers/components/detail/ActivitiesAddNewMenu.js.map +2 -2
  24. package/dist/modules/customers/components/detail/AiActionChips.js +5 -2
  25. package/dist/modules/customers/components/detail/AiActionChips.js.map +2 -2
  26. package/dist/modules/customers/components/detail/schedule/ParticipantsField.js +19 -21
  27. package/dist/modules/customers/components/detail/schedule/ParticipantsField.js.map +2 -2
  28. package/dist/modules/data_sync/api/runs/[id]/cancel.js +1 -1
  29. package/dist/modules/data_sync/api/runs/[id]/cancel.js.map +2 -2
  30. package/dist/modules/data_sync/backend/data-sync/page.js +9 -45
  31. package/dist/modules/data_sync/backend/data-sync/page.js.map +2 -2
  32. package/dist/modules/data_sync/backend/data-sync/runs/[id]/page.js +11 -17
  33. package/dist/modules/data_sync/backend/data-sync/runs/[id]/page.js.map +2 -2
  34. package/dist/modules/data_sync/components/IntegrationScheduleTab.js +12 -10
  35. package/dist/modules/data_sync/components/IntegrationScheduleTab.js.map +2 -2
  36. package/dist/modules/data_sync/lib/id-mapping.js +46 -1
  37. package/dist/modules/data_sync/lib/id-mapping.js.map +2 -2
  38. package/dist/modules/data_sync/lib/sync-engine.js +2 -2
  39. package/dist/modules/data_sync/lib/sync-engine.js.map +2 -2
  40. package/dist/modules/data_sync/lib/syncRunStatus.js +30 -0
  41. package/dist/modules/data_sync/lib/syncRunStatus.js.map +7 -0
  42. package/dist/modules/data_sync/workers/sync-scheduled.js +13 -14
  43. package/dist/modules/data_sync/workers/sync-scheduled.js.map +2 -2
  44. package/dist/modules/directory/components/TenantSelect.js +29 -16
  45. package/dist/modules/directory/components/TenantSelect.js.map +2 -2
  46. package/dist/modules/entities/api/records.js +6 -0
  47. package/dist/modules/entities/api/records.js.map +2 -2
  48. package/dist/modules/feature_toggles/api/global/route.js +17 -15
  49. package/dist/modules/feature_toggles/api/global/route.js.map +2 -2
  50. package/dist/modules/feature_toggles/api/openapi.js +2 -2
  51. package/dist/modules/feature_toggles/api/openapi.js.map +2 -2
  52. package/dist/modules/feature_toggles/commands/global.js +14 -0
  53. package/dist/modules/feature_toggles/commands/global.js.map +2 -2
  54. package/dist/modules/inbox_ops/backend/inbox-ops/page.js +6 -0
  55. package/dist/modules/inbox_ops/backend/inbox-ops/page.js.map +2 -2
  56. package/dist/modules/messages/components/message-detail/hooks/useMessageDetails.js +3 -1
  57. package/dist/modules/messages/components/message-detail/hooks/useMessageDetails.js.map +2 -2
  58. package/dist/modules/messages/components/message-detail/hooks/useMessageDetailsActions.js +12 -5
  59. package/dist/modules/messages/components/message-detail/hooks/useMessageDetailsActions.js.map +2 -2
  60. package/dist/modules/messages/components/message-detail/hooks/useMessageDetailsQueries.js +10 -1
  61. package/dist/modules/messages/components/message-detail/hooks/useMessageDetailsQueries.js.map +2 -2
  62. package/dist/modules/payment_gateways/api/cancel/route.js +5 -0
  63. package/dist/modules/payment_gateways/api/cancel/route.js.map +2 -2
  64. package/dist/modules/payment_gateways/api/capture/route.js +5 -0
  65. package/dist/modules/payment_gateways/api/capture/route.js.map +2 -2
  66. package/dist/modules/payment_gateways/api/refund/route.js +5 -0
  67. package/dist/modules/payment_gateways/api/refund/route.js.map +2 -2
  68. package/dist/modules/payment_gateways/lib/gateway-service.js +52 -25
  69. package/dist/modules/payment_gateways/lib/gateway-service.js.map +2 -2
  70. package/dist/modules/payment_gateways/lib/status-machine.js +13 -0
  71. package/dist/modules/payment_gateways/lib/status-machine.js.map +2 -2
  72. package/dist/modules/perspectives/api/[tableId]/route.js +8 -2
  73. package/dist/modules/perspectives/api/[tableId]/route.js.map +2 -2
  74. package/dist/modules/perspectives/data/entities.js +33 -3
  75. package/dist/modules/perspectives/data/entities.js.map +2 -2
  76. package/dist/modules/perspectives/migrations/Migration20260619120000_perspectives_live_row_uniqueness.js +63 -0
  77. package/dist/modules/perspectives/migrations/Migration20260619120000_perspectives_live_row_uniqueness.js.map +7 -0
  78. package/dist/modules/perspectives/services/perspectiveService.js +17 -0
  79. package/dist/modules/perspectives/services/perspectiveService.js.map +2 -2
  80. package/dist/modules/planner/commands/availability-weekly.js +33 -1
  81. package/dist/modules/planner/commands/availability-weekly.js.map +2 -2
  82. package/dist/modules/planner/components/AvailabilityRulesEditor.js +14 -2
  83. package/dist/modules/planner/components/AvailabilityRulesEditor.js.map +2 -2
  84. package/dist/modules/portal/frontend/[orgSlug]/portal/dashboard/hiddenWidgetsStorage.js +35 -0
  85. package/dist/modules/portal/frontend/[orgSlug]/portal/dashboard/hiddenWidgetsStorage.js.map +7 -0
  86. package/dist/modules/portal/frontend/[orgSlug]/portal/dashboard/page.js +18 -19
  87. package/dist/modules/portal/frontend/[orgSlug]/portal/dashboard/page.js.map +2 -2
  88. package/dist/modules/progress/api/active/route.js +1 -1
  89. package/dist/modules/progress/api/active/route.js.map +2 -2
  90. package/dist/modules/progress/lib/progressService.js.map +1 -1
  91. package/dist/modules/progress/lib/progressServiceImpl.js +33 -8
  92. package/dist/modules/progress/lib/progressServiceImpl.js.map +2 -2
  93. package/dist/modules/query_index/api/status.js +19 -24
  94. package/dist/modules/query_index/api/status.js.map +3 -3
  95. package/dist/modules/query_index/lib/coverage.js +35 -0
  96. package/dist/modules/query_index/lib/coverage.js.map +2 -2
  97. package/dist/modules/query_index/lib/engine.js +11 -6
  98. package/dist/modules/query_index/lib/engine.js.map +2 -2
  99. package/dist/modules/resources/api/resource-types.js +5 -37
  100. package/dist/modules/resources/api/resource-types.js.map +2 -2
  101. package/dist/modules/resources/api/resourceTypeCounts.js +35 -0
  102. package/dist/modules/resources/api/resourceTypeCounts.js.map +7 -0
  103. package/dist/modules/resources/backend/resources/resource-types/[id]/edit/page.js +1 -1
  104. package/dist/modules/resources/backend/resources/resource-types/[id]/edit/page.js.map +1 -1
  105. package/dist/modules/resources/backend/resources/resource-types/page.js +2 -1
  106. package/dist/modules/resources/backend/resources/resource-types/page.js.map +2 -2
  107. package/dist/modules/sales/components/documents/ReturnDialog.js +4 -1
  108. package/dist/modules/sales/components/documents/ReturnDialog.js.map +2 -2
  109. package/dist/modules/sales/components/documents/ShipmentDialog.js +17 -2
  110. package/dist/modules/sales/components/documents/ShipmentDialog.js.map +2 -2
  111. package/dist/modules/sales/di.js +4 -0
  112. package/dist/modules/sales/di.js.map +2 -2
  113. package/dist/modules/sales/services/salesOrderService.js +26 -0
  114. package/dist/modules/sales/services/salesOrderService.js.map +7 -0
  115. package/dist/modules/shipping_carriers/api/shipments/route.js +8 -0
  116. package/dist/modules/shipping_carriers/api/shipments/route.js.map +2 -2
  117. package/dist/modules/shipping_carriers/data/entities.js +38 -0
  118. package/dist/modules/shipping_carriers/data/entities.js.map +2 -2
  119. package/dist/modules/shipping_carriers/data/validators.js +2 -1
  120. package/dist/modules/shipping_carriers/data/validators.js.map +2 -2
  121. package/dist/modules/shipping_carriers/lib/shipment-idempotency.js +76 -0
  122. package/dist/modules/shipping_carriers/lib/shipment-idempotency.js.map +7 -0
  123. package/dist/modules/shipping_carriers/lib/shipment-wizard/hooks/shipmentApi.js +2 -1
  124. package/dist/modules/shipping_carriers/lib/shipment-wizard/hooks/shipmentApi.js.map +2 -2
  125. package/dist/modules/shipping_carriers/lib/shipment-wizard/hooks/useShipmentWizard.js +20 -2
  126. package/dist/modules/shipping_carriers/lib/shipment-wizard/hooks/useShipmentWizard.js.map +2 -2
  127. package/dist/modules/shipping_carriers/lib/shipping-service.js +82 -43
  128. package/dist/modules/shipping_carriers/lib/shipping-service.js.map +2 -2
  129. package/dist/modules/shipping_carriers/migrations/Migration20260618173616_shipping_carriers.js +14 -0
  130. package/dist/modules/shipping_carriers/migrations/Migration20260618173616_shipping_carriers.js.map +7 -0
  131. package/dist/modules/staff/lib/timesheets-ui/ListView.js +15 -6
  132. package/dist/modules/staff/lib/timesheets-ui/ListView.js.map +2 -2
  133. package/dist/modules/translations/components/TranslationManager.js +9 -18
  134. package/dist/modules/translations/components/TranslationManager.js.map +2 -2
  135. package/generated/entities/carrier_shipment_idempotency_key/index.ts +8 -0
  136. package/generated/entities.ids.generated.ts +2 -1
  137. package/generated/entity-fields-registry.ts +10 -0
  138. package/package.json +9 -8
  139. package/src/modules/catalog/lib/productResolution.ts +75 -0
  140. package/src/modules/communication_channels/workers/channel-import-history.ts +1 -1
  141. package/src/modules/currencies/api/fetch-configs/route.ts +36 -0
  142. package/src/modules/currencies/components/CurrencyFetchingConfig.tsx +20 -17
  143. package/src/modules/currencies/i18n/de.json +2 -1
  144. package/src/modules/currencies/i18n/en.json +2 -1
  145. package/src/modules/currencies/i18n/es.json +2 -1
  146. package/src/modules/currencies/i18n/pl.json +2 -1
  147. package/src/modules/customers/api/companies/[id]/route.ts +214 -189
  148. package/src/modules/customers/api/people/[id]/route.ts +106 -80
  149. package/src/modules/customers/backend/config/customers/pipeline-stages/page.tsx +19 -12
  150. package/src/modules/customers/components/detail/ActivitiesAddNewMenu.tsx +8 -6
  151. package/src/modules/customers/components/detail/AiActionChips.tsx +6 -3
  152. package/src/modules/customers/components/detail/schedule/ParticipantsField.tsx +16 -18
  153. package/src/modules/data_sync/api/runs/[id]/cancel.ts +1 -1
  154. package/src/modules/data_sync/backend/data-sync/page.tsx +11 -58
  155. package/src/modules/data_sync/backend/data-sync/runs/[id]/page.tsx +13 -20
  156. package/src/modules/data_sync/components/IntegrationScheduleTab.tsx +10 -8
  157. package/src/modules/data_sync/lib/id-mapping.ts +58 -1
  158. package/src/modules/data_sync/lib/sync-engine.ts +2 -2
  159. package/src/modules/data_sync/lib/syncRunStatus.ts +39 -0
  160. package/src/modules/data_sync/workers/sync-scheduled.ts +13 -14
  161. package/src/modules/directory/components/TenantSelect.tsx +33 -16
  162. package/src/modules/entities/api/records.ts +14 -0
  163. package/src/modules/feature_toggles/api/global/route.ts +16 -14
  164. package/src/modules/feature_toggles/api/openapi.ts +2 -2
  165. package/src/modules/feature_toggles/commands/global.ts +15 -0
  166. package/src/modules/inbox_ops/backend/inbox-ops/page.tsx +7 -0
  167. package/src/modules/messages/components/message-detail/hooks/useMessageDetails.ts +2 -0
  168. package/src/modules/messages/components/message-detail/hooks/useMessageDetailsActions.ts +14 -4
  169. package/src/modules/messages/components/message-detail/hooks/useMessageDetailsQueries.ts +15 -1
  170. package/src/modules/payment_gateways/api/cancel/route.ts +5 -0
  171. package/src/modules/payment_gateways/api/capture/route.ts +5 -0
  172. package/src/modules/payment_gateways/api/refund/route.ts +5 -0
  173. package/src/modules/payment_gateways/lib/gateway-service.ts +58 -25
  174. package/src/modules/payment_gateways/lib/status-machine.ts +16 -0
  175. package/src/modules/perspectives/api/[tableId]/route.ts +8 -2
  176. package/src/modules/perspectives/data/entities.ts +43 -3
  177. package/src/modules/perspectives/migrations/.snapshot-open-mercato.json +69 -25
  178. package/src/modules/perspectives/migrations/Migration20260619120000_perspectives_live_row_uniqueness.ts +65 -0
  179. package/src/modules/perspectives/services/perspectiveService.ts +17 -0
  180. package/src/modules/planner/commands/availability-weekly.ts +48 -1
  181. package/src/modules/planner/components/AvailabilityRulesEditor.tsx +31 -10
  182. package/src/modules/portal/frontend/[orgSlug]/portal/dashboard/hiddenWidgetsStorage.ts +36 -0
  183. package/src/modules/portal/frontend/[orgSlug]/portal/dashboard/page.tsx +22 -23
  184. package/src/modules/progress/api/active/route.ts +1 -1
  185. package/src/modules/progress/lib/progressService.ts +2 -2
  186. package/src/modules/progress/lib/progressServiceImpl.ts +33 -8
  187. package/src/modules/query_index/api/status.ts +28 -32
  188. package/src/modules/query_index/lib/coverage.ts +63 -0
  189. package/src/modules/query_index/lib/engine.ts +37 -24
  190. package/src/modules/resources/api/resource-types.ts +4 -44
  191. package/src/modules/resources/api/resourceTypeCounts.ts +74 -0
  192. package/src/modules/resources/backend/resources/resource-types/[id]/edit/page.tsx +1 -1
  193. package/src/modules/resources/backend/resources/resource-types/page.tsx +1 -0
  194. package/src/modules/sales/components/documents/ReturnDialog.tsx +4 -1
  195. package/src/modules/sales/components/documents/ShipmentDialog.tsx +20 -2
  196. package/src/modules/sales/di.ts +6 -0
  197. package/src/modules/sales/services/salesOrderService.ts +38 -0
  198. package/src/modules/shipping_carriers/api/shipments/route.ts +8 -0
  199. package/src/modules/shipping_carriers/data/entities.ts +33 -0
  200. package/src/modules/shipping_carriers/data/validators.ts +1 -0
  201. package/src/modules/shipping_carriers/i18n/de.json +1 -0
  202. package/src/modules/shipping_carriers/i18n/en.json +1 -0
  203. package/src/modules/shipping_carriers/i18n/es.json +1 -0
  204. package/src/modules/shipping_carriers/i18n/pl.json +1 -0
  205. package/src/modules/shipping_carriers/lib/shipment-idempotency.ts +119 -0
  206. package/src/modules/shipping_carriers/lib/shipment-wizard/hooks/shipmentApi.ts +3 -1
  207. package/src/modules/shipping_carriers/lib/shipment-wizard/hooks/useShipmentWizard.ts +24 -2
  208. package/src/modules/shipping_carriers/lib/shipping-service.ts +87 -43
  209. package/src/modules/shipping_carriers/migrations/.snapshot-open-mercato.json +197 -0
  210. package/src/modules/shipping_carriers/migrations/Migration20260618173616_shipping_carriers.ts +14 -0
  211. package/src/modules/staff/i18n/de.json +1 -0
  212. package/src/modules/staff/i18n/en.json +1 -0
  213. package/src/modules/staff/i18n/es.json +1 -0
  214. package/src/modules/staff/i18n/pl.json +1 -0
  215. package/src/modules/staff/lib/timesheets-ui/ListView.tsx +15 -7
  216. package/src/modules/translations/components/TranslationManager.tsx +17 -24
  217. package/src/modules/translations/i18n/de.json +1 -0
  218. package/src/modules/translations/i18n/en.json +1 -0
  219. package/src/modules/translations/i18n/es.json +1 -0
  220. package/src/modules/translations/i18n/pl.json +1 -0
@@ -6,6 +6,7 @@ import {
6
6
  runCrudMutationGuardAfterSuccess,
7
7
  validateCrudMutationGuard
8
8
  } from "@open-mercato/shared/lib/crud/mutation-guard";
9
+ import { isShipmentIdempotencyConflictError } from "../../lib/shipment-idempotency.js";
9
10
  import { createShipmentSchema } from "../../data/validators.js";
10
11
  import { shippingCarriersTag } from "../openapi.js";
11
12
  function resolveGuardUserId(auth) {
@@ -72,6 +73,12 @@ async function POST(req) {
72
73
  labelUrl: shipment.labelUrl
73
74
  }, { status: 201 });
74
75
  } catch (error) {
76
+ if (isShipmentIdempotencyConflictError(error)) {
77
+ return NextResponse.json(
78
+ { error: "Shipment idempotency conflict", code: "idempotency_conflict" },
79
+ { status: 409 }
80
+ );
81
+ }
75
82
  const message = error instanceof Error ? error.message : "Failed to create shipment";
76
83
  return NextResponse.json({ error: message }, { status: 502 });
77
84
  }
@@ -85,6 +92,7 @@ const openApi = {
85
92
  tags: [shippingCarriersTag],
86
93
  responses: [
87
94
  { status: 201, description: "Shipment created" },
95
+ { status: 409, description: "Idempotency conflict: the idempotency key was reused with a different payload" },
88
96
  { status: 422, description: "Validation failed" },
89
97
  { status: 502, description: "Provider upstream error" }
90
98
  ]
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/shipping_carriers/api/shipments/route.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'\nimport {\n runCrudMutationGuardAfterSuccess,\n validateCrudMutationGuard,\n} from '@open-mercato/shared/lib/crud/mutation-guard'\nimport type { ShippingCarrierService } from '../../lib/shipping-service'\nimport { createShipmentSchema } from '../../data/validators'\nimport { shippingCarriersTag } from '../openapi'\n\nfunction resolveGuardUserId(auth: {\n sub?: string | null\n userId?: string | null\n keyId?: string | null\n}): string {\n if (typeof auth.sub === 'string' && auth.sub.trim().length > 0) return auth.sub\n if (typeof auth.userId === 'string' && auth.userId.trim().length > 0) return auth.userId\n if (typeof auth.keyId === 'string' && auth.keyId.trim().length > 0) return auth.keyId\n return 'system'\n}\n\nexport const metadata = {\n path: '/shipping-carriers/shipments',\n POST: { requireAuth: true, requireFeatures: ['shipping_carriers.manage'] },\n}\n\nexport async function POST(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth?.tenantId || !auth.orgId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n const payload = await readJsonSafe<unknown>(req)\n const parsed = createShipmentSchema.safeParse(payload)\n if (!parsed.success) {\n return NextResponse.json({ error: 'Invalid payload', details: parsed.error.flatten() }, { status: 422 })\n }\n const container = await createRequestContainer()\n const guardUserId = resolveGuardUserId(auth)\n const guardResult = await validateCrudMutationGuard(container, {\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n userId: guardUserId,\n resourceKind: 'shipping_carriers.shipment',\n resourceId: parsed.data.orderId,\n operation: 'create',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: parsed.data as Record<string, unknown>,\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n const service = container.resolve('shippingCarrierService') as ShippingCarrierService\n try {\n const shipment = await service.createShipment({\n ...parsed.data,\n organizationId: auth.orgId as string,\n tenantId: auth.tenantId,\n })\n\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(container, {\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n userId: guardUserId,\n resourceKind: 'shipping_carriers.shipment',\n resourceId: parsed.data.orderId,\n operation: 'create',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n\n return NextResponse.json({\n shipmentId: shipment.id,\n carrierShipmentId: shipment.carrierShipmentId,\n trackingNumber: shipment.trackingNumber,\n status: shipment.unifiedStatus,\n labelUrl: shipment.labelUrl,\n }, { status: 201 })\n } catch (error: unknown) {\n const message = error instanceof Error ? error.message : 'Failed to create shipment'\n return NextResponse.json({ error: message }, { status: 502 })\n }\n}\n\nexport const openApi = {\n tags: [shippingCarriersTag],\n summary: 'Create shipment',\n methods: {\n POST: {\n summary: 'Create shipment',\n tags: [shippingCarriersTag],\n responses: [\n { status: 201, description: 'Shipment created' },\n { status: 422, description: 'Validation failed' },\n { status: 502, description: 'Provider upstream error' },\n ],\n },\n },\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'\nimport {\n runCrudMutationGuardAfterSuccess,\n validateCrudMutationGuard,\n} from '@open-mercato/shared/lib/crud/mutation-guard'\nimport type { ShippingCarrierService } from '../../lib/shipping-service'\nimport { isShipmentIdempotencyConflictError } from '../../lib/shipment-idempotency'\nimport { createShipmentSchema } from '../../data/validators'\nimport { shippingCarriersTag } from '../openapi'\n\nfunction resolveGuardUserId(auth: {\n sub?: string | null\n userId?: string | null\n keyId?: string | null\n}): string {\n if (typeof auth.sub === 'string' && auth.sub.trim().length > 0) return auth.sub\n if (typeof auth.userId === 'string' && auth.userId.trim().length > 0) return auth.userId\n if (typeof auth.keyId === 'string' && auth.keyId.trim().length > 0) return auth.keyId\n return 'system'\n}\n\nexport const metadata = {\n path: '/shipping-carriers/shipments',\n POST: { requireAuth: true, requireFeatures: ['shipping_carriers.manage'] },\n}\n\nexport async function POST(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth?.tenantId || !auth.orgId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n const payload = await readJsonSafe<unknown>(req)\n const parsed = createShipmentSchema.safeParse(payload)\n if (!parsed.success) {\n return NextResponse.json({ error: 'Invalid payload', details: parsed.error.flatten() }, { status: 422 })\n }\n const container = await createRequestContainer()\n const guardUserId = resolveGuardUserId(auth)\n const guardResult = await validateCrudMutationGuard(container, {\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n userId: guardUserId,\n resourceKind: 'shipping_carriers.shipment',\n resourceId: parsed.data.orderId,\n operation: 'create',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: parsed.data as Record<string, unknown>,\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n const service = container.resolve('shippingCarrierService') as ShippingCarrierService\n try {\n const shipment = await service.createShipment({\n ...parsed.data,\n organizationId: auth.orgId as string,\n tenantId: auth.tenantId,\n })\n\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(container, {\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n userId: guardUserId,\n resourceKind: 'shipping_carriers.shipment',\n resourceId: parsed.data.orderId,\n operation: 'create',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n\n return NextResponse.json({\n shipmentId: shipment.id,\n carrierShipmentId: shipment.carrierShipmentId,\n trackingNumber: shipment.trackingNumber,\n status: shipment.unifiedStatus,\n labelUrl: shipment.labelUrl,\n }, { status: 201 })\n } catch (error: unknown) {\n if (isShipmentIdempotencyConflictError(error)) {\n return NextResponse.json(\n { error: 'Shipment idempotency conflict', code: 'idempotency_conflict' },\n { status: 409 },\n )\n }\n const message = error instanceof Error ? error.message : 'Failed to create shipment'\n return NextResponse.json({ error: message }, { status: 502 })\n }\n}\n\nexport const openApi = {\n tags: [shippingCarriersTag],\n summary: 'Create shipment',\n methods: {\n POST: {\n summary: 'Create shipment',\n tags: [shippingCarriersTag],\n responses: [\n { status: 201, description: 'Shipment created' },\n { status: 409, description: 'Idempotency conflict: the idempotency key was reused with a different payload' },\n { status: 422, description: 'Validation failed' },\n { status: 502, description: 'Provider upstream error' },\n ],\n },\n },\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -109,8 +109,46 @@ CarrierWebhookProcessedEvent = __decorateClass([
109
109
  properties: ["idempotencyKey", "providerKey", "organizationId", "tenantId"]
110
110
  })
111
111
  ], CarrierWebhookProcessedEvent);
112
+ OptionalProps;
113
+ let CarrierShipmentIdempotencyKey = class {
114
+ constructor() {
115
+ this.createdAt = /* @__PURE__ */ new Date();
116
+ }
117
+ };
118
+ __decorateClass([
119
+ PrimaryKey({ type: "uuid", defaultRaw: "gen_random_uuid()" })
120
+ ], CarrierShipmentIdempotencyKey.prototype, "id", 2);
121
+ __decorateClass([
122
+ Property({ name: "provider_key", type: "text" })
123
+ ], CarrierShipmentIdempotencyKey.prototype, "providerKey", 2);
124
+ __decorateClass([
125
+ Property({ name: "idempotency_key", type: "text" })
126
+ ], CarrierShipmentIdempotencyKey.prototype, "idempotencyKey", 2);
127
+ __decorateClass([
128
+ Property({ name: "request_hash", type: "text" })
129
+ ], CarrierShipmentIdempotencyKey.prototype, "requestHash", 2);
130
+ __decorateClass([
131
+ Property({ name: "shipment_id", type: "uuid", nullable: true })
132
+ ], CarrierShipmentIdempotencyKey.prototype, "shipmentId", 2);
133
+ __decorateClass([
134
+ Property({ name: "organization_id", type: "uuid" })
135
+ ], CarrierShipmentIdempotencyKey.prototype, "organizationId", 2);
136
+ __decorateClass([
137
+ Property({ name: "tenant_id", type: "uuid" })
138
+ ], CarrierShipmentIdempotencyKey.prototype, "tenantId", 2);
139
+ __decorateClass([
140
+ Property({ name: "created_at", type: Date, onCreate: () => /* @__PURE__ */ new Date() })
141
+ ], CarrierShipmentIdempotencyKey.prototype, "createdAt", 2);
142
+ CarrierShipmentIdempotencyKey = __decorateClass([
143
+ Entity({ tableName: "carrier_shipment_idempotency_keys" }),
144
+ Unique({
145
+ name: "carrier_shipment_idempotency_keys_unique",
146
+ properties: ["idempotencyKey", "providerKey", "organizationId", "tenantId"]
147
+ })
148
+ ], CarrierShipmentIdempotencyKey);
112
149
  export {
113
150
  CarrierShipment,
151
+ CarrierShipmentIdempotencyKey,
114
152
  CarrierWebhookProcessedEvent
115
153
  };
116
154
  //# sourceMappingURL=entities.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/shipping_carriers/data/entities.ts"],
4
- "sourcesContent": ["import { OptionalProps } from '@mikro-orm/core'\nimport { Entity, Index, PrimaryKey, Property, Unique } from '@mikro-orm/decorators/legacy'\n\n@Entity({ tableName: 'carrier_shipments' })\n@Index({ properties: ['orderId', 'organizationId', 'tenantId'] })\n@Index({ properties: ['providerKey', 'carrierShipmentId', 'organizationId'] })\n@Index({ properties: ['organizationId', 'tenantId', 'unifiedStatus'] })\nexport class CarrierShipment {\n [OptionalProps]?: 'labelUrl' | 'labelData' | 'trackingEvents' | 'carrierStatus' | 'lastWebhookAt' | 'lastPolledAt' | 'createdAt' | 'updatedAt' | 'deletedAt'\n\n @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })\n id!: string\n\n @Property({ name: 'order_id', type: 'uuid' })\n orderId!: string\n\n @Property({ name: 'provider_key', type: 'text' })\n providerKey!: string\n\n @Property({ name: 'carrier_shipment_id', type: 'text' })\n carrierShipmentId!: string\n\n @Property({ name: 'tracking_number', type: 'text' })\n trackingNumber!: string\n\n @Property({ name: 'unified_status', type: 'text' })\n unifiedStatus: string = 'label_created'\n\n @Property({ name: 'carrier_status', type: 'text', nullable: true })\n carrierStatus?: string | null\n\n @Property({ name: 'label_url', type: 'text', nullable: true })\n labelUrl?: string | null\n\n @Property({ name: 'label_data', type: 'text', nullable: true })\n labelData?: string | null\n\n @Property({ name: 'tracking_events', type: 'jsonb', nullable: true })\n trackingEvents?: Array<{ status: string; occurredAt: string; location?: string }> | null\n\n @Property({ name: 'organization_id', type: 'uuid' })\n organizationId!: string\n\n @Property({ name: 'tenant_id', type: 'uuid' })\n tenantId!: string\n\n @Property({ name: 'last_webhook_at', type: Date, nullable: true })\n lastWebhookAt?: Date | null\n\n @Property({ name: 'last_polled_at', type: Date, nullable: true })\n lastPolledAt?: Date | null\n\n @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })\n createdAt: Date = new Date()\n\n @Property({ name: 'updated_at', type: Date, onUpdate: () => new Date() })\n updatedAt: Date = new Date()\n\n @Property({ name: 'deleted_at', type: Date, nullable: true })\n deletedAt?: Date | null\n}\n\n@Entity({ tableName: 'carrier_webhook_events' })\n@Unique({\n name: 'carrier_webhook_events_idempotency_unique',\n properties: ['idempotencyKey', 'providerKey', 'organizationId', 'tenantId'],\n})\nexport class CarrierWebhookProcessedEvent {\n [OptionalProps]?: 'processedAt'\n\n @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })\n id!: string\n\n @Property({ name: 'provider_key', type: 'text' })\n providerKey!: string\n\n @Property({ name: 'idempotency_key', type: 'text' })\n idempotencyKey!: string\n\n @Property({ name: 'event_type', type: 'text' })\n eventType!: string\n\n @Property({ name: 'organization_id', type: 'uuid' })\n organizationId!: string\n\n @Property({ name: 'tenant_id', type: 'uuid' })\n tenantId!: string\n\n @Property({ name: 'processed_at', type: Date, onCreate: () => new Date() })\n processedAt: Date = new Date()\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { OptionalProps } from '@mikro-orm/core'\nimport { Entity, Index, PrimaryKey, Property, Unique } from '@mikro-orm/decorators/legacy'\n\n@Entity({ tableName: 'carrier_shipments' })\n@Index({ properties: ['orderId', 'organizationId', 'tenantId'] })\n@Index({ properties: ['providerKey', 'carrierShipmentId', 'organizationId'] })\n@Index({ properties: ['organizationId', 'tenantId', 'unifiedStatus'] })\nexport class CarrierShipment {\n [OptionalProps]?: 'labelUrl' | 'labelData' | 'trackingEvents' | 'carrierStatus' | 'lastWebhookAt' | 'lastPolledAt' | 'createdAt' | 'updatedAt' | 'deletedAt'\n\n @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })\n id!: string\n\n @Property({ name: 'order_id', type: 'uuid' })\n orderId!: string\n\n @Property({ name: 'provider_key', type: 'text' })\n providerKey!: string\n\n @Property({ name: 'carrier_shipment_id', type: 'text' })\n carrierShipmentId!: string\n\n @Property({ name: 'tracking_number', type: 'text' })\n trackingNumber!: string\n\n @Property({ name: 'unified_status', type: 'text' })\n unifiedStatus: string = 'label_created'\n\n @Property({ name: 'carrier_status', type: 'text', nullable: true })\n carrierStatus?: string | null\n\n @Property({ name: 'label_url', type: 'text', nullable: true })\n labelUrl?: string | null\n\n @Property({ name: 'label_data', type: 'text', nullable: true })\n labelData?: string | null\n\n @Property({ name: 'tracking_events', type: 'jsonb', nullable: true })\n trackingEvents?: Array<{ status: string; occurredAt: string; location?: string }> | null\n\n @Property({ name: 'organization_id', type: 'uuid' })\n organizationId!: string\n\n @Property({ name: 'tenant_id', type: 'uuid' })\n tenantId!: string\n\n @Property({ name: 'last_webhook_at', type: Date, nullable: true })\n lastWebhookAt?: Date | null\n\n @Property({ name: 'last_polled_at', type: Date, nullable: true })\n lastPolledAt?: Date | null\n\n @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })\n createdAt: Date = new Date()\n\n @Property({ name: 'updated_at', type: Date, onUpdate: () => new Date() })\n updatedAt: Date = new Date()\n\n @Property({ name: 'deleted_at', type: Date, nullable: true })\n deletedAt?: Date | null\n}\n\n@Entity({ tableName: 'carrier_webhook_events' })\n@Unique({\n name: 'carrier_webhook_events_idempotency_unique',\n properties: ['idempotencyKey', 'providerKey', 'organizationId', 'tenantId'],\n})\nexport class CarrierWebhookProcessedEvent {\n [OptionalProps]?: 'processedAt'\n\n @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })\n id!: string\n\n @Property({ name: 'provider_key', type: 'text' })\n providerKey!: string\n\n @Property({ name: 'idempotency_key', type: 'text' })\n idempotencyKey!: string\n\n @Property({ name: 'event_type', type: 'text' })\n eventType!: string\n\n @Property({ name: 'organization_id', type: 'uuid' })\n organizationId!: string\n\n @Property({ name: 'tenant_id', type: 'uuid' })\n tenantId!: string\n\n @Property({ name: 'processed_at', type: Date, onCreate: () => new Date() })\n processedAt: Date = new Date()\n}\n\n@Entity({ tableName: 'carrier_shipment_idempotency_keys' })\n@Unique({\n name: 'carrier_shipment_idempotency_keys_unique',\n properties: ['idempotencyKey', 'providerKey', 'organizationId', 'tenantId'],\n})\nexport class CarrierShipmentIdempotencyKey {\n [OptionalProps]?: 'shipmentId' | 'createdAt'\n\n @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })\n id!: string\n\n @Property({ name: 'provider_key', type: 'text' })\n providerKey!: string\n\n @Property({ name: 'idempotency_key', type: 'text' })\n idempotencyKey!: string\n\n @Property({ name: 'request_hash', type: 'text' })\n requestHash!: string\n\n @Property({ name: 'shipment_id', type: 'uuid', nullable: true })\n shipmentId?: string | null\n\n @Property({ name: 'organization_id', type: 'uuid' })\n organizationId!: string\n\n @Property({ name: 'tenant_id', type: 'uuid' })\n tenantId!: string\n\n @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })\n createdAt: Date = new Date()\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -36,7 +36,8 @@ const createShipmentSchema = z.object({
36
36
  receiverPhone: phoneField,
37
37
  receiverEmail: emailField,
38
38
  targetPoint: z.string().optional(),
39
- c2cSendingMethod: z.enum(["parcel_locker", "dispatch_order", "pop", "any_point"]).optional()
39
+ c2cSendingMethod: z.enum(["parcel_locker", "dispatch_order", "pop", "any_point"]).optional(),
40
+ idempotencyKey: z.string().min(1).max(255).optional()
40
41
  });
41
42
  const trackingQuerySchema = z.object({
42
43
  providerKey: z.string().min(1),
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/shipping_carriers/data/validators.ts"],
4
- "sourcesContent": ["import { z } from 'zod'\n\nconst addressSchema = z.object({\n countryCode: z.string().min(2).max(3),\n postalCode: z.string().min(1),\n city: z.string().min(1),\n line1: z.string().min(1),\n line2: z.string().optional(),\n})\n\nconst packageSchema = z.object({\n weightKg: z.number().positive(),\n lengthCm: z.number().positive(),\n widthCm: z.number().positive(),\n heightCm: z.number().positive(),\n})\n\n/** Permissive phone regex: allows +, digits, spaces, dashes, dots, and parentheses. Min 7 chars. */\nexport const PHONE_REGEX = /^[+\\d][\\d\\s\\-().]{6,}$/\n\nconst emailField = z.string().trim().email('Invalid email address.').max(320).optional()\nconst phoneField = z.string().trim().regex(PHONE_REGEX, 'Invalid phone number.').max(50).optional()\n\nexport const calculateRatesSchema = z.object({\n providerKey: z.string().min(1),\n origin: addressSchema,\n destination: addressSchema,\n packages: z.array(packageSchema).min(1),\n receiverPhone: phoneField,\n receiverEmail: emailField,\n})\n\nexport const createShipmentSchema = z.object({\n providerKey: z.string().min(1),\n orderId: z.string().uuid(),\n origin: addressSchema,\n destination: addressSchema,\n packages: z.array(packageSchema).min(1),\n serviceCode: z.string().min(1),\n labelFormat: z.enum(['pdf', 'zpl', 'png']).optional(),\n senderPhone: phoneField,\n senderEmail: emailField,\n receiverPhone: phoneField,\n receiverEmail: emailField,\n targetPoint: z.string().optional(),\n c2cSendingMethod: z.enum(['parcel_locker', 'dispatch_order', 'pop', 'any_point']).optional(),\n})\n\nexport const trackingQuerySchema = z.object({\n providerKey: z.string().min(1),\n shipmentId: z.string().uuid().optional(),\n trackingNumber: z.string().min(1).optional(),\n}).refine((value) => Boolean(value.shipmentId || value.trackingNumber), {\n message: 'shipmentId or trackingNumber is required',\n path: ['shipmentId'],\n})\n\nexport const trackingRefreshSchema = z.object({\n providerKey: z.string().min(1),\n shipmentId: z.string().uuid(),\n})\n\nexport const cancelShipmentSchema = z.object({\n providerKey: z.string().min(1),\n shipmentId: z.string().uuid(),\n reason: z.string().max(200).optional(),\n})\n\nexport const searchDropOffPointsQuerySchema = z.object({\n providerKey: z.string().min(1),\n query: z.string().optional(),\n type: z.string().optional(),\n postCode: z.string().optional(),\n})\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { z } from 'zod'\n\nconst addressSchema = z.object({\n countryCode: z.string().min(2).max(3),\n postalCode: z.string().min(1),\n city: z.string().min(1),\n line1: z.string().min(1),\n line2: z.string().optional(),\n})\n\nconst packageSchema = z.object({\n weightKg: z.number().positive(),\n lengthCm: z.number().positive(),\n widthCm: z.number().positive(),\n heightCm: z.number().positive(),\n})\n\n/** Permissive phone regex: allows +, digits, spaces, dashes, dots, and parentheses. Min 7 chars. */\nexport const PHONE_REGEX = /^[+\\d][\\d\\s\\-().]{6,}$/\n\nconst emailField = z.string().trim().email('Invalid email address.').max(320).optional()\nconst phoneField = z.string().trim().regex(PHONE_REGEX, 'Invalid phone number.').max(50).optional()\n\nexport const calculateRatesSchema = z.object({\n providerKey: z.string().min(1),\n origin: addressSchema,\n destination: addressSchema,\n packages: z.array(packageSchema).min(1),\n receiverPhone: phoneField,\n receiverEmail: emailField,\n})\n\nexport const createShipmentSchema = z.object({\n providerKey: z.string().min(1),\n orderId: z.string().uuid(),\n origin: addressSchema,\n destination: addressSchema,\n packages: z.array(packageSchema).min(1),\n serviceCode: z.string().min(1),\n labelFormat: z.enum(['pdf', 'zpl', 'png']).optional(),\n senderPhone: phoneField,\n senderEmail: emailField,\n receiverPhone: phoneField,\n receiverEmail: emailField,\n targetPoint: z.string().optional(),\n c2cSendingMethod: z.enum(['parcel_locker', 'dispatch_order', 'pop', 'any_point']).optional(),\n idempotencyKey: z.string().min(1).max(255).optional(),\n})\n\nexport const trackingQuerySchema = z.object({\n providerKey: z.string().min(1),\n shipmentId: z.string().uuid().optional(),\n trackingNumber: z.string().min(1).optional(),\n}).refine((value) => Boolean(value.shipmentId || value.trackingNumber), {\n message: 'shipmentId or trackingNumber is required',\n path: ['shipmentId'],\n})\n\nexport const trackingRefreshSchema = z.object({\n providerKey: z.string().min(1),\n shipmentId: z.string().uuid(),\n})\n\nexport const cancelShipmentSchema = z.object({\n providerKey: z.string().min(1),\n shipmentId: z.string().uuid(),\n reason: z.string().max(200).optional(),\n})\n\nexport const searchDropOffPointsQuerySchema = z.object({\n providerKey: z.string().min(1),\n query: z.string().optional(),\n type: z.string().optional(),\n postCode: z.string().optional(),\n})\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,76 @@
1
+ var _a, _b;
2
+ import { createHash } from "node:crypto";
3
+ import { UniqueConstraintViolationException } from "@mikro-orm/core";
4
+ import { findOneWithDecryption } from "@open-mercato/shared/lib/encryption/find";
5
+ import { CarrierShipmentIdempotencyKey } from "../data/entities.js";
6
+ const SHIPMENT_IDEMPOTENCY_CONFLICT_MARKER = /* @__PURE__ */ Symbol.for("@open-mercato/ShipmentIdempotencyConflictError");
7
+ class ShipmentIdempotencyConflictError extends (_b = Error, _a = SHIPMENT_IDEMPOTENCY_CONFLICT_MARKER, _b) {
8
+ constructor(idempotencyKey) {
9
+ super(`[internal] Shipment idempotency conflict for key "${idempotencyKey}"`);
10
+ this[_a] = true;
11
+ this.name = "ShipmentIdempotencyConflictError";
12
+ this.idempotencyKey = idempotencyKey;
13
+ }
14
+ }
15
+ function isShipmentIdempotencyConflictError(error) {
16
+ return !!error && typeof error === "object" && error[SHIPMENT_IDEMPOTENCY_CONFLICT_MARKER] === true;
17
+ }
18
+ function stableSerialize(value) {
19
+ if (value === null || typeof value !== "object") return JSON.stringify(value) ?? "null";
20
+ if (Array.isArray(value)) return `[${value.map(stableSerialize).join(",")}]`;
21
+ const entries = Object.entries(value).filter(([, entryValue]) => entryValue !== void 0).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
22
+ return `{${entries.map(([key, entryValue]) => `${JSON.stringify(key)}:${stableSerialize(entryValue)}`).join(",")}}`;
23
+ }
24
+ function computeShipmentRequestHash(fingerprint) {
25
+ return createHash("sha256").update(stableSerialize(fingerprint)).digest("hex");
26
+ }
27
+ async function findShipmentIdempotencyClaim(em, idempotencyKey, providerKey, scope) {
28
+ return findOneWithDecryption(
29
+ em,
30
+ CarrierShipmentIdempotencyKey,
31
+ {
32
+ idempotencyKey,
33
+ providerKey,
34
+ organizationId: scope.organizationId,
35
+ tenantId: scope.tenantId
36
+ },
37
+ void 0,
38
+ scope
39
+ );
40
+ }
41
+ async function claimShipmentIdempotency(em, idempotencyKey, providerKey, requestHash, scope) {
42
+ const record = em.create(CarrierShipmentIdempotencyKey, {
43
+ idempotencyKey,
44
+ providerKey,
45
+ requestHash,
46
+ shipmentId: null,
47
+ organizationId: scope.organizationId,
48
+ tenantId: scope.tenantId
49
+ });
50
+ try {
51
+ await em.persist(record).flush();
52
+ return record;
53
+ } catch (error) {
54
+ if (error instanceof UniqueConstraintViolationException) return null;
55
+ throw error;
56
+ }
57
+ }
58
+ async function resolveShipmentIdempotency(em, claim, shipmentId) {
59
+ claim.shipmentId = shipmentId;
60
+ await em.flush();
61
+ }
62
+ async function releaseShipmentIdempotency(em, idempotencyKey, providerKey, scope) {
63
+ const existing = await findShipmentIdempotencyClaim(em, idempotencyKey, providerKey, scope);
64
+ if (!existing) return;
65
+ await em.remove(existing).flush();
66
+ }
67
+ export {
68
+ ShipmentIdempotencyConflictError,
69
+ claimShipmentIdempotency,
70
+ computeShipmentRequestHash,
71
+ findShipmentIdempotencyClaim,
72
+ isShipmentIdempotencyConflictError,
73
+ releaseShipmentIdempotency,
74
+ resolveShipmentIdempotency
75
+ };
76
+ //# sourceMappingURL=shipment-idempotency.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/shipping_carriers/lib/shipment-idempotency.ts"],
4
+ "sourcesContent": ["import { createHash } from 'node:crypto'\nimport { UniqueConstraintViolationException } from '@mikro-orm/core'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { CarrierShipmentIdempotencyKey } from '../data/entities'\n\ntype Scope = { organizationId: string; tenantId: string }\n\n// Use Symbol.for so the marker survives module duplication across bundle boundaries \u2014 an\n// `instanceof` check returns false when the API route and the service load separate copies of\n// this class in the production build, which would silently downgrade the 409 conflict to a 502.\nconst SHIPMENT_IDEMPOTENCY_CONFLICT_MARKER = Symbol.for('@open-mercato/ShipmentIdempotencyConflictError')\n\nexport class ShipmentIdempotencyConflictError extends Error {\n readonly [SHIPMENT_IDEMPOTENCY_CONFLICT_MARKER] = true\n readonly idempotencyKey: string\n constructor(idempotencyKey: string) {\n super(`[internal] Shipment idempotency conflict for key \"${idempotencyKey}\"`)\n this.name = 'ShipmentIdempotencyConflictError'\n this.idempotencyKey = idempotencyKey\n }\n}\n\nexport function isShipmentIdempotencyConflictError(error: unknown): error is ShipmentIdempotencyConflictError {\n return (\n !!error &&\n typeof error === 'object' &&\n (error as Record<symbol, unknown>)[SHIPMENT_IDEMPOTENCY_CONFLICT_MARKER] === true\n )\n}\n\nfunction stableSerialize(value: unknown): string {\n if (value === null || typeof value !== 'object') return JSON.stringify(value) ?? 'null'\n if (Array.isArray(value)) return `[${value.map(stableSerialize).join(',')}]`\n const entries = Object.entries(value as Record<string, unknown>)\n .filter(([, entryValue]) => entryValue !== undefined)\n .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))\n return `{${entries.map(([key, entryValue]) => `${JSON.stringify(key)}:${stableSerialize(entryValue)}`).join(',')}}`\n}\n\n/**\n * Deterministic fingerprint of a shipment-create request so that reusing the same\n * idempotency key with a conflicting payload can be detected. Key order is\n * normalized and `undefined` values are dropped so semantically equal requests\n * hash identically.\n */\nexport function computeShipmentRequestHash(fingerprint: unknown): string {\n return createHash('sha256').update(stableSerialize(fingerprint)).digest('hex')\n}\n\nexport async function findShipmentIdempotencyClaim(\n em: EntityManager,\n idempotencyKey: string,\n providerKey: string,\n scope: Scope,\n): Promise<CarrierShipmentIdempotencyKey | null> {\n return findOneWithDecryption(\n em,\n CarrierShipmentIdempotencyKey,\n {\n idempotencyKey,\n providerKey,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n },\n undefined,\n scope,\n )\n}\n\n/**\n * Atomically claims an idempotency key by inserting the dedup row. Returns the\n * managed claim on success, or `null` when a concurrent request already inserted\n * the same scoped key (the unique constraint fires), mirroring the webhook-event\n * claim idiom.\n */\nexport async function claimShipmentIdempotency(\n em: EntityManager,\n idempotencyKey: string,\n providerKey: string,\n requestHash: string,\n scope: Scope,\n): Promise<CarrierShipmentIdempotencyKey | null> {\n const record = em.create(CarrierShipmentIdempotencyKey, {\n idempotencyKey,\n providerKey,\n requestHash,\n shipmentId: null,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n })\n try {\n await em.persist(record).flush()\n return record\n } catch (error: unknown) {\n if (error instanceof UniqueConstraintViolationException) return null\n throw error\n }\n}\n\nexport async function resolveShipmentIdempotency(\n em: EntityManager,\n claim: CarrierShipmentIdempotencyKey,\n shipmentId: string,\n): Promise<void> {\n claim.shipmentId = shipmentId\n await em.flush()\n}\n\nexport async function releaseShipmentIdempotency(\n em: EntityManager,\n idempotencyKey: string,\n providerKey: string,\n scope: Scope,\n): Promise<void> {\n const existing = await findShipmentIdempotencyClaim(em, idempotencyKey, providerKey, scope)\n if (!existing) return\n await em.remove(existing).flush()\n}\n"],
5
+ "mappings": "AAAA;AAAA,SAAS,kBAAkB;AAC3B,SAAS,0CAA0C;AAEnD,SAAS,6BAA6B;AACtC,SAAS,qCAAqC;AAO9C,MAAM,uCAAuC,uBAAO,IAAI,gDAAgD;AAEjG,MAAM,0CAAyC,YAC1C,2CAD0C,IAAM;AAAA,EAG1D,YAAY,gBAAwB;AAClC,UAAM,qDAAqD,cAAc,GAAG;AAH9E,SAAU,MAAwC;AAIhD,SAAK,OAAO;AACZ,SAAK,iBAAiB;AAAA,EACxB;AACF;AAEO,SAAS,mCAAmC,OAA2D;AAC5G,SACE,CAAC,CAAC,SACF,OAAO,UAAU,YAChB,MAAkC,oCAAoC,MAAM;AAEjF;AAEA,SAAS,gBAAgB,OAAwB;AAC/C,MAAI,UAAU,QAAQ,OAAO,UAAU,SAAU,QAAO,KAAK,UAAU,KAAK,KAAK;AACjF,MAAI,MAAM,QAAQ,KAAK,EAAG,QAAO,IAAI,MAAM,IAAI,eAAe,EAAE,KAAK,GAAG,CAAC;AACzE,QAAM,UAAU,OAAO,QAAQ,KAAgC,EAC5D,OAAO,CAAC,CAAC,EAAE,UAAU,MAAM,eAAe,MAAS,EACnD,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,MAAO,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,CAAE;AAClD,SAAO,IAAI,QAAQ,IAAI,CAAC,CAAC,KAAK,UAAU,MAAM,GAAG,KAAK,UAAU,GAAG,CAAC,IAAI,gBAAgB,UAAU,CAAC,EAAE,EAAE,KAAK,GAAG,CAAC;AAClH;AAQO,SAAS,2BAA2B,aAA8B;AACvE,SAAO,WAAW,QAAQ,EAAE,OAAO,gBAAgB,WAAW,CAAC,EAAE,OAAO,KAAK;AAC/E;AAEA,eAAsB,6BACpB,IACA,gBACA,aACA,OAC+C;AAC/C,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,MACE;AAAA,MACA;AAAA,MACA,gBAAgB,MAAM;AAAA,MACtB,UAAU,MAAM;AAAA,IAClB;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AAQA,eAAsB,yBACpB,IACA,gBACA,aACA,aACA,OAC+C;AAC/C,QAAM,SAAS,GAAG,OAAO,+BAA+B;AAAA,IACtD;AAAA,IACA;AAAA,IACA;AAAA,IACA,YAAY;AAAA,IACZ,gBAAgB,MAAM;AAAA,IACtB,UAAU,MAAM;AAAA,EAClB,CAAC;AACD,MAAI;AACF,UAAM,GAAG,QAAQ,MAAM,EAAE,MAAM;AAC/B,WAAO;AAAA,EACT,SAAS,OAAgB;AACvB,QAAI,iBAAiB,mCAAoC,QAAO;AAChE,UAAM;AAAA,EACR;AACF;AAEA,eAAsB,2BACpB,IACA,OACA,YACe;AACf,QAAM,aAAa;AACnB,QAAM,GAAG,MAAM;AACjB;AAEA,eAAsB,2BACpB,IACA,gBACA,aACA,OACe;AACf,QAAM,WAAW,MAAM,6BAA6B,IAAI,gBAAgB,aAAa,KAAK;AAC1F,MAAI,CAAC,SAAU;AACf,QAAM,GAAG,OAAO,QAAQ,EAAE,MAAM;AAClC;",
6
+ "names": []
7
+ }
@@ -60,7 +60,8 @@ const createShipment = async (params) => {
60
60
  );
61
61
  return match(call).with({ ok: true }, () => ({ ok: true })).otherwise(({ result }) => ({
62
62
  ok: false,
63
- error: result?.error ?? "Failed to create shipment."
63
+ error: result?.error ?? "Failed to create shipment.",
64
+ code: result?.code
64
65
  }));
65
66
  };
66
67
  const fetchDropOffPoints = async (params) => {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/shipping_carriers/lib/shipment-wizard/hooks/shipmentApi.ts"],
4
- "sourcesContent": ["import { match, P } from 'ts-pattern'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport type { Provider, Address, PackageDimension, ShippingRate, DocumentAddress, DropOffPoint } from '../types'\n\nexport type FetchProvidersResult =\n | { ok: true; providers: Provider[] }\n | { ok: false; error: string }\n\nexport type FetchOrderAddressesResult =\n | { ok: true; items: DocumentAddress[] }\n | { ok: false }\n\nexport type FetchRatesParams = {\n providerKey: string\n origin: Address\n destination: Address\n packages: PackageDimension[]\n receiverPhone?: string\n receiverEmail?: string\n}\n\nexport type FetchRatesResult =\n | { ok: true; rates: ShippingRate[] }\n | { ok: false; error: string }\n\nexport type CreateShipmentParams = {\n providerKey: string\n orderId: string\n origin: Address\n destination: Address\n packages: PackageDimension[]\n serviceCode: string\n labelFormat: string\n senderPhone?: string\n senderEmail?: string\n receiverPhone?: string\n receiverEmail?: string\n targetPoint?: string\n c2cSendingMethod?: string\n}\n\nexport type CreateShipmentResult =\n | { ok: true }\n | { ok: false; error: string }\n\nexport type FetchDropOffPointsParams = {\n providerKey: string\n query?: string\n type?: string\n postCode?: string\n}\n\nexport type FetchDropOffPointsResult =\n | { ok: true; points: DropOffPoint[] }\n | { ok: false; error: string }\n\nexport const fetchProviders = async (): Promise<FetchProvidersResult> => {\n const call = await apiCall<{ providers: Provider[] }>(\n '/api/shipping-carriers/providers',\n undefined,\n { fallback: { providers: [] } },\n )\n return match(call)\n .with({ ok: true, result: P.not(P.nullish) }, ({ result }) => ({\n ok: true as const,\n providers: result.providers,\n }))\n .otherwise(() => ({ ok: false as const, error: 'Failed to load shipping providers.' }))\n}\n\nexport const fetchOrderAddresses = async (orderId: string): Promise<FetchOrderAddressesResult> => {\n const call = await apiCall<{ items: DocumentAddress[] }>(\n `/api/sales/document-addresses?documentId=${orderId}&documentKind=order&pageSize=50`,\n undefined,\n { fallback: { items: [] } },\n )\n return match(call)\n .with({ ok: true, result: P.not(P.nullish) }, ({ result }) => ({\n ok: true as const,\n items: result.items,\n }))\n .otherwise(() => ({ ok: false as const }))\n}\n\nexport const fetchRates = async (params: FetchRatesParams): Promise<FetchRatesResult> => {\n const { providerKey, origin, destination, packages, receiverPhone, receiverEmail } = params\n const call = await apiCall<{ rates: ShippingRate[] }>(\n '/api/shipping-carriers/rates',\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n providerKey,\n origin,\n destination,\n packages,\n ...(receiverPhone !== undefined ? { receiverPhone } : {}),\n ...(receiverEmail !== undefined ? { receiverEmail } : {}),\n }),\n },\n { fallback: { rates: [] } },\n )\n return match(call)\n .with({ ok: true, result: P.not(P.nullish) }, ({ result }) => ({\n ok: true as const,\n rates: result.rates,\n }))\n .otherwise(({ result }) => ({\n ok: false as const,\n error: (result as { error?: string } | null)?.error ?? 'Failed to fetch shipping rates.',\n }))\n}\n\nexport const createShipment = async (params: CreateShipmentParams): Promise<CreateShipmentResult> => {\n const call = await apiCall(\n '/api/shipping-carriers/shipments',\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(params),\n },\n { fallback: null },\n )\n return match(call)\n .with({ ok: true }, () => ({ ok: true as const }))\n .otherwise(({ result }) => ({\n ok: false as const,\n error: (result as { error?: string } | null)?.error ?? 'Failed to create shipment.',\n }))\n}\n\nexport const fetchDropOffPoints = async (params: FetchDropOffPointsParams): Promise<FetchDropOffPointsResult> => {\n const url = new URL('/api/shipping-carriers/points', 'http://placeholder')\n url.searchParams.set('providerKey', params.providerKey)\n if (params.query) url.searchParams.set('query', params.query)\n if (params.type) url.searchParams.set('type', params.type)\n if (params.postCode) url.searchParams.set('postCode', params.postCode)\n\n const call = await apiCall<{ points: DropOffPoint[] }>(\n `${url.pathname}${url.search}`,\n undefined,\n { fallback: { points: [] } },\n )\n return match(call)\n .with({ ok: true, result: P.not(P.nullish) }, ({ result }) => ({\n ok: true as const,\n points: result.points,\n }))\n .otherwise(({ result }) => ({\n ok: false as const,\n error: (result as { error?: string } | null)?.error ?? 'Failed to fetch drop-off points.',\n }))\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { match, P } from 'ts-pattern'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport type { Provider, Address, PackageDimension, ShippingRate, DocumentAddress, DropOffPoint } from '../types'\n\nexport type FetchProvidersResult =\n | { ok: true; providers: Provider[] }\n | { ok: false; error: string }\n\nexport type FetchOrderAddressesResult =\n | { ok: true; items: DocumentAddress[] }\n | { ok: false }\n\nexport type FetchRatesParams = {\n providerKey: string\n origin: Address\n destination: Address\n packages: PackageDimension[]\n receiverPhone?: string\n receiverEmail?: string\n}\n\nexport type FetchRatesResult =\n | { ok: true; rates: ShippingRate[] }\n | { ok: false; error: string }\n\nexport type CreateShipmentParams = {\n providerKey: string\n orderId: string\n origin: Address\n destination: Address\n packages: PackageDimension[]\n serviceCode: string\n labelFormat: string\n senderPhone?: string\n senderEmail?: string\n receiverPhone?: string\n receiverEmail?: string\n targetPoint?: string\n c2cSendingMethod?: string\n idempotencyKey?: string\n}\n\nexport type CreateShipmentResult =\n | { ok: true }\n | { ok: false; error: string; code?: string }\n\nexport type FetchDropOffPointsParams = {\n providerKey: string\n query?: string\n type?: string\n postCode?: string\n}\n\nexport type FetchDropOffPointsResult =\n | { ok: true; points: DropOffPoint[] }\n | { ok: false; error: string }\n\nexport const fetchProviders = async (): Promise<FetchProvidersResult> => {\n const call = await apiCall<{ providers: Provider[] }>(\n '/api/shipping-carriers/providers',\n undefined,\n { fallback: { providers: [] } },\n )\n return match(call)\n .with({ ok: true, result: P.not(P.nullish) }, ({ result }) => ({\n ok: true as const,\n providers: result.providers,\n }))\n .otherwise(() => ({ ok: false as const, error: 'Failed to load shipping providers.' }))\n}\n\nexport const fetchOrderAddresses = async (orderId: string): Promise<FetchOrderAddressesResult> => {\n const call = await apiCall<{ items: DocumentAddress[] }>(\n `/api/sales/document-addresses?documentId=${orderId}&documentKind=order&pageSize=50`,\n undefined,\n { fallback: { items: [] } },\n )\n return match(call)\n .with({ ok: true, result: P.not(P.nullish) }, ({ result }) => ({\n ok: true as const,\n items: result.items,\n }))\n .otherwise(() => ({ ok: false as const }))\n}\n\nexport const fetchRates = async (params: FetchRatesParams): Promise<FetchRatesResult> => {\n const { providerKey, origin, destination, packages, receiverPhone, receiverEmail } = params\n const call = await apiCall<{ rates: ShippingRate[] }>(\n '/api/shipping-carriers/rates',\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n providerKey,\n origin,\n destination,\n packages,\n ...(receiverPhone !== undefined ? { receiverPhone } : {}),\n ...(receiverEmail !== undefined ? { receiverEmail } : {}),\n }),\n },\n { fallback: { rates: [] } },\n )\n return match(call)\n .with({ ok: true, result: P.not(P.nullish) }, ({ result }) => ({\n ok: true as const,\n rates: result.rates,\n }))\n .otherwise(({ result }) => ({\n ok: false as const,\n error: (result as { error?: string } | null)?.error ?? 'Failed to fetch shipping rates.',\n }))\n}\n\nexport const createShipment = async (params: CreateShipmentParams): Promise<CreateShipmentResult> => {\n const call = await apiCall(\n '/api/shipping-carriers/shipments',\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(params),\n },\n { fallback: null },\n )\n return match(call)\n .with({ ok: true }, () => ({ ok: true as const }))\n .otherwise(({ result }) => ({\n ok: false as const,\n error: (result as { error?: string } | null)?.error ?? 'Failed to create shipment.',\n code: (result as { code?: string } | null)?.code,\n }))\n}\n\nexport const fetchDropOffPoints = async (params: FetchDropOffPointsParams): Promise<FetchDropOffPointsResult> => {\n const url = new URL('/api/shipping-carriers/points', 'http://placeholder')\n url.searchParams.set('providerKey', params.providerKey)\n if (params.query) url.searchParams.set('query', params.query)\n if (params.type) url.searchParams.set('type', params.type)\n if (params.postCode) url.searchParams.set('postCode', params.postCode)\n\n const call = await apiCall<{ points: DropOffPoint[] }>(\n `${url.pathname}${url.search}`,\n undefined,\n { fallback: { points: [] } },\n )\n return match(call)\n .with({ ok: true, result: P.not(P.nullish) }, ({ result }) => ({\n ok: true as const,\n points: result.points,\n }))\n .otherwise(({ result }) => ({\n ok: false as const,\n error: (result as { error?: string } | null)?.error ?? 'Failed to fetch drop-off points.',\n }))\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -104,6 +104,8 @@ const useShipmentWizard = () => {
104
104
  }
105
105
  setIsFetchingRates(false);
106
106
  };
107
+ const idempotencyKeyRef = React.useRef(null);
108
+ const idempotencySignatureRef = React.useRef(null);
107
109
  const handleSubmit = async () => {
108
110
  if (!selectedProvider || !selectedRate || !orderId) return;
109
111
  setIsSubmitting(true);
@@ -122,20 +124,36 @@ const useShipmentWizard = () => {
122
124
  ...targetPoint ? { targetPoint } : {},
123
125
  ...c2cSendingMethod ? { c2cSendingMethod } : {}
124
126
  };
127
+ const signature = JSON.stringify(payload);
128
+ if (idempotencyKeyRef.current === null || idempotencySignatureRef.current !== signature) {
129
+ idempotencyKeyRef.current = crypto.randomUUID();
130
+ idempotencySignatureRef.current = signature;
131
+ }
132
+ const submitPayload = { ...payload, idempotencyKey: idempotencyKeyRef.current };
125
133
  try {
126
134
  const result = await runMutation({
127
- operation: () => createShipment(payload),
135
+ operation: () => createShipment(submitPayload),
128
136
  context: {
129
137
  operation: "create",
130
138
  resourceKind: "shipping_carriers.shipment",
131
139
  resourceId: orderId,
132
140
  retryLastMutation
133
141
  },
134
- mutationPayload: payload
142
+ mutationPayload: submitPayload
135
143
  });
136
144
  if (result.ok) {
145
+ idempotencyKeyRef.current = null;
146
+ idempotencySignatureRef.current = null;
137
147
  flash(t("shipping_carriers.create.success", "Shipment created successfully."), "success");
138
148
  router.push(backHref);
149
+ } else if (result.code === "idempotency_conflict") {
150
+ flash(
151
+ t(
152
+ "shipping_carriers.create.error.idempotencyConflict",
153
+ "This shipment was already submitted. Refresh the page before retrying."
154
+ ),
155
+ "error"
156
+ );
139
157
  } else {
140
158
  flash(t("shipping_carriers.create.error.create", result.error), "error");
141
159
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/shipping_carriers/lib/shipment-wizard/hooks/useShipmentWizard.ts"],
4
- "sourcesContent": ["import * as React from 'react'\nimport { useRouter, useSearchParams } from 'next/navigation'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport {\n fetchProviders,\n fetchOrderAddresses,\n fetchRates,\n createShipment,\n fetchDropOffPoints,\n} from './shipmentApi'\nimport type { CreateShipmentParams } from './shipmentApi'\nimport type {\n WizardStep,\n Provider,\n Address,\n PackageDimension,\n ShippingRate,\n LabelFormat,\n DocumentAddress,\n ContactInfo,\n DropOffPoint,\n} from '../types'\n\nconst EMPTY_ADDRESS: Address = { countryCode: '', postalCode: '', city: '', line1: '' }\nconst EMPTY_CONTACT: ContactInfo = { phone: '', email: '' }\nconst DEFAULT_PACKAGE: PackageDimension = { weightKg: 1, lengthCm: 20, widthCm: 15, heightCm: 10 }\n\nconst buildAddressFromDocument = (doc: DocumentAddress): Address => ({\n countryCode: doc.country ?? '',\n postalCode: doc.postal_code ?? '',\n city: doc.city ?? '',\n line1: doc.address_line1,\n line2: doc.address_line2 ?? undefined,\n})\n\nconst isAddressValid = (addr: Address) =>\n addr.countryCode.length >= 2 &&\n addr.postalCode.length > 0 &&\n addr.city.length > 0 &&\n addr.line1.length > 0\n\nconst isPackageValid = (pkg: PackageDimension) =>\n pkg.weightKg > 0 && pkg.lengthCm > 0 && pkg.widthCm > 0 && pkg.heightCm > 0\n\nconst EMAIL_REGEX = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/\nconst PHONE_REGEX = /^[+\\d][\\d\\s\\-().]{6,}$/\n\nconst isEmailValid = (email: string) => email === '' || EMAIL_REGEX.test(email)\nconst isPhoneValid = (phone: string) => phone === '' || PHONE_REGEX.test(phone)\n\nexport type ShipmentWizard = {\n step: WizardStep\n backHref: string\n\n // Provider step\n providers: Provider[]\n isLoadingProviders: boolean\n providerError: string | null\n selectedProvider: string | null\n\n // Configure step\n origin: Address\n destination: Address\n packages: PackageDimension[]\n labelFormat: LabelFormat\n senderContact: ContactInfo\n receiverContact: ContactInfo\n targetPoint: string\n c2cSendingMethod: string\n isFetchingRates: boolean\n canProceedFromConfigure: boolean\n senderContactErrors: { email: string | null; phone: string | null }\n receiverContactErrors: { email: string | null; phone: string | null }\n\n // Drop-off point search\n dropOffPointQuery: string\n dropOffPoints: DropOffPoint[]\n isFetchingDropOffPoints: boolean\n dropOffPointsError: string | null\n\n // Confirm step\n rates: ShippingRate[]\n ratesError: string | null\n selectedRate: ShippingRate | null\n isSubmitting: boolean\n\n // Actions\n goToStep: (step: WizardStep) => void\n handleProviderSelect: (providerKey: string) => void\n handleConfigureNext: () => void\n handleSubmit: () => void\n setOrigin: (address: Address) => void\n setDestination: (address: Address) => void\n setPackages: (packages: PackageDimension[]) => void\n setLabelFormat: (format: LabelFormat) => void\n setSenderContact: (contact: ContactInfo) => void\n setReceiverContact: (contact: ContactInfo) => void\n setTargetPoint: (point: string) => void\n setC2cSendingMethod: (method: string) => void\n setSelectedRate: (rate: ShippingRate) => void\n searchDropOffPoints: (query: string) => void\n}\n\nexport const useShipmentWizard = (): ShipmentWizard => {\n const t = useT()\n const router = useRouter()\n const searchParams = useSearchParams()\n const { runMutation, retryLastMutation } = useGuardedMutation({\n contextId: 'shipping_carriers.shipment.create',\n })\n const orderId = searchParams?.get('orderId') ?? null\n\n const [step, setStep] = React.useState<WizardStep>('provider')\n const [providers, setProviders] = React.useState<Provider[]>([])\n const [isLoadingProviders, setIsLoadingProviders] = React.useState(true)\n const [providerError, setProviderError] = React.useState<string | null>(null)\n const [selectedProvider, setSelectedProvider] = React.useState<string | null>(null)\n\n const [origin, setOrigin] = React.useState<Address>(EMPTY_ADDRESS)\n const [destination, setDestination] = React.useState<Address>(EMPTY_ADDRESS)\n const [packages, setPackages] = React.useState<PackageDimension[]>([DEFAULT_PACKAGE])\n const [labelFormat, setLabelFormat] = React.useState<LabelFormat>('pdf')\n const [senderContact, setSenderContact] = React.useState<ContactInfo>(EMPTY_CONTACT)\n const [receiverContact, setReceiverContact] = React.useState<ContactInfo>(EMPTY_CONTACT)\n const [targetPoint, setTargetPoint] = React.useState<string>('')\n const [c2cSendingMethod, setC2cSendingMethod] = React.useState<string>('')\n\n const [dropOffPointQuery, setDropOffPointQuery] = React.useState<string>('')\n const [dropOffPoints, setDropOffPoints] = React.useState<DropOffPoint[]>([])\n const [isFetchingDropOffPoints, setIsFetchingDropOffPoints] = React.useState(false)\n const [dropOffPointsError, setDropOffPointsError] = React.useState<string | null>(null)\n\n const [rates, setRates] = React.useState<ShippingRate[]>([])\n const [isFetchingRates, setIsFetchingRates] = React.useState(false)\n const [ratesError, setRatesError] = React.useState<string | null>(null)\n const [selectedRate, setSelectedRate] = React.useState<ShippingRate | null>(null)\n\n const [isSubmitting, setIsSubmitting] = React.useState(false)\n\n const backHref = orderId ? `/backend/sales/orders/${orderId}` : '/backend/sales/orders'\n\n React.useEffect(() => {\n const loadProviders = async () => {\n setIsLoadingProviders(true)\n setProviderError(null)\n const result = await fetchProviders()\n if (result.ok) {\n setProviders(result.providers)\n } else {\n setProviderError(t('shipping_carriers.create.error.loadProviders', result.error))\n }\n setIsLoadingProviders(false)\n }\n void loadProviders()\n }, [t])\n\n React.useEffect(() => {\n if (!orderId) return\n const prefillAddresses = async () => {\n const result = await fetchOrderAddresses(orderId)\n if (!result.ok) return\n const items = result.items\n const shippingAddr =\n items.find((item) => item.purpose === 'shipping') ??\n items.find((item) => item.purpose === 'delivery') ??\n items[0]\n if (shippingAddr) setDestination(buildAddressFromDocument(shippingAddr))\n }\n void prefillAddresses()\n }, [orderId])\n\n const loadRates = async () => {\n if (!selectedProvider) return\n setIsFetchingRates(true)\n setRatesError(null)\n setRates([])\n setSelectedRate(null)\n const result = await fetchRates({\n providerKey: selectedProvider,\n origin,\n destination,\n packages,\n ...(receiverContact.phone ? { receiverPhone: receiverContact.phone } : {}),\n ...(receiverContact.email ? { receiverEmail: receiverContact.email } : {}),\n })\n if (result.ok) {\n setRates(result.rates)\n if (result.rates.length > 0) setSelectedRate(result.rates[0])\n } else {\n setRatesError(t('shipping_carriers.create.error.fetchRates', result.error))\n }\n setIsFetchingRates(false)\n }\n\n const handleSubmit = async () => {\n if (!selectedProvider || !selectedRate || !orderId) return\n setIsSubmitting(true)\n const payload: CreateShipmentParams = {\n providerKey: selectedProvider,\n orderId,\n origin,\n destination,\n packages,\n serviceCode: selectedRate.serviceCode,\n labelFormat,\n ...(senderContact.phone ? { senderPhone: senderContact.phone } : {}),\n ...(senderContact.email ? { senderEmail: senderContact.email } : {}),\n ...(receiverContact.phone ? { receiverPhone: receiverContact.phone } : {}),\n ...(receiverContact.email ? { receiverEmail: receiverContact.email } : {}),\n ...(targetPoint ? { targetPoint } : {}),\n ...(c2cSendingMethod ? { c2cSendingMethod } : {}),\n }\n try {\n const result = await runMutation({\n operation: () => createShipment(payload),\n context: {\n operation: 'create',\n resourceKind: 'shipping_carriers.shipment',\n resourceId: orderId,\n retryLastMutation,\n },\n mutationPayload: payload as unknown as Record<string, unknown>,\n })\n if (result.ok) {\n flash(t('shipping_carriers.create.success', 'Shipment created successfully.'), 'success')\n router.push(backHref)\n } else {\n flash(t('shipping_carriers.create.error.create', result.error), 'error')\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : t('shipping_carriers.create.error.create', 'Failed to create shipment.')\n flash(t('shipping_carriers.create.error.create', message), 'error')\n } finally {\n setIsSubmitting(false)\n }\n }\n\n const goToStep = (next: WizardStep) => setStep(next)\n\n const handleProviderSelect = (providerKey: string) => {\n setSelectedProvider(providerKey)\n goToStep('configure')\n }\n\n const handleConfigureNext = () => {\n void loadRates().then(() => goToStep('confirm'))\n }\n\n const searchDropOffPoints = async (query: string) => {\n if (!selectedProvider) return\n setDropOffPointQuery(query)\n setIsFetchingDropOffPoints(true)\n setDropOffPointsError(null)\n const isPostCode = /^\\d{2}-?\\d{3}$/.test(query.trim())\n const resolvedType = c2cSendingMethod === 'pop' ? 'pop' : 'parcel_locker'\n const result = await fetchDropOffPoints({\n providerKey: selectedProvider,\n ...(isPostCode ? { postCode: query.trim() } : { query: query.trim() }),\n type: resolvedType,\n })\n if (result.ok) {\n setDropOffPoints(result.points)\n } else {\n setDropOffPointsError(t('shipping_carriers.create.error.fetchDropOffPoints', result.error))\n setDropOffPoints([])\n }\n setIsFetchingDropOffPoints(false)\n }\n\n const canProceedFromConfigure =\n isAddressValid(origin) &&\n isAddressValid(destination) &&\n packages.length > 0 &&\n packages.every(isPackageValid) &&\n isEmailValid(senderContact.email) &&\n isPhoneValid(senderContact.phone) &&\n isEmailValid(receiverContact.email) &&\n isPhoneValid(receiverContact.phone)\n\n const senderContactErrors = {\n email: !isEmailValid(senderContact.email) ? t('shipping_carriers.create.error.invalidEmail', 'Invalid email address.') : null,\n phone: !isPhoneValid(senderContact.phone) ? t('shipping_carriers.create.error.invalidPhone', 'Invalid phone number.') : null,\n }\n const receiverContactErrors = {\n email: !isEmailValid(receiverContact.email) ? t('shipping_carriers.create.error.invalidEmail', 'Invalid email address.') : null,\n phone: !isPhoneValid(receiverContact.phone) ? t('shipping_carriers.create.error.invalidPhone', 'Invalid phone number.') : null,\n }\n\n return {\n step,\n backHref,\n providers,\n isLoadingProviders,\n providerError,\n selectedProvider,\n origin,\n destination,\n packages,\n labelFormat,\n senderContact,\n receiverContact,\n targetPoint,\n c2cSendingMethod,\n isFetchingRates,\n canProceedFromConfigure,\n senderContactErrors,\n receiverContactErrors,\n dropOffPointQuery,\n dropOffPoints,\n isFetchingDropOffPoints,\n dropOffPointsError,\n rates,\n ratesError,\n selectedRate,\n isSubmitting,\n goToStep,\n handleProviderSelect,\n handleConfigureNext,\n handleSubmit: () => void handleSubmit(),\n setOrigin,\n setDestination,\n setPackages,\n setLabelFormat,\n setSenderContact,\n setReceiverContact,\n setTargetPoint,\n setC2cSendingMethod,\n setSelectedRate,\n searchDropOffPoints: (query: string) => void searchDropOffPoints(query),\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import * as React from 'react'\nimport { useRouter, useSearchParams } from 'next/navigation'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport {\n fetchProviders,\n fetchOrderAddresses,\n fetchRates,\n createShipment,\n fetchDropOffPoints,\n} from './shipmentApi'\nimport type { CreateShipmentParams } from './shipmentApi'\nimport type {\n WizardStep,\n Provider,\n Address,\n PackageDimension,\n ShippingRate,\n LabelFormat,\n DocumentAddress,\n ContactInfo,\n DropOffPoint,\n} from '../types'\n\nconst EMPTY_ADDRESS: Address = { countryCode: '', postalCode: '', city: '', line1: '' }\nconst EMPTY_CONTACT: ContactInfo = { phone: '', email: '' }\nconst DEFAULT_PACKAGE: PackageDimension = { weightKg: 1, lengthCm: 20, widthCm: 15, heightCm: 10 }\n\nconst buildAddressFromDocument = (doc: DocumentAddress): Address => ({\n countryCode: doc.country ?? '',\n postalCode: doc.postal_code ?? '',\n city: doc.city ?? '',\n line1: doc.address_line1,\n line2: doc.address_line2 ?? undefined,\n})\n\nconst isAddressValid = (addr: Address) =>\n addr.countryCode.length >= 2 &&\n addr.postalCode.length > 0 &&\n addr.city.length > 0 &&\n addr.line1.length > 0\n\nconst isPackageValid = (pkg: PackageDimension) =>\n pkg.weightKg > 0 && pkg.lengthCm > 0 && pkg.widthCm > 0 && pkg.heightCm > 0\n\nconst EMAIL_REGEX = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/\nconst PHONE_REGEX = /^[+\\d][\\d\\s\\-().]{6,}$/\n\nconst isEmailValid = (email: string) => email === '' || EMAIL_REGEX.test(email)\nconst isPhoneValid = (phone: string) => phone === '' || PHONE_REGEX.test(phone)\n\nexport type ShipmentWizard = {\n step: WizardStep\n backHref: string\n\n // Provider step\n providers: Provider[]\n isLoadingProviders: boolean\n providerError: string | null\n selectedProvider: string | null\n\n // Configure step\n origin: Address\n destination: Address\n packages: PackageDimension[]\n labelFormat: LabelFormat\n senderContact: ContactInfo\n receiverContact: ContactInfo\n targetPoint: string\n c2cSendingMethod: string\n isFetchingRates: boolean\n canProceedFromConfigure: boolean\n senderContactErrors: { email: string | null; phone: string | null }\n receiverContactErrors: { email: string | null; phone: string | null }\n\n // Drop-off point search\n dropOffPointQuery: string\n dropOffPoints: DropOffPoint[]\n isFetchingDropOffPoints: boolean\n dropOffPointsError: string | null\n\n // Confirm step\n rates: ShippingRate[]\n ratesError: string | null\n selectedRate: ShippingRate | null\n isSubmitting: boolean\n\n // Actions\n goToStep: (step: WizardStep) => void\n handleProviderSelect: (providerKey: string) => void\n handleConfigureNext: () => void\n handleSubmit: () => void\n setOrigin: (address: Address) => void\n setDestination: (address: Address) => void\n setPackages: (packages: PackageDimension[]) => void\n setLabelFormat: (format: LabelFormat) => void\n setSenderContact: (contact: ContactInfo) => void\n setReceiverContact: (contact: ContactInfo) => void\n setTargetPoint: (point: string) => void\n setC2cSendingMethod: (method: string) => void\n setSelectedRate: (rate: ShippingRate) => void\n searchDropOffPoints: (query: string) => void\n}\n\nexport const useShipmentWizard = (): ShipmentWizard => {\n const t = useT()\n const router = useRouter()\n const searchParams = useSearchParams()\n const { runMutation, retryLastMutation } = useGuardedMutation({\n contextId: 'shipping_carriers.shipment.create',\n })\n const orderId = searchParams?.get('orderId') ?? null\n\n const [step, setStep] = React.useState<WizardStep>('provider')\n const [providers, setProviders] = React.useState<Provider[]>([])\n const [isLoadingProviders, setIsLoadingProviders] = React.useState(true)\n const [providerError, setProviderError] = React.useState<string | null>(null)\n const [selectedProvider, setSelectedProvider] = React.useState<string | null>(null)\n\n const [origin, setOrigin] = React.useState<Address>(EMPTY_ADDRESS)\n const [destination, setDestination] = React.useState<Address>(EMPTY_ADDRESS)\n const [packages, setPackages] = React.useState<PackageDimension[]>([DEFAULT_PACKAGE])\n const [labelFormat, setLabelFormat] = React.useState<LabelFormat>('pdf')\n const [senderContact, setSenderContact] = React.useState<ContactInfo>(EMPTY_CONTACT)\n const [receiverContact, setReceiverContact] = React.useState<ContactInfo>(EMPTY_CONTACT)\n const [targetPoint, setTargetPoint] = React.useState<string>('')\n const [c2cSendingMethod, setC2cSendingMethod] = React.useState<string>('')\n\n const [dropOffPointQuery, setDropOffPointQuery] = React.useState<string>('')\n const [dropOffPoints, setDropOffPoints] = React.useState<DropOffPoint[]>([])\n const [isFetchingDropOffPoints, setIsFetchingDropOffPoints] = React.useState(false)\n const [dropOffPointsError, setDropOffPointsError] = React.useState<string | null>(null)\n\n const [rates, setRates] = React.useState<ShippingRate[]>([])\n const [isFetchingRates, setIsFetchingRates] = React.useState(false)\n const [ratesError, setRatesError] = React.useState<string | null>(null)\n const [selectedRate, setSelectedRate] = React.useState<ShippingRate | null>(null)\n\n const [isSubmitting, setIsSubmitting] = React.useState(false)\n\n const backHref = orderId ? `/backend/sales/orders/${orderId}` : '/backend/sales/orders'\n\n React.useEffect(() => {\n const loadProviders = async () => {\n setIsLoadingProviders(true)\n setProviderError(null)\n const result = await fetchProviders()\n if (result.ok) {\n setProviders(result.providers)\n } else {\n setProviderError(t('shipping_carriers.create.error.loadProviders', result.error))\n }\n setIsLoadingProviders(false)\n }\n void loadProviders()\n }, [t])\n\n React.useEffect(() => {\n if (!orderId) return\n const prefillAddresses = async () => {\n const result = await fetchOrderAddresses(orderId)\n if (!result.ok) return\n const items = result.items\n const shippingAddr =\n items.find((item) => item.purpose === 'shipping') ??\n items.find((item) => item.purpose === 'delivery') ??\n items[0]\n if (shippingAddr) setDestination(buildAddressFromDocument(shippingAddr))\n }\n void prefillAddresses()\n }, [orderId])\n\n const loadRates = async () => {\n if (!selectedProvider) return\n setIsFetchingRates(true)\n setRatesError(null)\n setRates([])\n setSelectedRate(null)\n const result = await fetchRates({\n providerKey: selectedProvider,\n origin,\n destination,\n packages,\n ...(receiverContact.phone ? { receiverPhone: receiverContact.phone } : {}),\n ...(receiverContact.email ? { receiverEmail: receiverContact.email } : {}),\n })\n if (result.ok) {\n setRates(result.rates)\n if (result.rates.length > 0) setSelectedRate(result.rates[0])\n } else {\n setRatesError(t('shipping_carriers.create.error.fetchRates', result.error))\n }\n setIsFetchingRates(false)\n }\n\n const idempotencyKeyRef = React.useRef<string | null>(null)\n const idempotencySignatureRef = React.useRef<string | null>(null)\n\n const handleSubmit = async () => {\n if (!selectedProvider || !selectedRate || !orderId) return\n setIsSubmitting(true)\n const payload: CreateShipmentParams = {\n providerKey: selectedProvider,\n orderId,\n origin,\n destination,\n packages,\n serviceCode: selectedRate.serviceCode,\n labelFormat,\n ...(senderContact.phone ? { senderPhone: senderContact.phone } : {}),\n ...(senderContact.email ? { senderEmail: senderContact.email } : {}),\n ...(receiverContact.phone ? { receiverPhone: receiverContact.phone } : {}),\n ...(receiverContact.email ? { receiverEmail: receiverContact.email } : {}),\n ...(targetPoint ? { targetPoint } : {}),\n ...(c2cSendingMethod ? { c2cSendingMethod } : {}),\n }\n // Reuse one idempotency key per confirmed payload so a double-submit or a\n // network-timeout retry de-duplicates server-side; regenerate it when the\n // payload changes so an edited shipment is not rejected as a conflict.\n const signature = JSON.stringify(payload)\n if (idempotencyKeyRef.current === null || idempotencySignatureRef.current !== signature) {\n idempotencyKeyRef.current = crypto.randomUUID()\n idempotencySignatureRef.current = signature\n }\n const submitPayload: CreateShipmentParams = { ...payload, idempotencyKey: idempotencyKeyRef.current }\n try {\n const result = await runMutation({\n operation: () => createShipment(submitPayload),\n context: {\n operation: 'create',\n resourceKind: 'shipping_carriers.shipment',\n resourceId: orderId,\n retryLastMutation,\n },\n mutationPayload: submitPayload as unknown as Record<string, unknown>,\n })\n if (result.ok) {\n idempotencyKeyRef.current = null\n idempotencySignatureRef.current = null\n flash(t('shipping_carriers.create.success', 'Shipment created successfully.'), 'success')\n router.push(backHref)\n } else if (result.code === 'idempotency_conflict') {\n flash(\n t(\n 'shipping_carriers.create.error.idempotencyConflict',\n 'This shipment was already submitted. Refresh the page before retrying.',\n ),\n 'error',\n )\n } else {\n flash(t('shipping_carriers.create.error.create', result.error), 'error')\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : t('shipping_carriers.create.error.create', 'Failed to create shipment.')\n flash(t('shipping_carriers.create.error.create', message), 'error')\n } finally {\n setIsSubmitting(false)\n }\n }\n\n const goToStep = (next: WizardStep) => setStep(next)\n\n const handleProviderSelect = (providerKey: string) => {\n setSelectedProvider(providerKey)\n goToStep('configure')\n }\n\n const handleConfigureNext = () => {\n void loadRates().then(() => goToStep('confirm'))\n }\n\n const searchDropOffPoints = async (query: string) => {\n if (!selectedProvider) return\n setDropOffPointQuery(query)\n setIsFetchingDropOffPoints(true)\n setDropOffPointsError(null)\n const isPostCode = /^\\d{2}-?\\d{3}$/.test(query.trim())\n const resolvedType = c2cSendingMethod === 'pop' ? 'pop' : 'parcel_locker'\n const result = await fetchDropOffPoints({\n providerKey: selectedProvider,\n ...(isPostCode ? { postCode: query.trim() } : { query: query.trim() }),\n type: resolvedType,\n })\n if (result.ok) {\n setDropOffPoints(result.points)\n } else {\n setDropOffPointsError(t('shipping_carriers.create.error.fetchDropOffPoints', result.error))\n setDropOffPoints([])\n }\n setIsFetchingDropOffPoints(false)\n }\n\n const canProceedFromConfigure =\n isAddressValid(origin) &&\n isAddressValid(destination) &&\n packages.length > 0 &&\n packages.every(isPackageValid) &&\n isEmailValid(senderContact.email) &&\n isPhoneValid(senderContact.phone) &&\n isEmailValid(receiverContact.email) &&\n isPhoneValid(receiverContact.phone)\n\n const senderContactErrors = {\n email: !isEmailValid(senderContact.email) ? t('shipping_carriers.create.error.invalidEmail', 'Invalid email address.') : null,\n phone: !isPhoneValid(senderContact.phone) ? t('shipping_carriers.create.error.invalidPhone', 'Invalid phone number.') : null,\n }\n const receiverContactErrors = {\n email: !isEmailValid(receiverContact.email) ? t('shipping_carriers.create.error.invalidEmail', 'Invalid email address.') : null,\n phone: !isPhoneValid(receiverContact.phone) ? t('shipping_carriers.create.error.invalidPhone', 'Invalid phone number.') : null,\n }\n\n return {\n step,\n backHref,\n providers,\n isLoadingProviders,\n providerError,\n selectedProvider,\n origin,\n destination,\n packages,\n labelFormat,\n senderContact,\n receiverContact,\n targetPoint,\n c2cSendingMethod,\n isFetchingRates,\n canProceedFromConfigure,\n senderContactErrors,\n receiverContactErrors,\n dropOffPointQuery,\n dropOffPoints,\n isFetchingDropOffPoints,\n dropOffPointsError,\n rates,\n ratesError,\n selectedRate,\n isSubmitting,\n goToStep,\n handleProviderSelect,\n handleConfigureNext,\n handleSubmit: () => void handleSubmit(),\n setOrigin,\n setDestination,\n setPackages,\n setLabelFormat,\n setSenderContact,\n setReceiverContact,\n setTargetPoint,\n setC2cSendingMethod,\n setSelectedRate,\n searchDropOffPoints: (query: string) => void searchDropOffPoints(query),\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }