@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
@@ -1,10 +1,19 @@
1
1
  import type { EntityManager } from '@mikro-orm/postgresql'
2
2
  import { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
3
3
  import type { CredentialsService } from '../../integrations/lib/credentials-service'
4
- import { CarrierShipment } from '../data/entities'
4
+ import { CarrierShipment, type CarrierShipmentIdempotencyKey } from '../data/entities'
5
5
  import { emitShippingEvent } from '../events'
6
6
  import { getShippingAdapter } from './adapter-registry'
7
7
  import type { UnifiedShipmentStatus } from './adapter'
8
+ import {
9
+ claimShipmentIdempotency,
10
+ computeShipmentRequestHash,
11
+ findShipmentIdempotencyClaim,
12
+ isShipmentIdempotencyConflictError,
13
+ releaseShipmentIdempotency,
14
+ resolveShipmentIdempotency,
15
+ ShipmentIdempotencyConflictError,
16
+ } from './shipment-idempotency'
8
17
  import {
9
18
  getTerminalShippingEvent,
10
19
  isValidShippingTransition,
@@ -90,52 +99,87 @@ export function createShippingCarrierService(deps: {
90
99
  receiverEmail?: string
91
100
  targetPoint?: string
92
101
  c2cSendingMethod?: string
102
+ idempotencyKey?: string
93
103
  organizationId: string
94
104
  tenantId: string
95
105
  }) {
96
- const { adapter, credentials } = await resolveAdapter(input.providerKey, {
97
- organizationId: input.organizationId,
98
- tenantId: input.tenantId,
99
- })
100
- const mergedCredentials = {
101
- ...credentials,
102
- ...(input.senderPhone !== undefined ? { senderPhone: input.senderPhone } : {}),
103
- ...(input.senderEmail !== undefined ? { senderEmail: input.senderEmail } : {}),
104
- ...(input.receiverPhone !== undefined ? { receiverPhone: input.receiverPhone } : {}),
105
- ...(input.receiverEmail !== undefined ? { receiverEmail: input.receiverEmail } : {}),
106
- ...(input.targetPoint !== undefined ? { targetPoint: input.targetPoint } : {}),
107
- ...(input.c2cSendingMethod !== undefined ? { c2cSendingMethod: input.c2cSendingMethod } : {}),
106
+ const scope = { organizationId: input.organizationId, tenantId: input.tenantId }
107
+ let claim: CarrierShipmentIdempotencyKey | null = null
108
+ if (input.idempotencyKey) {
109
+ const { idempotencyKey: _idempotencyKey, ...fingerprint } = input
110
+ const requestHash = computeShipmentRequestHash(fingerprint)
111
+ const existing = await findShipmentIdempotencyClaim(em, input.idempotencyKey, input.providerKey, scope)
112
+ if (existing) {
113
+ if (existing.requestHash !== requestHash) {
114
+ throw new ShipmentIdempotencyConflictError(input.idempotencyKey)
115
+ }
116
+ if (existing.shipmentId) {
117
+ return findShipmentOrThrow(existing.shipmentId, scope)
118
+ }
119
+ // Claim exists but the original request has not resolved yet (concurrent in-flight).
120
+ throw new ShipmentIdempotencyConflictError(input.idempotencyKey)
121
+ }
122
+ claim = await claimShipmentIdempotency(em, input.idempotencyKey, input.providerKey, requestHash, scope)
123
+ if (!claim) {
124
+ // Lost the claim race with a concurrent request creating the same shipment.
125
+ throw new ShipmentIdempotencyConflictError(input.idempotencyKey)
126
+ }
127
+ }
128
+ let carrierShipmentCreated = false
129
+ try {
130
+ const { adapter, credentials } = await resolveAdapter(input.providerKey, scope)
131
+ const mergedCredentials = {
132
+ ...credentials,
133
+ ...(input.senderPhone !== undefined ? { senderPhone: input.senderPhone } : {}),
134
+ ...(input.senderEmail !== undefined ? { senderEmail: input.senderEmail } : {}),
135
+ ...(input.receiverPhone !== undefined ? { receiverPhone: input.receiverPhone } : {}),
136
+ ...(input.receiverEmail !== undefined ? { receiverEmail: input.receiverEmail } : {}),
137
+ ...(input.targetPoint !== undefined ? { targetPoint: input.targetPoint } : {}),
138
+ ...(input.c2cSendingMethod !== undefined ? { c2cSendingMethod: input.c2cSendingMethod } : {}),
139
+ }
140
+ const created = await adapter.createShipment({
141
+ orderId: input.orderId,
142
+ origin: input.origin,
143
+ destination: input.destination,
144
+ packages: input.packages,
145
+ serviceCode: input.serviceCode,
146
+ credentials: mergedCredentials,
147
+ labelFormat: input.labelFormat,
148
+ })
149
+ carrierShipmentCreated = true
150
+ const shipment = em.create(CarrierShipment, {
151
+ orderId: input.orderId,
152
+ providerKey: input.providerKey,
153
+ carrierShipmentId: created.shipmentId,
154
+ trackingNumber: created.trackingNumber,
155
+ unifiedStatus: 'label_created',
156
+ labelUrl: created.labelUrl ?? null,
157
+ labelData: created.labelData ?? null,
158
+ organizationId: input.organizationId,
159
+ tenantId: input.tenantId,
160
+ })
161
+ await em.persist(shipment).flush()
162
+ if (claim) {
163
+ await resolveShipmentIdempotency(em, claim, shipment.id)
164
+ }
165
+ await emitShippingEvent('shipping_carriers.shipment.created', {
166
+ shipmentId: shipment.id,
167
+ orderId: input.orderId,
168
+ providerKey: input.providerKey,
169
+ trackingNumber: created.trackingNumber,
170
+ organizationId: input.organizationId,
171
+ tenantId: input.tenantId,
172
+ })
173
+ return shipment
174
+ } catch (error: unknown) {
175
+ // Release the claim only when the carrier was NOT successfully called, so a retry can
176
+ // proceed. If the carrier already created the shipment, keep the claim so a retry does
177
+ // not produce a duplicate upstream shipment.
178
+ if (claim && !carrierShipmentCreated && input.idempotencyKey && !isShipmentIdempotencyConflictError(error)) {
179
+ await releaseShipmentIdempotency(em, input.idempotencyKey, input.providerKey, scope).catch(() => {})
180
+ }
181
+ throw error
108
182
  }
109
- const created = await adapter.createShipment({
110
- orderId: input.orderId,
111
- origin: input.origin,
112
- destination: input.destination,
113
- packages: input.packages,
114
- serviceCode: input.serviceCode,
115
- credentials: mergedCredentials,
116
- labelFormat: input.labelFormat,
117
- })
118
- const shipment = em.create(CarrierShipment, {
119
- orderId: input.orderId,
120
- providerKey: input.providerKey,
121
- carrierShipmentId: created.shipmentId,
122
- trackingNumber: created.trackingNumber,
123
- unifiedStatus: 'label_created',
124
- labelUrl: created.labelUrl ?? null,
125
- labelData: created.labelData ?? null,
126
- organizationId: input.organizationId,
127
- tenantId: input.tenantId,
128
- })
129
- await em.persist(shipment).flush()
130
- await emitShippingEvent('shipping_carriers.shipment.created', {
131
- shipmentId: shipment.id,
132
- orderId: input.orderId,
133
- providerKey: input.providerKey,
134
- trackingNumber: created.trackingNumber,
135
- organizationId: input.organizationId,
136
- tenantId: input.tenantId,
137
- })
138
- return shipment
139
183
  },
140
184
 
141
185
  async getTracking(input: {
@@ -4,6 +4,177 @@
4
4
  "public"
5
5
  ],
6
6
  "tables": [
7
+ {
8
+ "name": "carrier_shipment_idempotency_keys",
9
+ "schema": "public",
10
+ "columns": {
11
+ "created_at": {
12
+ "name": "created_at",
13
+ "type": "timestamptz(6)",
14
+ "unsigned": false,
15
+ "autoincrement": false,
16
+ "primary": false,
17
+ "nullable": false,
18
+ "unique": false,
19
+ "length": 6,
20
+ "precision": null,
21
+ "scale": null,
22
+ "default": null,
23
+ "comment": null,
24
+ "collation": null,
25
+ "enumItems": [],
26
+ "mappedType": "datetime"
27
+ },
28
+ "id": {
29
+ "name": "id",
30
+ "type": "uuid",
31
+ "unsigned": false,
32
+ "autoincrement": false,
33
+ "primary": true,
34
+ "nullable": false,
35
+ "unique": false,
36
+ "length": null,
37
+ "precision": null,
38
+ "scale": null,
39
+ "default": "gen_random_uuid()",
40
+ "comment": null,
41
+ "collation": null,
42
+ "enumItems": [],
43
+ "mappedType": "uuid"
44
+ },
45
+ "idempotency_key": {
46
+ "name": "idempotency_key",
47
+ "type": "text",
48
+ "unsigned": false,
49
+ "autoincrement": false,
50
+ "primary": false,
51
+ "nullable": false,
52
+ "unique": true,
53
+ "length": null,
54
+ "precision": null,
55
+ "scale": null,
56
+ "default": null,
57
+ "comment": null,
58
+ "collation": null,
59
+ "enumItems": [],
60
+ "mappedType": "text"
61
+ },
62
+ "organization_id": {
63
+ "name": "organization_id",
64
+ "type": "uuid",
65
+ "unsigned": false,
66
+ "autoincrement": false,
67
+ "primary": false,
68
+ "nullable": false,
69
+ "unique": false,
70
+ "length": null,
71
+ "precision": null,
72
+ "scale": null,
73
+ "default": null,
74
+ "comment": null,
75
+ "collation": null,
76
+ "enumItems": [],
77
+ "mappedType": "uuid"
78
+ },
79
+ "provider_key": {
80
+ "name": "provider_key",
81
+ "type": "text",
82
+ "unsigned": false,
83
+ "autoincrement": false,
84
+ "primary": false,
85
+ "nullable": false,
86
+ "unique": false,
87
+ "length": null,
88
+ "precision": null,
89
+ "scale": null,
90
+ "default": null,
91
+ "comment": null,
92
+ "collation": null,
93
+ "enumItems": [],
94
+ "mappedType": "text"
95
+ },
96
+ "request_hash": {
97
+ "name": "request_hash",
98
+ "type": "text",
99
+ "unsigned": false,
100
+ "autoincrement": false,
101
+ "primary": false,
102
+ "nullable": false,
103
+ "unique": false,
104
+ "length": null,
105
+ "precision": null,
106
+ "scale": null,
107
+ "default": null,
108
+ "comment": null,
109
+ "collation": null,
110
+ "enumItems": [],
111
+ "mappedType": "text"
112
+ },
113
+ "shipment_id": {
114
+ "name": "shipment_id",
115
+ "type": "uuid",
116
+ "unsigned": false,
117
+ "autoincrement": false,
118
+ "primary": false,
119
+ "nullable": true,
120
+ "unique": false,
121
+ "length": null,
122
+ "precision": null,
123
+ "scale": null,
124
+ "default": null,
125
+ "comment": null,
126
+ "collation": null,
127
+ "enumItems": [],
128
+ "mappedType": "uuid"
129
+ },
130
+ "tenant_id": {
131
+ "name": "tenant_id",
132
+ "type": "uuid",
133
+ "unsigned": false,
134
+ "autoincrement": false,
135
+ "primary": false,
136
+ "nullable": false,
137
+ "unique": false,
138
+ "length": null,
139
+ "precision": null,
140
+ "scale": null,
141
+ "default": null,
142
+ "comment": null,
143
+ "collation": null,
144
+ "enumItems": [],
145
+ "mappedType": "uuid"
146
+ }
147
+ },
148
+ "indexes": [
149
+ {
150
+ "columnNames": [
151
+ "id"
152
+ ],
153
+ "composite": false,
154
+ "constraint": true,
155
+ "keyName": "carrier_shipment_idempotency_keys_pkey",
156
+ "primary": true,
157
+ "unique": true
158
+ },
159
+ {
160
+ "columnNames": [
161
+ "idempotency_key",
162
+ "provider_key",
163
+ "organization_id",
164
+ "tenant_id"
165
+ ],
166
+ "composite": true,
167
+ "constraint": true,
168
+ "keyName": "carrier_shipment_idempotency_keys_unique",
169
+ "primary": false,
170
+ "unique": true
171
+ }
172
+ ],
173
+ "checks": [],
174
+ "triggers": [],
175
+ "foreignKeys": {},
176
+ "comment": null
177
+ },
7
178
  {
8
179
  "name": "carrier_shipments",
9
180
  "schema": "public",
@@ -21,6 +192,7 @@
21
192
  "scale": null,
22
193
  "default": null,
23
194
  "comment": null,
195
+ "collation": null,
24
196
  "enumItems": [],
25
197
  "mappedType": "text"
26
198
  },
@@ -37,6 +209,7 @@
37
209
  "scale": null,
38
210
  "default": null,
39
211
  "comment": null,
212
+ "collation": null,
40
213
  "enumItems": [],
41
214
  "mappedType": "text"
42
215
  },
@@ -53,6 +226,7 @@
53
226
  "scale": null,
54
227
  "default": null,
55
228
  "comment": null,
229
+ "collation": null,
56
230
  "enumItems": [],
57
231
  "mappedType": "datetime"
58
232
  },
@@ -69,6 +243,7 @@
69
243
  "scale": null,
70
244
  "default": null,
71
245
  "comment": null,
246
+ "collation": null,
72
247
  "enumItems": [],
73
248
  "mappedType": "datetime"
74
249
  },
@@ -85,6 +260,7 @@
85
260
  "scale": null,
86
261
  "default": "gen_random_uuid()",
87
262
  "comment": null,
263
+ "collation": null,
88
264
  "enumItems": [],
89
265
  "mappedType": "uuid"
90
266
  },
@@ -101,6 +277,7 @@
101
277
  "scale": null,
102
278
  "default": null,
103
279
  "comment": null,
280
+ "collation": null,
104
281
  "enumItems": [],
105
282
  "mappedType": "text"
106
283
  },
