@open-mercato/core 0.6.6-develop.6257.1.6d0af84d26 → 0.6.6-develop.6286.1.d985c5c374

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 (208) 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/entities/api/records.js +6 -0
  43. package/dist/modules/entities/api/records.js.map +2 -2
  44. package/dist/modules/feature_toggles/api/global/route.js +17 -15
  45. package/dist/modules/feature_toggles/api/global/route.js.map +2 -2
  46. package/dist/modules/feature_toggles/api/openapi.js +2 -2
  47. package/dist/modules/feature_toggles/api/openapi.js.map +2 -2
  48. package/dist/modules/feature_toggles/commands/global.js +14 -0
  49. package/dist/modules/feature_toggles/commands/global.js.map +2 -2
  50. package/dist/modules/inbox_ops/backend/inbox-ops/page.js +6 -0
  51. package/dist/modules/inbox_ops/backend/inbox-ops/page.js.map +2 -2
  52. package/dist/modules/messages/components/message-detail/hooks/useMessageDetails.js +3 -1
  53. package/dist/modules/messages/components/message-detail/hooks/useMessageDetails.js.map +2 -2
  54. package/dist/modules/messages/components/message-detail/hooks/useMessageDetailsActions.js +12 -5
  55. package/dist/modules/messages/components/message-detail/hooks/useMessageDetailsActions.js.map +2 -2
  56. package/dist/modules/messages/components/message-detail/hooks/useMessageDetailsQueries.js +10 -1
  57. package/dist/modules/messages/components/message-detail/hooks/useMessageDetailsQueries.js.map +2 -2
  58. package/dist/modules/payment_gateways/api/cancel/route.js +5 -0
  59. package/dist/modules/payment_gateways/api/cancel/route.js.map +2 -2
  60. package/dist/modules/payment_gateways/api/capture/route.js +5 -0
  61. package/dist/modules/payment_gateways/api/capture/route.js.map +2 -2
  62. package/dist/modules/payment_gateways/api/refund/route.js +5 -0
  63. package/dist/modules/payment_gateways/api/refund/route.js.map +2 -2
  64. package/dist/modules/payment_gateways/lib/gateway-service.js +52 -25
  65. package/dist/modules/payment_gateways/lib/gateway-service.js.map +2 -2
  66. package/dist/modules/payment_gateways/lib/status-machine.js +13 -0
  67. package/dist/modules/payment_gateways/lib/status-machine.js.map +2 -2
  68. package/dist/modules/perspectives/api/[tableId]/route.js +8 -2
  69. package/dist/modules/perspectives/api/[tableId]/route.js.map +2 -2
  70. package/dist/modules/perspectives/data/entities.js +33 -3
  71. package/dist/modules/perspectives/data/entities.js.map +2 -2
  72. package/dist/modules/perspectives/migrations/Migration20260619120000_perspectives_live_row_uniqueness.js +63 -0
  73. package/dist/modules/perspectives/migrations/Migration20260619120000_perspectives_live_row_uniqueness.js.map +7 -0
  74. package/dist/modules/perspectives/services/perspectiveService.js +17 -0
  75. package/dist/modules/perspectives/services/perspectiveService.js.map +2 -2
  76. package/dist/modules/planner/commands/availability-weekly.js +33 -1
  77. package/dist/modules/planner/commands/availability-weekly.js.map +2 -2
  78. package/dist/modules/planner/components/AvailabilityRulesEditor.js +14 -2
  79. package/dist/modules/planner/components/AvailabilityRulesEditor.js.map +2 -2
  80. package/dist/modules/progress/api/active/route.js +1 -1
  81. package/dist/modules/progress/api/active/route.js.map +2 -2
  82. package/dist/modules/progress/lib/progressService.js.map +1 -1
  83. package/dist/modules/progress/lib/progressServiceImpl.js +33 -8
  84. package/dist/modules/progress/lib/progressServiceImpl.js.map +2 -2
  85. package/dist/modules/query_index/api/status.js +19 -24
  86. package/dist/modules/query_index/api/status.js.map +3 -3
  87. package/dist/modules/query_index/lib/coverage.js +35 -0
  88. package/dist/modules/query_index/lib/coverage.js.map +2 -2
  89. package/dist/modules/query_index/lib/engine.js +11 -6
  90. package/dist/modules/query_index/lib/engine.js.map +2 -2
  91. package/dist/modules/resources/api/resource-types.js +5 -37
  92. package/dist/modules/resources/api/resource-types.js.map +2 -2
  93. package/dist/modules/resources/api/resourceTypeCounts.js +35 -0
  94. package/dist/modules/resources/api/resourceTypeCounts.js.map +7 -0
  95. package/dist/modules/resources/backend/resources/resource-types/[id]/edit/page.js +1 -1
  96. package/dist/modules/resources/backend/resources/resource-types/[id]/edit/page.js.map +1 -1
  97. package/dist/modules/resources/backend/resources/resource-types/page.js +2 -1
  98. package/dist/modules/resources/backend/resources/resource-types/page.js.map +2 -2
  99. package/dist/modules/sales/components/documents/ReturnDialog.js +4 -1
  100. package/dist/modules/sales/components/documents/ReturnDialog.js.map +2 -2
  101. package/dist/modules/sales/components/documents/ShipmentDialog.js +17 -2
  102. package/dist/modules/sales/components/documents/ShipmentDialog.js.map +2 -2
  103. package/dist/modules/sales/di.js +4 -0
  104. package/dist/modules/sales/di.js.map +2 -2
  105. package/dist/modules/sales/services/salesOrderService.js +26 -0
  106. package/dist/modules/sales/services/salesOrderService.js.map +7 -0
  107. package/dist/modules/shipping_carriers/api/shipments/route.js +8 -0
  108. package/dist/modules/shipping_carriers/api/shipments/route.js.map +2 -2
  109. package/dist/modules/shipping_carriers/data/entities.js +38 -0
  110. package/dist/modules/shipping_carriers/data/entities.js.map +2 -2
  111. package/dist/modules/shipping_carriers/data/validators.js +2 -1
  112. package/dist/modules/shipping_carriers/data/validators.js.map +2 -2
  113. package/dist/modules/shipping_carriers/lib/shipment-idempotency.js +76 -0
  114. package/dist/modules/shipping_carriers/lib/shipment-idempotency.js.map +7 -0
  115. package/dist/modules/shipping_carriers/lib/shipment-wizard/hooks/shipmentApi.js +2 -1
  116. package/dist/modules/shipping_carriers/lib/shipment-wizard/hooks/shipmentApi.js.map +2 -2
  117. package/dist/modules/shipping_carriers/lib/shipment-wizard/hooks/useShipmentWizard.js +20 -2
  118. package/dist/modules/shipping_carriers/lib/shipment-wizard/hooks/useShipmentWizard.js.map +2 -2
  119. package/dist/modules/shipping_carriers/lib/shipping-service.js +82 -43
  120. package/dist/modules/shipping_carriers/lib/shipping-service.js.map +2 -2
  121. package/dist/modules/shipping_carriers/migrations/Migration20260618173616_shipping_carriers.js +14 -0
  122. package/dist/modules/shipping_carriers/migrations/Migration20260618173616_shipping_carriers.js.map +7 -0
  123. package/dist/modules/staff/lib/timesheets-ui/ListView.js +15 -6
  124. package/dist/modules/staff/lib/timesheets-ui/ListView.js.map +2 -2
  125. package/dist/modules/translations/components/TranslationManager.js +9 -18
  126. package/dist/modules/translations/components/TranslationManager.js.map +2 -2
  127. package/generated/entities/carrier_shipment_idempotency_key/index.ts +8 -0
  128. package/generated/entities.ids.generated.ts +2 -1
  129. package/generated/entity-fields-registry.ts +10 -0
  130. package/package.json +9 -8
  131. package/src/modules/catalog/lib/productResolution.ts +75 -0
  132. package/src/modules/communication_channels/workers/channel-import-history.ts +1 -1
  133. package/src/modules/currencies/api/fetch-configs/route.ts +36 -0
  134. package/src/modules/currencies/components/CurrencyFetchingConfig.tsx +20 -17
  135. package/src/modules/currencies/i18n/de.json +2 -1
  136. package/src/modules/currencies/i18n/en.json +2 -1
  137. package/src/modules/currencies/i18n/es.json +2 -1
  138. package/src/modules/currencies/i18n/pl.json +2 -1
  139. package/src/modules/customers/api/companies/[id]/route.ts +214 -189
  140. package/src/modules/customers/api/people/[id]/route.ts +106 -80
  141. package/src/modules/customers/backend/config/customers/pipeline-stages/page.tsx +19 -12
  142. package/src/modules/customers/components/detail/ActivitiesAddNewMenu.tsx +8 -6
  143. package/src/modules/customers/components/detail/AiActionChips.tsx +6 -3
  144. package/src/modules/customers/components/detail/schedule/ParticipantsField.tsx +16 -18
  145. package/src/modules/data_sync/api/runs/[id]/cancel.ts +1 -1
  146. package/src/modules/data_sync/backend/data-sync/page.tsx +11 -58
  147. package/src/modules/data_sync/backend/data-sync/runs/[id]/page.tsx +13 -20
  148. package/src/modules/data_sync/components/IntegrationScheduleTab.tsx +10 -8
  149. package/src/modules/data_sync/lib/id-mapping.ts +58 -1
  150. package/src/modules/data_sync/lib/sync-engine.ts +2 -2
  151. package/src/modules/data_sync/lib/syncRunStatus.ts +39 -0
  152. package/src/modules/entities/api/records.ts +14 -0
  153. package/src/modules/feature_toggles/api/global/route.ts +16 -14
  154. package/src/modules/feature_toggles/api/openapi.ts +2 -2
  155. package/src/modules/feature_toggles/commands/global.ts +15 -0
  156. package/src/modules/inbox_ops/backend/inbox-ops/page.tsx +7 -0
  157. package/src/modules/messages/components/message-detail/hooks/useMessageDetails.ts +2 -0
  158. package/src/modules/messages/components/message-detail/hooks/useMessageDetailsActions.ts +14 -4
  159. package/src/modules/messages/components/message-detail/hooks/useMessageDetailsQueries.ts +15 -1
  160. package/src/modules/payment_gateways/api/cancel/route.ts +5 -0
  161. package/src/modules/payment_gateways/api/capture/route.ts +5 -0
  162. package/src/modules/payment_gateways/api/refund/route.ts +5 -0
  163. package/src/modules/payment_gateways/lib/gateway-service.ts +58 -25
  164. package/src/modules/payment_gateways/lib/status-machine.ts +16 -0
  165. package/src/modules/perspectives/api/[tableId]/route.ts +8 -2
  166. package/src/modules/perspectives/data/entities.ts +43 -3
  167. package/src/modules/perspectives/migrations/.snapshot-open-mercato.json +69 -25
  168. package/src/modules/perspectives/migrations/Migration20260619120000_perspectives_live_row_uniqueness.ts +65 -0
  169. package/src/modules/perspectives/services/perspectiveService.ts +17 -0
  170. package/src/modules/planner/commands/availability-weekly.ts +48 -1
  171. package/src/modules/planner/components/AvailabilityRulesEditor.tsx +31 -10
  172. package/src/modules/progress/api/active/route.ts +1 -1
  173. package/src/modules/progress/lib/progressService.ts +2 -2
  174. package/src/modules/progress/lib/progressServiceImpl.ts +33 -8
  175. package/src/modules/query_index/api/status.ts +28 -32
  176. package/src/modules/query_index/lib/coverage.ts +63 -0
  177. package/src/modules/query_index/lib/engine.ts +37 -24
  178. package/src/modules/resources/api/resource-types.ts +4 -44
  179. package/src/modules/resources/api/resourceTypeCounts.ts +74 -0
  180. package/src/modules/resources/backend/resources/resource-types/[id]/edit/page.tsx +1 -1
  181. package/src/modules/resources/backend/resources/resource-types/page.tsx +1 -0
  182. package/src/modules/sales/components/documents/ReturnDialog.tsx +4 -1
  183. package/src/modules/sales/components/documents/ShipmentDialog.tsx +20 -2
  184. package/src/modules/sales/di.ts +6 -0
  185. package/src/modules/sales/services/salesOrderService.ts +38 -0
  186. package/src/modules/shipping_carriers/api/shipments/route.ts +8 -0
  187. package/src/modules/shipping_carriers/data/entities.ts +33 -0
  188. package/src/modules/shipping_carriers/data/validators.ts +1 -0
  189. package/src/modules/shipping_carriers/i18n/de.json +1 -0
  190. package/src/modules/shipping_carriers/i18n/en.json +1 -0
  191. package/src/modules/shipping_carriers/i18n/es.json +1 -0
  192. package/src/modules/shipping_carriers/i18n/pl.json +1 -0
  193. package/src/modules/shipping_carriers/lib/shipment-idempotency.ts +119 -0
  194. package/src/modules/shipping_carriers/lib/shipment-wizard/hooks/shipmentApi.ts +3 -1
  195. package/src/modules/shipping_carriers/lib/shipment-wizard/hooks/useShipmentWizard.ts +24 -2
  196. package/src/modules/shipping_carriers/lib/shipping-service.ts +87 -43
  197. package/src/modules/shipping_carriers/migrations/.snapshot-open-mercato.json +197 -0
  198. package/src/modules/shipping_carriers/migrations/Migration20260618173616_shipping_carriers.ts +14 -0
  199. package/src/modules/staff/i18n/de.json +1 -0
  200. package/src/modules/staff/i18n/en.json +1 -0
  201. package/src/modules/staff/i18n/es.json +1 -0
  202. package/src/modules/staff/i18n/pl.json +1 -0
  203. package/src/modules/staff/lib/timesheets-ui/ListView.tsx +15 -7
  204. package/src/modules/translations/components/TranslationManager.tsx +17 -24
  205. package/src/modules/translations/i18n/de.json +1 -0
  206. package/src/modules/translations/i18n/en.json +1 -0
  207. package/src/modules/translations/i18n/es.json +1 -0
  208. 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": "AAAA,YAAY,WAAW;AACvB,SAAS,WAAW,uBAAuB;AAC3C,SAAS,aAAa;AACtB,SAAS,0BAA0B;AACnC,SAAS,YAAY;AACrB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAcP,MAAM,gBAAyB,EAAE,aAAa,IAAI,YAAY,IAAI,MAAM,IAAI,OAAO,GAAG;AACtF,MAAM,gBAA6B,EAAE,OAAO,IAAI,OAAO,GAAG;AAC1D,MAAM,kBAAoC,EAAE,UAAU,GAAG,UAAU,IAAI,SAAS,IAAI,UAAU,GAAG;AAEjG,MAAM,2BAA2B,CAAC,SAAmC;AAAA,EACnE,aAAa,IAAI,WAAW;AAAA,EAC5B,YAAY,IAAI,eAAe;AAAA,EAC/B,MAAM,IAAI,QAAQ;AAAA,EAClB,OAAO,IAAI;AAAA,EACX,OAAO,IAAI,iBAAiB;AAC9B;AAEA,MAAM,iBAAiB,CAAC,SACtB,KAAK,YAAY,UAAU,KAC3B,KAAK,WAAW,SAAS,KACzB,KAAK,KAAK,SAAS,KACnB,KAAK,MAAM,SAAS;AAEtB,MAAM,iBAAiB,CAAC,QACtB,IAAI,WAAW,KAAK,IAAI,WAAW,KAAK,IAAI,UAAU,KAAK,IAAI,WAAW;AAE5E,MAAM,cAAc;AACpB,MAAM,cAAc;AAEpB,MAAM,eAAe,CAAC,UAAkB,UAAU,MAAM,YAAY,KAAK,KAAK;AAC9E,MAAM,eAAe,CAAC,UAAkB,UAAU,MAAM,YAAY,KAAK,KAAK;AAuDvE,MAAM,oBAAoB,MAAsB;AACrD,QAAM,IAAI,KAAK;AACf,QAAM,SAAS,UAAU;AACzB,QAAM,eAAe,gBAAgB;AACrC,QAAM,EAAE,aAAa,kBAAkB,IAAI,mBAAmB;AAAA,IAC5D,WAAW;AAAA,EACb,CAAC;AACD,QAAM,UAAU,cAAc,IAAI,SAAS,KAAK;AAEhD,QAAM,CAAC,MAAM,OAAO,IAAI,MAAM,SAAqB,UAAU;AAC7D,QAAM,CAAC,WAAW,YAAY,IAAI,MAAM,SAAqB,CAAC,CAAC;AAC/D,QAAM,CAAC,oBAAoB,qBAAqB,IAAI,MAAM,SAAS,IAAI;AACvE,QAAM,CAAC,eAAe,gBAAgB,IAAI,MAAM,SAAwB,IAAI;AAC5E,QAAM,CAAC,kBAAkB,mBAAmB,IAAI,MAAM,SAAwB,IAAI;AAElF,QAAM,CAAC,QAAQ,SAAS,IAAI,MAAM,SAAkB,aAAa;AACjE,QAAM,CAAC,aAAa,cAAc,IAAI,MAAM,SAAkB,aAAa;AAC3E,QAAM,CAAC,UAAU,WAAW,IAAI,MAAM,SAA6B,CAAC,eAAe,CAAC;AACpF,QAAM,CAAC,aAAa,cAAc,IAAI,MAAM,SAAsB,KAAK;AACvE,QAAM,CAAC,eAAe,gBAAgB,IAAI,MAAM,SAAsB,aAAa;AACnF,QAAM,CAAC,iBAAiB,kBAAkB,IAAI,MAAM,SAAsB,aAAa;AACvF,QAAM,CAAC,aAAa,cAAc,IAAI,MAAM,SAAiB,EAAE;AAC/D,QAAM,CAAC,kBAAkB,mBAAmB,IAAI,MAAM,SAAiB,EAAE;AAEzE,QAAM,CAAC,mBAAmB,oBAAoB,IAAI,MAAM,SAAiB,EAAE;AAC3E,QAAM,CAAC,eAAe,gBAAgB,IAAI,MAAM,SAAyB,CAAC,CAAC;AAC3E,QAAM,CAAC,yBAAyB,0BAA0B,IAAI,MAAM,SAAS,KAAK;AAClF,QAAM,CAAC,oBAAoB,qBAAqB,IAAI,MAAM,SAAwB,IAAI;AAEtF,QAAM,CAAC,OAAO,QAAQ,IAAI,MAAM,SAAyB,CAAC,CAAC;AAC3D,QAAM,CAAC,iBAAiB,kBAAkB,IAAI,MAAM,SAAS,KAAK;AAClE,QAAM,CAAC,YAAY,aAAa,IAAI,MAAM,SAAwB,IAAI;AACtE,QAAM,CAAC,cAAc,eAAe,IAAI,MAAM,SAA8B,IAAI;AAEhF,QAAM,CAAC,cAAc,eAAe,IAAI,MAAM,SAAS,KAAK;AAE5D,QAAM,WAAW,UAAU,yBAAyB,OAAO,KAAK;AAEhE,QAAM,UAAU,MAAM;AACpB,UAAM,gBAAgB,YAAY;AAChC,4BAAsB,IAAI;AAC1B,uBAAiB,IAAI;AACrB,YAAM,SAAS,MAAM,eAAe;AACpC,UAAI,OAAO,IAAI;AACb,qBAAa,OAAO,SAAS;AAAA,MAC/B,OAAO;AACL,yBAAiB,EAAE,gDAAgD,OAAO,KAAK,CAAC;AAAA,MAClF;AACA,4BAAsB,KAAK;AAAA,IAC7B;AACA,SAAK,cAAc;AAAA,EACrB,GAAG,CAAC,CAAC,CAAC;AAEN,QAAM,UAAU,MAAM;AACpB,QAAI,CAAC,QAAS;AACd,UAAM,mBAAmB,YAAY;AACnC,YAAM,SAAS,MAAM,oBAAoB,OAAO;AAChD,UAAI,CAAC,OAAO,GAAI;AAChB,YAAM,QAAQ,OAAO;AACrB,YAAM,eACJ,MAAM,KAAK,CAAC,SAAS,KAAK,YAAY,UAAU,KAChD,MAAM,KAAK,CAAC,SAAS,KAAK,YAAY,UAAU,KAChD,MAAM,CAAC;AACT,UAAI,aAAc,gBAAe,yBAAyB,YAAY,CAAC;AAAA,IACzE;AACA,SAAK,iBAAiB;AAAA,EACxB,GAAG,CAAC,OAAO,CAAC;AAEZ,QAAM,YAAY,YAAY;AAC5B,QAAI,CAAC,iBAAkB;AACvB,uBAAmB,IAAI;AACvB,kBAAc,IAAI;AAClB,aAAS,CAAC,CAAC;AACX,oBAAgB,IAAI;AACpB,UAAM,SAAS,MAAM,WAAW;AAAA,MAC9B,aAAa;AAAA,MACb;AAAA,MACA;AAAA,MACA;AAAA,MACA,GAAI,gBAAgB,QAAQ,EAAE,eAAe,gBAAgB,MAAM,IAAI,CAAC;AAAA,MACxE,GAAI,gBAAgB,QAAQ,EAAE,eAAe,gBAAgB,MAAM,IAAI,CAAC;AAAA,IAC1E,CAAC;AACD,QAAI,OAAO,IAAI;AACb,eAAS,OAAO,KAAK;AACrB,UAAI,OAAO,MAAM,SAAS,EAAG,iBAAgB,OAAO,MAAM,CAAC,CAAC;AAAA,IAC9D,OAAO;AACL,oBAAc,EAAE,6CAA6C,OAAO,KAAK,CAAC;AAAA,IAC5E;AACA,uBAAmB,KAAK;AAAA,EAC1B;AAEA,QAAM,oBAAoB,MAAM,OAAsB,IAAI;AAC1D,QAAM,0BAA0B,MAAM,OAAsB,IAAI;AAEhE,QAAM,eAAe,YAAY;AAC/B,QAAI,CAAC,oBAAoB,CAAC,gBAAgB,CAAC,QAAS;AACpD,oBAAgB,IAAI;AACpB,UAAM,UAAgC;AAAA,MACpC,aAAa;AAAA,MACb;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,aAAa,aAAa;AAAA,MAC1B;AAAA,MACA,GAAI,cAAc,QAAQ,EAAE,aAAa,cAAc,MAAM,IAAI,CAAC;AAAA,MAClE,GAAI,cAAc,QAAQ,EAAE,aAAa,cAAc,MAAM,IAAI,CAAC;AAAA,MAClE,GAAI,gBAAgB,QAAQ,EAAE,eAAe,gBAAgB,MAAM,IAAI,CAAC;AAAA,MACxE,GAAI,gBAAgB,QAAQ,EAAE,eAAe,gBAAgB,MAAM,IAAI,CAAC;AAAA,MACxE,GAAI,cAAc,EAAE,YAAY,IAAI,CAAC;AAAA,MACrC,GAAI,mBAAmB,EAAE,iBAAiB,IAAI,CAAC;AAAA,IACjD;AAIA,UAAM,YAAY,KAAK,UAAU,OAAO;AACxC,QAAI,kBAAkB,YAAY,QAAQ,wBAAwB,YAAY,WAAW;AACvF,wBAAkB,UAAU,OAAO,WAAW;AAC9C,8BAAwB,UAAU;AAAA,IACpC;AACA,UAAM,gBAAsC,EAAE,GAAG,SAAS,gBAAgB,kBAAkB,QAAQ;AACpG,QAAI;AACF,YAAM,SAAS,MAAM,YAAY;AAAA,QAC/B,WAAW,MAAM,eAAe,aAAa;AAAA,QAC7C,SAAS;AAAA,UACP,WAAW;AAAA,UACX,cAAc;AAAA,UACd,YAAY;AAAA,UACZ;AAAA,QACF;AAAA,QACA,iBAAiB;AAAA,MACnB,CAAC;AACD,UAAI,OAAO,IAAI;AACb,0BAAkB,UAAU;AAC5B,gCAAwB,UAAU;AAClC,cAAM,EAAE,oCAAoC,gCAAgC,GAAG,SAAS;AACxF,eAAO,KAAK,QAAQ;AAAA,MACtB,WAAW,OAAO,SAAS,wBAAwB;AACjD;AAAA,UACE;AAAA,YACE;AAAA,YACA;AAAA,UACF;AAAA,UACA;AAAA,QACF;AAAA,MACF,OAAO;AACL,cAAM,EAAE,yCAAyC,OAAO,KAAK,GAAG,OAAO;AAAA,MACzE;AAAA,IACF,SAAS,OAAO;AACd,YAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,EAAE,yCAAyC,4BAA4B;AAChI,YAAM,EAAE,yCAAyC,OAAO,GAAG,OAAO;AAAA,IACpE,UAAE;AACA,sBAAgB,KAAK;AAAA,IACvB;AAAA,EACF;AAEA,QAAM,WAAW,CAAC,SAAqB,QAAQ,IAAI;AAEnD,QAAM,uBAAuB,CAAC,gBAAwB;AACpD,wBAAoB,WAAW;AAC/B,aAAS,WAAW;AAAA,EACtB;AAEA,QAAM,sBAAsB,MAAM;AAChC,SAAK,UAAU,EAAE,KAAK,MAAM,SAAS,SAAS,CAAC;AAAA,EACjD;AAEA,QAAM,sBAAsB,OAAO,UAAkB;AACnD,QAAI,CAAC,iBAAkB;AACvB,yBAAqB,KAAK;AAC1B,+BAA2B,IAAI;AAC/B,0BAAsB,IAAI;AAC1B,UAAM,aAAa,iBAAiB,KAAK,MAAM,KAAK,CAAC;AACrD,UAAM,eAAe,qBAAqB,QAAQ,QAAQ;AAC1D,UAAM,SAAS,MAAM,mBAAmB;AAAA,MACtC,aAAa;AAAA,MACb,GAAI,aAAa,EAAE,UAAU,MAAM,KAAK,EAAE,IAAI,EAAE,OAAO,MAAM,KAAK,EAAE;AAAA,MACpE,MAAM;AAAA,IACR,CAAC;AACD,QAAI,OAAO,IAAI;AACb,uBAAiB,OAAO,MAAM;AAAA,IAChC,OAAO;AACL,4BAAsB,EAAE,qDAAqD,OAAO,KAAK,CAAC;AAC1F,uBAAiB,CAAC,CAAC;AAAA,IACrB;AACA,+BAA2B,KAAK;AAAA,EAClC;AAEA,QAAM,0BACJ,eAAe,MAAM,KACrB,eAAe,WAAW,KAC1B,SAAS,SAAS,KAClB,SAAS,MAAM,cAAc,KAC7B,aAAa,cAAc,KAAK,KAChC,aAAa,cAAc,KAAK,KAChC,aAAa,gBAAgB,KAAK,KAClC,aAAa,gBAAgB,KAAK;AAEpC,QAAM,sBAAsB;AAAA,IAC1B,OAAO,CAAC,aAAa,cAAc,KAAK,IAAI,EAAE,+CAA+C,wBAAwB,IAAI;AAAA,IACzH,OAAO,CAAC,aAAa,cAAc,KAAK,IAAI,EAAE,+CAA+C,uBAAuB,IAAI;AAAA,EAC1H;AACA,QAAM,wBAAwB;AAAA,IAC5B,OAAO,CAAC,aAAa,gBAAgB,KAAK,IAAI,EAAE,+CAA+C,wBAAwB,IAAI;AAAA,IAC3H,OAAO,CAAC,aAAa,gBAAgB,KAAK,IAAI,EAAE,+CAA+C,uBAAuB,IAAI;AAAA,EAC5H;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,cAAc,MAAM,KAAK,aAAa;AAAA,IACtC;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,qBAAqB,CAAC,UAAkB,KAAK,oBAAoB,KAAK;AAAA,EACxE;AACF;",
6
6
  "names": []
7
7
  }