@open-mercato/core 0.6.6-develop.6205.1.109e4b6a84 → 0.6.6-develop.6228.1.cf483c68b5

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 (205) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/modules/currencies/backend/currencies/[id]/page.js +33 -8
  3. package/dist/modules/currencies/backend/currencies/[id]/page.js.map +2 -2
  4. package/dist/modules/currencies/backend/currencies/page.js +63 -27
  5. package/dist/modules/currencies/backend/currencies/page.js.map +2 -2
  6. package/dist/modules/currencies/backend/exchange-rates/page.js +35 -14
  7. package/dist/modules/currencies/backend/exchange-rates/page.js.map +2 -2
  8. package/dist/modules/currencies/components/CurrencyFetchingConfig.js +104 -41
  9. package/dist/modules/currencies/components/CurrencyFetchingConfig.js.map +2 -2
  10. package/dist/modules/customer_accounts/api/admin/roles/[id]/acl.js +57 -8
  11. package/dist/modules/customer_accounts/api/admin/roles/[id]/acl.js.map +2 -2
  12. package/dist/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.js +5 -2
  13. package/dist/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.js.map +2 -2
  14. package/dist/modules/customer_accounts/backend/customer_accounts/roles/page.js +30 -14
  15. package/dist/modules/customer_accounts/backend/customer_accounts/roles/page.js.map +2 -2
  16. package/dist/modules/customer_accounts/widgets/injection/account-status/widget.client.js +29 -19
  17. package/dist/modules/customer_accounts/widgets/injection/account-status/widget.client.js.map +2 -2
  18. package/dist/modules/customers/backend/config/customers/pipeline-stages/page.js +166 -64
  19. package/dist/modules/customers/backend/config/customers/pipeline-stages/page.js.map +2 -2
  20. package/dist/modules/customers/components/AddressFormatSettings.js +31 -16
  21. package/dist/modules/customers/components/AddressFormatSettings.js.map +2 -2
  22. package/dist/modules/customers/components/DictionarySettings.js +57 -31
  23. package/dist/modules/customers/components/DictionarySettings.js.map +2 -2
  24. package/dist/modules/customers/components/PipelineSettings.js +156 -76
  25. package/dist/modules/customers/components/PipelineSettings.js.map +2 -2
  26. package/dist/modules/customers/components/calendar/AgendaList.js +10 -4
  27. package/dist/modules/customers/components/calendar/AgendaList.js.map +2 -2
  28. package/dist/modules/customers/components/calendar/CalendarSettingsModal.js +18 -2
  29. package/dist/modules/customers/components/calendar/CalendarSettingsModal.js.map +2 -2
  30. package/dist/modules/customers/components/calendar/MonthGrid.js +5 -2
  31. package/dist/modules/customers/components/calendar/MonthGrid.js.map +2 -2
  32. package/dist/modules/customers/components/calendar/editor/PeopleField.js +6 -0
  33. package/dist/modules/customers/components/calendar/editor/PeopleField.js.map +2 -2
  34. package/dist/modules/customers/components/calendar/editor/RelatedToField.js +2 -0
  35. package/dist/modules/customers/components/calendar/editor/RelatedToField.js.map +2 -2
  36. package/dist/modules/customers/components/calendar/editor/ScheduleSection.js +4 -1
  37. package/dist/modules/customers/components/calendar/editor/ScheduleSection.js.map +2 -2
  38. package/dist/modules/customers/lib/calendar/labels.js +33 -0
  39. package/dist/modules/customers/lib/calendar/labels.js.map +7 -0
  40. package/dist/modules/dashboards/components/WidgetVisibilityEditor.js +32 -11
  41. package/dist/modules/dashboards/components/WidgetVisibilityEditor.js.map +2 -2
  42. package/dist/modules/data_sync/api/mappings/[id]/route.js +59 -0
  43. package/dist/modules/data_sync/api/mappings/[id]/route.js.map +2 -2
  44. package/dist/modules/data_sync/api/mappings/route.js +44 -0
  45. package/dist/modules/data_sync/api/mappings/route.js.map +2 -2
  46. package/dist/modules/data_sync/api/run.js +31 -0
  47. package/dist/modules/data_sync/api/run.js.map +2 -2
  48. package/dist/modules/data_sync/api/runs/[id]/cancel.js +31 -0
  49. package/dist/modules/data_sync/api/runs/[id]/cancel.js.map +2 -2
  50. package/dist/modules/data_sync/api/runs/[id]/retry.js +31 -0
  51. package/dist/modules/data_sync/api/runs/[id]/retry.js.map +2 -2
  52. package/dist/modules/data_sync/api/schedules/[id]/route.js +59 -3
  53. package/dist/modules/data_sync/api/schedules/[id]/route.js.map +2 -2
  54. package/dist/modules/data_sync/api/schedules/route.js +33 -4
  55. package/dist/modules/data_sync/api/schedules/route.js.map +2 -2
  56. package/dist/modules/dictionaries/fields/dictionary.js +29 -4
  57. package/dist/modules/dictionaries/fields/dictionary.js.map +2 -2
  58. package/dist/modules/directory/api/organization-branding/route.js +54 -2
  59. package/dist/modules/directory/api/organization-branding/route.js.map +2 -2
  60. package/dist/modules/directory/backend/directory/branding/page.js +15 -9
  61. package/dist/modules/directory/backend/directory/branding/page.js.map +2 -2
  62. package/dist/modules/directory/backend/directory/organizations/page.js +28 -9
  63. package/dist/modules/directory/backend/directory/organizations/page.js.map +2 -2
  64. package/dist/modules/directory/backend/directory/tenants/page.js +29 -13
  65. package/dist/modules/directory/backend/directory/tenants/page.js.map +2 -2
  66. package/dist/modules/entities/api/definitions.batch.js +15 -0
  67. package/dist/modules/entities/api/definitions.batch.js.map +2 -2
  68. package/dist/modules/entities/api/definitions.js +26 -0
  69. package/dist/modules/entities/api/definitions.js.map +2 -2
  70. package/dist/modules/entities/api/definitions.mutation-guard.js +57 -0
  71. package/dist/modules/entities/api/definitions.mutation-guard.js.map +7 -0
  72. package/dist/modules/entities/api/definitions.restore.js +15 -0
  73. package/dist/modules/entities/api/definitions.restore.js.map +2 -2
  74. package/dist/modules/entities/api/entities.js +31 -3
  75. package/dist/modules/entities/api/entities.js.map +2 -2
  76. package/dist/modules/entities/api/records.js +18 -0
  77. package/dist/modules/entities/api/records.js.map +2 -2
  78. package/dist/modules/entities/backend/entities/user/[entityId]/records/page.js +28 -10
  79. package/dist/modules/entities/backend/entities/user/[entityId]/records/page.js.map +2 -2
  80. package/dist/modules/feature_toggles/api/overrides/route.js +38 -1
  81. package/dist/modules/feature_toggles/api/overrides/route.js.map +2 -2
  82. package/dist/modules/inbox_ops/api/emails/[id]/route.js +30 -0
  83. package/dist/modules/inbox_ops/api/emails/[id]/route.js.map +2 -2
  84. package/dist/modules/inbox_ops/api/proposals/[id]/accept-all/route.js +30 -0
  85. package/dist/modules/inbox_ops/api/proposals/[id]/accept-all/route.js.map +2 -2
  86. package/dist/modules/inbox_ops/api/proposals/[id]/actions/[actionId]/route.js +31 -0
  87. package/dist/modules/inbox_ops/api/proposals/[id]/actions/[actionId]/route.js.map +2 -2
  88. package/dist/modules/inbox_ops/api/proposals/[id]/categorize/route.js +31 -0
  89. package/dist/modules/inbox_ops/api/proposals/[id]/categorize/route.js.map +2 -2
  90. package/dist/modules/messages/components/message-detail/hooks/useMessageDetailsActions.js +68 -39
  91. package/dist/modules/messages/components/message-detail/hooks/useMessageDetailsActions.js.map +2 -2
  92. package/dist/modules/notifications/frontend/NotificationSettingsPageClient.js +14 -4
  93. package/dist/modules/notifications/frontend/NotificationSettingsPageClient.js.map +2 -2
  94. package/dist/modules/payment_gateways/api/cancel/route.js +37 -0
  95. package/dist/modules/payment_gateways/api/cancel/route.js.map +2 -2
  96. package/dist/modules/payment_gateways/api/capture/route.js +37 -0
  97. package/dist/modules/payment_gateways/api/capture/route.js.map +2 -2
  98. package/dist/modules/payment_gateways/api/guards.js +31 -0
  99. package/dist/modules/payment_gateways/api/guards.js.map +7 -0
  100. package/dist/modules/payment_gateways/api/refund/route.js +37 -0
  101. package/dist/modules/payment_gateways/api/refund/route.js.map +2 -2
  102. package/dist/modules/payment_gateways/api/sessions/route.js +37 -0
  103. package/dist/modules/payment_gateways/api/sessions/route.js.map +2 -2
  104. package/dist/modules/planner/api/availability-date-specific.js +11 -1
  105. package/dist/modules/planner/api/availability-date-specific.js.map +2 -2
  106. package/dist/modules/planner/backend/planner/availability-rulesets/page.js +20 -3
  107. package/dist/modules/planner/backend/planner/availability-rulesets/page.js.map +2 -2
  108. package/dist/modules/planner/commands/availability-date-specific.js +16 -0
  109. package/dist/modules/planner/commands/availability-date-specific.js.map +2 -2
  110. package/dist/modules/planner/components/AvailabilityRulesEditor.js +109 -40
  111. package/dist/modules/planner/components/AvailabilityRulesEditor.js.map +2 -2
  112. package/dist/modules/query_index/api/purge.js +35 -3
  113. package/dist/modules/query_index/api/purge.js.map +2 -2
  114. package/dist/modules/query_index/api/reindex.js +41 -3
  115. package/dist/modules/query_index/api/reindex.js.map +2 -2
  116. package/dist/modules/query_index/components/QueryIndexesTable.js +57 -24
  117. package/dist/modules/query_index/components/QueryIndexesTable.js.map +2 -2
  118. package/dist/modules/shipping_carriers/api/shipments/route.js +31 -0
  119. package/dist/modules/shipping_carriers/api/shipments/route.js.map +2 -2
  120. package/dist/modules/shipping_carriers/api/tracking/refresh/route.js +55 -0
  121. package/dist/modules/shipping_carriers/api/tracking/refresh/route.js.map +7 -0
  122. package/dist/modules/shipping_carriers/data/validators.js +6 -1
  123. package/dist/modules/shipping_carriers/data/validators.js.map +2 -2
  124. package/dist/modules/shipping_carriers/lib/shipment-wizard/hooks/useShipmentWizard.js +28 -8
  125. package/dist/modules/shipping_carriers/lib/shipment-wizard/hooks/useShipmentWizard.js.map +2 -2
  126. package/dist/modules/shipping_carriers/lib/shipping-service.js +37 -7
  127. package/dist/modules/shipping_carriers/lib/shipping-service.js.map +2 -2
  128. package/dist/modules/shipping_carriers/workers/status-poller.js +1 -1
  129. package/dist/modules/shipping_carriers/workers/status-poller.js.map +2 -2
  130. package/dist/modules/translations/components/TranslationManager.js +49 -20
  131. package/dist/modules/translations/components/TranslationManager.js.map +2 -2
  132. package/package.json +7 -7
  133. package/src/modules/currencies/backend/currencies/[id]/page.tsx +40 -10
  134. package/src/modules/currencies/backend/currencies/page.tsx +68 -29
  135. package/src/modules/currencies/backend/exchange-rates/page.tsx +40 -15
  136. package/src/modules/currencies/components/CurrencyFetchingConfig.tsx +110 -41
  137. package/src/modules/customer_accounts/api/admin/roles/[id]/acl.ts +69 -7
  138. package/src/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.tsx +16 -8
  139. package/src/modules/customer_accounts/backend/customer_accounts/roles/page.tsx +32 -14
  140. package/src/modules/customer_accounts/widgets/injection/account-status/widget.client.tsx +32 -20
  141. package/src/modules/customers/backend/config/customers/pipeline-stages/page.tsx +171 -64
  142. package/src/modules/customers/components/AddressFormatSettings.tsx +39 -19
  143. package/src/modules/customers/components/DictionarySettings.tsx +63 -29
  144. package/src/modules/customers/components/PipelineSettings.tsx +165 -80
  145. package/src/modules/customers/components/calendar/AgendaList.tsx +13 -8
  146. package/src/modules/customers/components/calendar/CalendarSettingsModal.tsx +16 -2
  147. package/src/modules/customers/components/calendar/MonthGrid.tsx +5 -2
  148. package/src/modules/customers/components/calendar/editor/PeopleField.tsx +6 -0
  149. package/src/modules/customers/components/calendar/editor/RelatedToField.tsx +2 -0
  150. package/src/modules/customers/components/calendar/editor/ScheduleSection.tsx +7 -0
  151. package/src/modules/customers/i18n/de.json +11 -7
  152. package/src/modules/customers/i18n/en.json +6 -2
  153. package/src/modules/customers/i18n/es.json +11 -7
  154. package/src/modules/customers/i18n/pl.json +11 -7
  155. package/src/modules/customers/lib/calendar/labels.ts +42 -0
  156. package/src/modules/dashboards/components/WidgetVisibilityEditor.tsx +39 -11
  157. package/src/modules/data_sync/api/mappings/[id]/route.ts +63 -0
  158. package/src/modules/data_sync/api/mappings/route.ts +48 -0
  159. package/src/modules/data_sync/api/run.ts +33 -0
  160. package/src/modules/data_sync/api/runs/[id]/cancel.ts +34 -0
  161. package/src/modules/data_sync/api/runs/[id]/retry.ts +33 -0
  162. package/src/modules/data_sync/api/schedules/[id]/route.ts +64 -2
  163. package/src/modules/data_sync/api/schedules/route.ts +36 -4
  164. package/src/modules/dictionaries/fields/dictionary.tsx +31 -8
  165. package/src/modules/dictionaries/i18n/de.json +1 -0
  166. package/src/modules/dictionaries/i18n/en.json +1 -0
  167. package/src/modules/dictionaries/i18n/es.json +1 -0
  168. package/src/modules/dictionaries/i18n/pl.json +1 -0
  169. package/src/modules/directory/api/organization-branding/route.ts +61 -0
  170. package/src/modules/directory/backend/directory/branding/page.tsx +16 -10
  171. package/src/modules/directory/backend/directory/organizations/page.tsx +35 -8
  172. package/src/modules/directory/backend/directory/tenants/page.tsx +37 -13
  173. package/src/modules/entities/api/definitions.batch.ts +17 -0
  174. package/src/modules/entities/api/definitions.mutation-guard.ts +80 -0
  175. package/src/modules/entities/api/definitions.restore.ts +17 -0
  176. package/src/modules/entities/api/definitions.ts +30 -0
  177. package/src/modules/entities/api/entities.ts +35 -3
  178. package/src/modules/entities/api/records.ts +20 -0
  179. package/src/modules/entities/backend/entities/user/[entityId]/records/page.tsx +33 -10
  180. package/src/modules/feature_toggles/api/overrides/route.ts +44 -1
  181. package/src/modules/inbox_ops/api/emails/[id]/route.ts +32 -0
  182. package/src/modules/inbox_ops/api/proposals/[id]/accept-all/route.ts +32 -0
  183. package/src/modules/inbox_ops/api/proposals/[id]/actions/[actionId]/route.ts +33 -0
  184. package/src/modules/inbox_ops/api/proposals/[id]/categorize/route.ts +33 -0
  185. package/src/modules/messages/components/message-detail/hooks/useMessageDetailsActions.ts +80 -42
  186. package/src/modules/notifications/frontend/NotificationSettingsPageClient.tsx +21 -4
  187. package/src/modules/payment_gateways/api/cancel/route.ts +39 -0
  188. package/src/modules/payment_gateways/api/capture/route.ts +39 -0
  189. package/src/modules/payment_gateways/api/guards.ts +59 -0
  190. package/src/modules/payment_gateways/api/refund/route.ts +39 -0
  191. package/src/modules/payment_gateways/api/sessions/route.ts +40 -0
  192. package/src/modules/planner/api/availability-date-specific.ts +10 -0
  193. package/src/modules/planner/backend/planner/availability-rulesets/page.tsx +26 -5
  194. package/src/modules/planner/commands/availability-date-specific.ts +24 -0
  195. package/src/modules/planner/components/AvailabilityRulesEditor.tsx +122 -41
  196. package/src/modules/query_index/api/purge.ts +37 -3
  197. package/src/modules/query_index/api/reindex.ts +43 -3
  198. package/src/modules/query_index/components/QueryIndexesTable.tsx +66 -24
  199. package/src/modules/shipping_carriers/api/shipments/route.ts +31 -0
  200. package/src/modules/shipping_carriers/api/tracking/refresh/route.ts +53 -0
  201. package/src/modules/shipping_carriers/data/validators.ts +5 -0
  202. package/src/modules/shipping_carriers/lib/shipment-wizard/hooks/useShipmentWizard.ts +29 -8
  203. package/src/modules/shipping_carriers/lib/shipping-service.ts +43 -7
  204. package/src/modules/shipping_carriers/workers/status-poller.ts +1 -1
  205. package/src/modules/translations/components/TranslationManager.tsx +65 -21