@@ -117,6 +294,7 @@
117
294
  "scale": null,
118
295
  "default": null,
119
296
  "comment": null,
297
+ "collation": null,
120
298
  "enumItems": [],
121
299
  "mappedType": "text"
122
300
  },
@@ -133,6 +311,7 @@
133
311
  "scale": null,
134
312
  "default": null,
135
313
  "comment": null,
314
+ "collation": null,
136
315
  "enumItems": [],
137
316
  "mappedType": "datetime"
138
317
  },
@@ -149,6 +328,7 @@
149
328
  "scale": null,
150
329
  "default": null,
151
330
  "comment": null,
331
+ "collation": null,
152
332
  "enumItems": [],
153
333
  "mappedType": "datetime"
154
334
  },
@@ -165,6 +345,7 @@
165
345
  "scale": null,
166
346
  "default": null,
167
347
  "comment": null,
348
+ "collation": null,
168
349
  "enumItems": [],
169
350
  "mappedType": "uuid"
170
351
  },
@@ -181,6 +362,7 @@
181
362
  "scale": null,
182
363
  "default": null,
183
364
  "comment": null,
365
+ "collation": null,
184
366
  "enumItems": [],
185
367
  "mappedType": "uuid"
186
368
  },
@@ -197,6 +379,7 @@
197
379
  "scale": null,
198
380
  "default": null,
199
381
  "comment": null,
382
+ "collation": null,
200
383
  "enumItems": [],
201
384
  "mappedType": "text"
202
385
  },
@@ -213,6 +396,7 @@
213
396
  "scale": null,
214
397
  "default": null,
215
398
  "comment": null,
399
+ "collation": null,
216
400
  "enumItems": [],
217
401
  "mappedType": "uuid"
218
402
  },
@@ -229,6 +413,7 @@
229
413
  "scale": null,
230
414
  "default": null,
231
415
  "comment": null,
416
+ "collation": null,
232
417
  "enumItems": [],
233
418
  "mappedType": "json"
234
419
  },
@@ -245,6 +430,7 @@
245
430
  "scale": null,
246
431
  "default": null,
247
432
  "comment": null,
433
+ "collation": null,
248
434
  "enumItems": [],
249
435
  "mappedType": "text"
250
436
  },
@@ -261,6 +447,7 @@
261
447
  "scale": null,
262
448
  "default": "'label_created'",
263
449
  "comment": null,
450
+ "collation": null,
264
451
  "enumItems": [],
265
452
  "mappedType": "text"
266
453
  },
@@ -277,6 +464,7 @@
277
464
  "scale": null,
278
465
  "default": null,
279
466
  "comment": null,