@@ -0,0 +1,53 @@
1
+ import { NextResponse } from 'next/server'
2
+ import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
3
+ import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
4
+ import { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'
5
+ import type { ShippingCarrierService } from '../../../lib/shipping-service'
6
+ import { trackingRefreshSchema } from '../../../data/validators'
7
+ import { shippingCarriersTag } from '../../openapi'
8
+
9
+ export const metadata = {
10
+ path: '/shipping-carriers/tracking/refresh',
11
+ POST: { requireAuth: true, requireFeatures: ['shipping_carriers.manage'] },
12
+ }
13
+
14
+ export async function POST(req: Request) {
15
+ const auth = await getAuthFromRequest(req)
16
+ if (!auth?.tenantId || !auth.orgId) {
17
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
18
+ }
19
+ const payload = await readJsonSafe<unknown>(req)
20
+ const parsed = trackingRefreshSchema.safeParse(payload)
21
+ if (!parsed.success) {
22
+ return NextResponse.json({ error: 'Invalid payload', details: parsed.error.flatten() }, { status: 422 })
23
+ }
24
+ const container = await createRequestContainer()
25
+ const service = container.resolve('shippingCarrierService') as ShippingCarrierService
26
+ try {
27
+ const tracking = await service.refreshTracking({
28
+ ...parsed.data,
29
+ organizationId: auth.orgId as string,
30
+ tenantId: auth.tenantId,
31
+ })
32
+ return NextResponse.json(tracking)
33
+ } catch (error: unknown) {
34
+ const message = error instanceof Error ? error.message : 'Failed to refresh tracking'
35
+ return NextResponse.json({ error: message }, { status: 502 })
36
+ }
37
+ }
38
+
39
+ export const openApi = {
40
+ tags: [shippingCarriersTag],
41
+ summary: 'Refresh tracking',
42
+ methods: {
43
+ POST: {
44
+ summary: 'Refresh tracking and persist the latest shipment status',
45
+ tags: [shippingCarriersTag],
46
+ responses: [
47
+ { status: 200, description: 'Tracking refreshed and shipment status persisted' },
48
+ { status: 422, description: 'Validation failed' },
49
+ { status: 502, description: 'Provider upstream error' },
50
+ ],
51
+ },
52
+ },
53
+ }
@@ -55,6 +55,11 @@ export const trackingQuerySchema = z.object({
55
55
  path: ['shipmentId'],
56
56
  })
57
57
 
58
+ export const trackingRefreshSchema = z.object({
59
+ providerKey: z.string().min(1),
60
+ shipmentId: z.string().uuid(),
61
+ })
62
+
58
63
  export const cancelShipmentSchema = z.object({
59
64
  providerKey: z.string().min(1),
60
65
  shipmentId: z.string().uuid(),
@@ -1,6 +1,7 @@
1
1
  import * as React from 'react'
2
2
  import { useRouter, useSearchParams } from 'next/navigation'
3
3
  import { flash } from '@open-mercato/ui/backend/FlashMessages'
4
+ import { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'
4
5
  import { useT } from '@open-mercato/shared/lib/i18n/context'
5
6
  import {
6
7
  fetchProviders,
@@ -9,6 +10,7 @@ import {
9
10
  createShipment,
10
11
  fetchDropOffPoints,
11
12
  } from './shipmentApi'
13
+ import type { CreateShipmentParams } from './shipmentApi'
12
14
  import type {
13
15
  WizardStep,
14
16
  Provider,
@@ -105,6 +107,9 @@ export const useShipmentWizard = (): ShipmentWizard => {
105
107
  const t = useT()
106
108
  const router = useRouter()
107
109
  const searchParams = useSearchParams()
110
+ const { runMutation, retryLastMutation } = useGuardedMutation({
111
+ contextId: 'shipping_carriers.shipment.create',
112
+ })
108
113
  const orderId = searchParams?.get('orderId') ?? null
109
114
 
110
115
  const [step, setStep] = React.useState<WizardStep>('provider')
@@ -192,7 +197,7 @@ export const useShipmentWizard = (): ShipmentWizard => {
192
197
  const handleSubmit = async () => {
193
198
  if (!selectedProvider || !selectedRate || !orderId) return
194
199
  setIsSubmitting(true)
195
- const result = await createShipment({
200
+ const payload: CreateShipmentParams = {
196
201
  providerKey: selectedProvider,
197
202
  orderId,
198
203
  origin,
@@ -206,13 +211,29 @@ export const useShipmentWizard = (): ShipmentWizard => {
206
211
  ...(receiverContact.email ? { receiverEmail: receiverContact.email } : {}),
207
212
  ...(targetPoint ? { targetPoint } : {}),
208
213
  ...(c2cSendingMethod ? { c2cSendingMethod } : {}),
209
- })
210
- setIsSubmitting(false)
211
- if (result.ok) {
212
- flash(t('shipping_carriers.create.success', 'Shipment created successfully.'), 'success')
213
- router.push(backHref)
214
- } else {
215
- flash(t('shipping_carriers.create.error.create', result.error), 'error')
214
+ }
215
+ try {
216
+ const result = await runMutation({
217
+ operation: () => createShipment(payload),
218
+ context: {
219
+ operation: 'create',
220
+ resourceKind: 'shipping_carriers.shipment',
221
+ resourceId: orderId,
222
+ retryLastMutation,
223
+ },
224
+ mutationPayload: payload as unknown as Record<string, unknown>,
225
+ })
226
+ if (result.ok) {
227
+ flash(t('shipping_carriers.create.success', 'Shipment created successfully.'), 'success')
228
+ router.push(backHref)
229
+ } else {
230
+ flash(t('shipping_carriers.create.error.create', result.error), 'error')
231
+ }
232
+ } catch (error) {
233
+ const message = error instanceof Error ? error.message : t('shipping_carriers.create.error.create', 'Failed to create shipment.')
234
+ flash(t('shipping_carriers.create.error.create', message), 'error')
235
+ } finally {
236
+ setIsSubmitting(false)
216
237
  }
217
238
  }
218
239
 
@@ -5,7 +5,13 @@ import { CarrierShipment } 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 { isValidShippingTransition, ShipmentCancelNotAllowedError } from './status-sync'
8
+ import {
9
+ getTerminalShippingEvent,
10
+ isValidShippingTransition,
11
+ ShipmentCancelNotAllowedError,
12
+ syncShipmentStatus,
13
+ TERMINAL_SHIPPING_STATUSES,
14
+ } from './status-sync'
9
15
 
10
16
  export function createShippingCarrierService(deps: {
11
17
  em: EntityManager
@@ -160,16 +166,46 @@ export function createShippingCarrierService(deps: {
160
166
  },
161
167
  )
162
168
  : null
163
- const tracking = await adapter.getTracking({
169
+ return adapter.getTracking({
164
170
  shipmentId: shipment?.carrierShipmentId ?? input.shipmentId,
165
171
  trackingNumber: input.trackingNumber ?? shipment?.trackingNumber,
166
172
  credentials,
167
173
  })
168
- if (shipment) {
169
- shipment.unifiedStatus = tracking.status
170
- shipment.trackingEvents = tracking.events
171
- shipment.lastPolledAt = new Date()
172
- await em.flush()
174
+ },
175
+
176
+ async refreshTracking(input: {
177
+ providerKey: string
178
+ shipmentId: string
179
+ organizationId: string
180
+ tenantId: string
181
+ }) {
182
+ const scope = { organizationId: input.organizationId, tenantId: input.tenantId }
183
+ const shipment = await findShipmentOrThrow(input.shipmentId, scope)
184
+ const { adapter, credentials } = await resolveAdapter(input.providerKey, scope)
185
+ const tracking = await adapter.getTracking({
186
+ shipmentId: shipment.carrierShipmentId,
187
+ trackingNumber: shipment.trackingNumber,
188
+ credentials,
189
+ })
190
+ const previousStatus = shipment.unifiedStatus
191
+ shipment.trackingEvents = tracking.events
192
+ shipment.lastPolledAt = new Date()
193
+ const transitionApplied = syncShipmentStatus(shipment, tracking.status)
194
+ await em.flush()
195
+ if (transitionApplied) {
196
+ const eventPayload = {
197
+ shipmentId: shipment.id,
198
+ providerKey: input.providerKey,
199
+ previousStatus,
200
+ newStatus: tracking.status,
201
+ organizationId: input.organizationId,
202
+ tenantId: input.tenantId,
203
+ }
204
+ await emitShippingEvent('shipping_carriers.shipment.status_changed', eventPayload)
205
+ if (TERMINAL_SHIPPING_STATUSES.has(tracking.status)) {
206
+ const terminalEvent = getTerminalShippingEvent(tracking.status)
207
+ if (terminalEvent) await emitShippingEvent(terminalEvent, eventPayload)
208
+ }
173
209
  }
174
210
  return tracking
175
211
  },
@@ -23,7 +23,7 @@ export const metadata: WorkerMeta = {
23
23
  export default async function handle(job: QueuedJob<PollerJobPayload>, ctx: HandlerContext): Promise<void> {
24
24
  const service = ctx.resolve<ShippingCarrierService>('shippingCarrierService')
25
25
  for (const shipmentId of job.payload.shipmentIds) {
26
- await service.getTracking({
26
+ await service.refreshTracking({
27
27
  providerKey: job.payload.providerKey,
28
28
  shipmentId,
29
29
  organizationId: job.payload.scope.organizationId,
@@ -9,6 +9,7 @@ import { LoadingMessage, ErrorMessage } from '@open-mercato/ui/backend/detail'
9
9
  import { flash } from '@open-mercato/ui/backend/FlashMessages'
10
10
  import { apiCall, readApiResultOrThrow, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'
11
11
  import { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'
12
+ import { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'
12
13
  import { useCustomFieldDefs } from '@open-mercato/ui/backend/utils/customFieldDefs'
13
14
  import { Save, Plus, X } from 'lucide-react'
14
15
  import { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'
@@ -19,6 +20,9 @@ import { formatEntityLabel, buildEntityListUrl, getRecordLabel, resolveBaseValue
19
20
  import { resolveFieldList } from '../lib/resolve-field-list'
20
21
  import type { ResolvedField } from '../lib/resolve-field-list'
21
22
 
23
+ const TRANSLATION_MUTATION_CONTEXT_ID = 'translations.entity-translations'
24
+ const SUPPORTED_LOCALES_MUTATION_CONTEXT_ID = 'translations.supported-locales'
25
+
22
26
  type TranslationManagerProps = {
23
27
  entityType?: string
24
28
  recordId?: string
@@ -218,6 +222,16 @@ export function TranslationManager({
218
222
  }
219
223
  }, [translationSignature, translationData])
220
224
 
225
+ const { runMutation, retryLastMutation } = useGuardedMutation<{
226
+ formId: string
227
+ entityType: string
228
+ recordId: string
229
+ resourceKind: string
230
+ resourceId: string
231
+ data: TranslationsResponse | null
232
+ retryLastMutation: () => Promise<boolean>
233
+ }>({ contextId: TRANSLATION_MUTATION_CONTEXT_ID })
234
+
221
235
  const mutation = useMutation({
222
236
  mutationFn: async () => {
223
237
  if (!entityType || !recordId) {
@@ -239,21 +253,35 @@ export function TranslationManager({
239
253
  console.warn('[translations] Save skipped: payload is empty — no locale contains any non-empty field')
240
254
  throw new Error(t('translations.manager.errors.nothingToSave', 'Nothing to save — enter a translation first'))
241
255
  }
242
- const res = await withScopedApiRequestHeaders(
243
- buildOptimisticLockHeader(translationData?.updatedAt),
244
- () => apiCall(
245
- `/api/translations/${encodeURIComponent(entityType)}/${encodeURIComponent(recordId)}`,
246
- {
247
- method: 'PUT',
248
- headers: { 'content-type': 'application/json' },
249
- body: JSON.stringify(body),
250
- },
251
- ),
252
- )
253
- if (!res.ok) {
254
- throw new Error(t('translations.manager.errors.save', 'Failed to save translations'))
255
- }
256
- return true
256
+ return runMutation({
257
+ operation: async () => {
258
+ const res = await withScopedApiRequestHeaders(
259
+ buildOptimisticLockHeader(translationData?.updatedAt),
260
+ () => apiCall(
261
+ `/api/translations/${encodeURIComponent(entityType)}/${encodeURIComponent(recordId)}`,
262
+ {
263
+ method: 'PUT',
264
+ headers: { 'content-type': 'application/json' },
265
+ body: JSON.stringify(body),
266
+ },
267
+ ),
268
+ )
269
+ if (!res.ok) {
270
+ throw new Error(t('translations.manager.errors.save', 'Failed to save translations'))
271
+ }
272
+ return true
273
+ },
274
+ context: {
275
+ formId: TRANSLATION_MUTATION_CONTEXT_ID,
276
+ entityType,
277
+ recordId,
278
+ resourceKind: 'translation',
279
+ resourceId: recordId,
280
+ data: translationData ?? null,
281
+ retryLastMutation,
282
+ },
283
+ mutationPayload: body,
284
+ })
257
285
  },
258
286
  onSuccess: () => {
259
287
  flash(t('translations.manager.flash.saved', 'Translations saved'), 'success')
@@ -541,16 +569,32 @@ export function LocaleManager() {
541
569
  const { data: locales = [], isLoading } = useTranslationLocales()
542
570
  const [newLocale, setNewLocale] = React.useState('')
543
571
 
572
+ const { runMutation, retryLastMutation } = useGuardedMutation<{
573
+ formId: string
574
+ resourceKind: string
575
+ retryLastMutation: () => Promise<boolean>
576
+ }>({ contextId: SUPPORTED_LOCALES_MUTATION_CONTEXT_ID })
577
+
544
578
  const mutation = useMutation({
545
579
  mutationFn: async (updatedLocales: string[]) => {
546
580
  // optimistic-lock-exempt: single-row tenant supported-locales settings list — no per-record version / concurrent record edit
547
- const res = await apiCall<{ locales: string[] }>('/api/translations/locales', {
548
- method: 'PUT',
549
- headers: { 'content-type': 'application/json' },
550
- body: JSON.stringify({ locales: updatedLocales }),
581
+ return runMutation({
582
+ operation: async () => {
583
+ const res = await apiCall<{ locales: string[] }>('/api/translations/locales', {
584
+ method: 'PUT',
585
+ headers: { 'content-type': 'application/json' },
586
+ body: JSON.stringify({ locales: updatedLocales }),
587
+ })
588
+ if (!res.ok) throw new Error('Failed to save locales')
589
+ return res.result?.locales ?? updatedLocales
590
+ },
591
+ context: {
592
+ formId: SUPPORTED_LOCALES_MUTATION_CONTEXT_ID,
593
+ resourceKind: 'translation-locales',
594
+ retryLastMutation,
595
+ },
596
+ mutationPayload: { locales: updatedLocales },
551
597
  })
552
- if (!res.ok) throw new Error('Failed to save locales')
553
- return res.result?.locales ?? updatedLocales
554
598
  },
555
599
  onSuccess: (result) => {
556
600
  queryClient.setQueryData(['translation-locales'], result)