467
+ "collation": null,
280
468
  "enumItems": [],
281
469
  "mappedType": "datetime"
282
470
  }
@@ -330,6 +518,7 @@
330
518
  }
331
519
  ],
332
520
  "checks": [],
521
+ "triggers": [],
333
522
  "foreignKeys": {},
334
523
  "comment": null
335
524
  },
@@ -350,6 +539,7 @@
350
539
  "scale": null,
351
540
  "default": null,
352
541
  "comment": null,
542
+ "collation": null,
353
543
  "enumItems": [],
354
544
  "mappedType": "text"
355
545
  },
@@ -366,6 +556,7 @@
366
556
  "scale": null,
367
557
  "default": "gen_random_uuid()",
368
558
  "comment": null,
559
+ "collation": null,
369
560
  "enumItems": [],
370
561
  "mappedType": "uuid"
371
562
  },
@@ -382,6 +573,7 @@
382
573
  "scale": null,
383
574
  "default": null,
384
575
  "comment": null,
576
+ "collation": null,
385
577
  "enumItems": [],
386
578
  "mappedType": "text"
387
579
  },
@@ -398,6 +590,7 @@
398
590
  "scale": null,
399
591
  "default": null,
400
592
  "comment": null,
593
+ "collation": null,
401
594
  "enumItems": [],
402
595
  "mappedType": "uuid"
403
596
  },
@@ -414,6 +607,7 @@
414
607
  "scale": null,
415
608
  "default": null,
416
609
  "comment": null,
610
+ "collation": null,
417
611
  "enumItems": [],
418
612
  "mappedType": "datetime"
419
613
  },
@@ -430,6 +624,7 @@
430
624
  "scale": null,
431
625
  "default": null,
432
626
  "comment": null,
627
+ "collation": null,
433
628
  "enumItems": [],
434
629
  "mappedType": "text"
435
630
  },
@@ -446,6 +641,7 @@
446
641
  "scale": null,
447
642
  "default": null,
448
643
  "comment": null,
644
+ "collation": null,
449
645
  "enumItems": [],
450
646
  "mappedType": "uuid"
451
647
  }
@@ -476,6 +672,7 @@
476
672
  }
477
673
  ],
478
674
  "checks": [],
675
+ "triggers": [],
479
676
  "foreignKeys": {},
480
677
  "comment": null
481
678
  }
@@ -0,0 +1,14 @@
1
+ import { Migration } from '@mikro-orm/migrations';
2
+
3
+ export class Migration20260618173616_shipping_carriers extends Migration {
4
+
5
+ override up(): void | Promise<void> {
6
+ this.addSql(`create table "carrier_shipment_idempotency_keys" ("id" uuid not null default gen_random_uuid(), "provider_key" text not null, "idempotency_key" text not null, "request_hash" text not null, "shipment_id" uuid null, "organization_id" uuid not null, "tenant_id" uuid not null, "created_at" timestamptz not null, primary key ("id"));`);
7
+ this.addSql(`alter table "carrier_shipment_idempotency_keys" add constraint "carrier_shipment_idempotency_keys_unique" unique ("idempotency_key", "provider_key", "organization_id", "tenant_id");`);
8
+ }
9
+
10
+ override down(): void | Promise<void> {
11
+ this.addSql(`drop table if exists "carrier_shipment_idempotency_keys";`);
12
+ }
13
+
14
+ }
@@ -977,6 +977,7 @@
977
977
  "staff.timesheets.my.errors.updateAssignment": "Failed to update projects.",
978
978
  "staff.timesheets.my.list.addDescription": "Add description",
979
979
  "staff.timesheets.my.list.noEntries": "No entries for this period.",
980
+ "staff.timesheets.my.list.saveError": "Beschreibung konnte nicht gespeichert werden. Bitte versuchen Sie es erneut.",
980
981
  "staff.timesheets.my.list.today": "Today",
981
982
  "staff.timesheets.my.list.yesterday": "Yesterday",
982
983
  "staff.timesheets.my.loading": "Loading timesheets...",
@@ -977,6 +977,7 @@
977
977
  "staff.timesheets.my.errors.updateAssignment": "Failed to update projects.",
978
978
  "staff.timesheets.my.list.addDescription": "Add description",
979
979
  "staff.timesheets.my.list.noEntries": "No entries for this period.",
980
+ "staff.timesheets.my.list.saveError": "Failed to save description. Please try again.",
980
981
  "staff.timesheets.my.list.today": "Today",
981
982
  "staff.timesheets.my.list.yesterday": "Yesterday",
982
983
  "staff.timesheets.my.loading": "Loading timesheets...",
@@ -977,6 +977,7 @@
977
977
  "staff.timesheets.my.errors.updateAssignment": "Failed to update projects.",
978
978
  "staff.timesheets.my.list.addDescription": "Add description",
979
979
  "staff.timesheets.my.list.noEntries": "No entries for this period.",
980
+ "staff.timesheets.my.list.saveError": "No se pudo guardar la descripción. Inténtalo de nuevo.",
980
981
  "staff.timesheets.my.list.today": "Today",
981
982
  "staff.timesheets.my.list.yesterday": "Yesterday",
982
983
  "staff.timesheets.my.loading": "Loading timesheets...",
@@ -977,6 +977,7 @@
977
977
  "staff.timesheets.my.errors.updateAssignment": "Failed to update projects.",
978
978
  "staff.timesheets.my.list.addDescription": "Add description",
979
979
  "staff.timesheets.my.list.noEntries": "No entries for this period.",
980
+ "staff.timesheets.my.list.saveError": "Nie udało się zapisać opisu. Spróbuj ponownie.",
980
981
  "staff.timesheets.my.list.today": "Today",
981
982
  "staff.timesheets.my.list.yesterday": "Yesterday",
982
983
  "staff.timesheets.my.loading": "Loading timesheets...",
@@ -3,7 +3,9 @@
3
3
  import * as React from 'react'
4
4
  import { useT } from '@open-mercato/shared/lib/i18n/context'
5
5
  import type { TranslateFn } from '@open-mercato/shared/lib/i18n/context'
6
- import { apiCall } from '@open-mercato/ui/backend/utils/apiCall'
6
+ import { apiCallOrThrow } from '@open-mercato/ui/backend/utils/apiCall'
7
+ import { surfaceRecordConflict } from '@open-mercato/ui/backend/conflicts'
8
+ import { flash } from '@open-mercato/ui/backend/FlashMessages'
7
9
  import { ProjectColorDot } from './ProjectColorDot'
8
10
 
9
11
  type TimeEntry = {
@@ -86,6 +88,7 @@ function InlineDescription({
86
88
  placeholder: string
87
89
  onSaved?: () => void
88
90
  }) {
91
+ const t = useT()
89
92
  const [editing, setEditing] = React.useState(false)
90
93
  const [value, setValue] = React.useState(initialValue ?? '')
91
94
  const [saving, setSaving] = React.useState(false)
@@ -103,19 +106,24 @@ function InlineDescription({
103
106
  }
104
107
  setSaving(true)
105
108
  try {
106
- await apiCall(`/api/staff/timesheets/time-entries`, {
109
+ await apiCallOrThrow(`/api/staff/timesheets/time-entries`, {
107
110
  method: 'PUT',
108
111
  headers: { 'Content-Type': 'application/json' },
109
112
  body: JSON.stringify({ id: entryId, notes: trimmed || null }),
110
113
  })
111
- onSaved?.()
112
- } catch {
113
- // silent
114
- } finally {
115
114
  setSaving(false)
116
115
  setEditing(false)
116
+ onSaved?.()
117
+ } catch (err) {
118
+ setSaving(false)
119
+ if (!surfaceRecordConflict(err, t)) {
120
+ flash(
121
+ t('staff.timesheets.my.list.saveError', 'Failed to save description. Please try again.'),
122
+ 'error',
123
+ )
124
+ }
117
125
  }
118
- }, [entryId, value, initialValue, onSaved])
126
+ }, [entryId, value, initialValue, onSaved, t])
119
127
 
120
128
  if (!editing) {
121
129
  return (