@open-mercato/core 0.7.1-develop.7130.1.fef2396fd8 → 0.7.1-develop.7135.1.19bf969756

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 (242) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/helpers/integration/communicationChannelsFixtures.js.map +2 -2
  3. package/dist/modules/audit_logs/api/audit-logs/actions/redo/route.js +5 -0
  4. package/dist/modules/audit_logs/api/audit-logs/actions/redo/route.js.map +2 -2
  5. package/dist/modules/auth/api/profile/route.js +5 -0
  6. package/dist/modules/auth/api/profile/route.js.map +2 -2
  7. package/dist/modules/communication_channels/api/post/test-seed/route.js +40 -2
  8. package/dist/modules/communication_channels/api/post/test-seed/route.js.map +2 -2
  9. package/dist/modules/communication_channels/lib/access-control.js +6 -1
  10. package/dist/modules/communication_channels/lib/access-control.js.map +2 -2
  11. package/dist/modules/communication_channels/lib/system-user.js +8 -3
  12. package/dist/modules/communication_channels/lib/system-user.js.map +2 -2
  13. package/dist/modules/customers/api/activities/route.js +13 -0
  14. package/dist/modules/customers/api/activities/route.js.map +2 -2
  15. package/dist/modules/customers/api/dictionaries/[kind]/[id]/route.js +9 -0
  16. package/dist/modules/customers/api/dictionaries/[kind]/[id]/route.js.map +2 -2
  17. package/dist/modules/customers/api/dictionaries/[kind]/route.js +5 -0
  18. package/dist/modules/customers/api/dictionaries/[kind]/route.js.map +2 -2
  19. package/dist/modules/customers/api/dictionaries/kind-settings/route.js +5 -0
  20. package/dist/modules/customers/api/dictionaries/kind-settings/route.js.map +2 -2
  21. package/dist/modules/customers/api/interactions/cancel/route.js +5 -0
  22. package/dist/modules/customers/api/interactions/cancel/route.js.map +2 -2
  23. package/dist/modules/customers/api/interactions/complete/route.js +5 -0
  24. package/dist/modules/customers/api/interactions/complete/route.js.map +2 -2
  25. package/dist/modules/customers/api/labels/assign/route.js +5 -0
  26. package/dist/modules/customers/api/labels/assign/route.js.map +2 -2
  27. package/dist/modules/customers/api/labels/route.js +5 -0
  28. package/dist/modules/customers/api/labels/route.js.map +2 -2
  29. package/dist/modules/customers/api/labels/unassign/route.js +5 -0
  30. package/dist/modules/customers/api/labels/unassign/route.js.map +2 -2
  31. package/dist/modules/customers/api/people/[id]/companies/[linkId]/route.js +9 -0
  32. package/dist/modules/customers/api/people/[id]/companies/[linkId]/route.js.map +2 -2
  33. package/dist/modules/customers/api/people/[id]/companies/route.js +5 -0
  34. package/dist/modules/customers/api/people/[id]/companies/route.js.map +2 -2
  35. package/dist/modules/customers/api/pipeline-stages/reorder/route.js +5 -0
  36. package/dist/modules/customers/api/pipeline-stages/reorder/route.js.map +2 -2
  37. package/dist/modules/customers/api/pipeline-stages/route.js +13 -0
  38. package/dist/modules/customers/api/pipeline-stages/route.js.map +2 -2
  39. package/dist/modules/customers/api/pipelines/route.js +13 -0
  40. package/dist/modules/customers/api/pipelines/route.js.map +2 -2
  41. package/dist/modules/customers/api/settings/address-format/route.js +5 -0
  42. package/dist/modules/customers/api/settings/address-format/route.js.map +2 -2
  43. package/dist/modules/customers/api/settings/dictionary-sort-modes/route.js +5 -0
  44. package/dist/modules/customers/api/settings/dictionary-sort-modes/route.js.map +2 -2
  45. package/dist/modules/customers/api/settings/stuck-threshold/route.js +5 -0
  46. package/dist/modules/customers/api/settings/stuck-threshold/route.js.map +2 -2
  47. package/dist/modules/customers/api/tags/assign/route.js +5 -0
  48. package/dist/modules/customers/api/tags/assign/route.js.map +2 -2
  49. package/dist/modules/customers/api/tags/unassign/route.js +5 -0
  50. package/dist/modules/customers/api/tags/unassign/route.js.map +2 -2
  51. package/dist/modules/customers/api/todos/route.js +13 -0
  52. package/dist/modules/customers/api/todos/route.js.map +2 -2
  53. package/dist/modules/dictionaries/api/[dictionaryId]/entries/[entryId]/route.js +9 -0
  54. package/dist/modules/dictionaries/api/[dictionaryId]/entries/[entryId]/route.js.map +2 -2
  55. package/dist/modules/dictionaries/api/[dictionaryId]/entries/reorder/route.js +5 -0
  56. package/dist/modules/dictionaries/api/[dictionaryId]/entries/reorder/route.js.map +2 -2
  57. package/dist/modules/dictionaries/api/[dictionaryId]/entries/route.js +5 -0
  58. package/dist/modules/dictionaries/api/[dictionaryId]/entries/route.js.map +2 -2
  59. package/dist/modules/dictionaries/api/[dictionaryId]/entries/set-default/route.js +5 -0
  60. package/dist/modules/dictionaries/api/[dictionaryId]/entries/set-default/route.js.map +2 -2
  61. package/dist/modules/directory/api/organization-branding/route.js +5 -0
  62. package/dist/modules/directory/api/organization-branding/route.js.map +2 -2
  63. package/dist/modules/eudr/api/plots/import/route.js.map +2 -2
  64. package/dist/modules/eudr/api/product-mappings/suggestions/apply/route.js.map +2 -2
  65. package/dist/modules/feature_toggles/api/overrides/route.js +5 -0
  66. package/dist/modules/feature_toggles/api/overrides/route.js.map +2 -2
  67. package/dist/modules/messages/api/[id]/actions/[actionId]/route.js +5 -0
  68. package/dist/modules/messages/api/[id]/actions/[actionId]/route.js.map +2 -2
  69. package/dist/modules/messages/api/[id]/conversation/archive/route.js +5 -0
  70. package/dist/modules/messages/api/[id]/conversation/archive/route.js.map +2 -2
  71. package/dist/modules/messages/api/[id]/conversation/read/route.js +5 -0
  72. package/dist/modules/messages/api/[id]/conversation/read/route.js.map +2 -2
  73. package/dist/modules/messages/api/[id]/conversation/route.js +5 -0
  74. package/dist/modules/messages/api/[id]/conversation/route.js.map +2 -2
  75. package/dist/modules/messages/api/[id]/forward/route.js +5 -0
  76. package/dist/modules/messages/api/[id]/forward/route.js.map +2 -2
  77. package/dist/modules/messages/api/[id]/reply/route.js +5 -0
  78. package/dist/modules/messages/api/[id]/reply/route.js.map +2 -2
  79. package/dist/modules/messages/api/[id]/route.js +9 -0
  80. package/dist/modules/messages/api/[id]/route.js.map +2 -2
  81. package/dist/modules/messages/api/token/[token]/route.js +5 -0
  82. package/dist/modules/messages/api/token/[token]/route.js.map +2 -2
  83. package/dist/modules/resources/api/resources/tags/assign/route.js +5 -0
  84. package/dist/modules/resources/api/resources/tags/assign/route.js.map +2 -2
  85. package/dist/modules/resources/api/resources/tags/unassign/route.js +5 -0
  86. package/dist/modules/resources/api/resources/tags/unassign/route.js.map +2 -2
  87. package/dist/modules/sales/api/quotes/accept/route.js +5 -0
  88. package/dist/modules/sales/api/quotes/accept/route.js.map +2 -2
  89. package/dist/modules/sales/api/quotes/convert/route.js +5 -0
  90. package/dist/modules/sales/api/quotes/convert/route.js.map +2 -2
  91. package/dist/modules/sales/api/settings/document-numbers/route.js +5 -0
  92. package/dist/modules/sales/api/settings/document-numbers/route.js.map +2 -2
  93. package/dist/modules/sales/api/settings/order-editing/route.js +5 -0
  94. package/dist/modules/sales/api/settings/order-editing/route.js.map +2 -2
  95. package/dist/modules/staff/api/leave-requests/accept/route.js +5 -0
  96. package/dist/modules/staff/api/leave-requests/accept/route.js.map +2 -2
  97. package/dist/modules/staff/api/leave-requests/reject/route.js +5 -0
  98. package/dist/modules/staff/api/leave-requests/reject/route.js.map +2 -2
  99. package/dist/modules/staff/api/team-members/self/route.js +5 -0
  100. package/dist/modules/staff/api/team-members/self/route.js.map +2 -2
  101. package/dist/modules/staff/api/team-members/tags/assign/route.js +5 -0
  102. package/dist/modules/staff/api/team-members/tags/assign/route.js.map +2 -2
  103. package/dist/modules/staff/api/team-members/tags/unassign/route.js +5 -0
  104. package/dist/modules/staff/api/team-members/tags/unassign/route.js.map +2 -2
  105. package/dist/modules/staff/api/timesheets/time-entries/start-timer/route.js +5 -0
  106. package/dist/modules/staff/api/timesheets/time-entries/start-timer/route.js.map +2 -2
  107. package/dist/modules/translations/api/[entityType]/[entityId]/route.js +9 -0
  108. package/dist/modules/translations/api/[entityType]/[entityId]/route.js.map +2 -2
  109. package/dist/modules/warranty_claims/api/ai/assess/route.js +25 -18
  110. package/dist/modules/warranty_claims/api/ai/assess/route.js.map +2 -2
  111. package/dist/modules/warranty_claims/api/assign/route.js +5 -0
  112. package/dist/modules/warranty_claims/api/assign/route.js.map +2 -2
  113. package/dist/modules/warranty_claims/api/credit-memo/route.js +16 -12
  114. package/dist/modules/warranty_claims/api/credit-memo/route.js.map +2 -2
  115. package/dist/modules/warranty_claims/api/events/route.js +6 -1
  116. package/dist/modules/warranty_claims/api/events/route.js.map +2 -2
  117. package/dist/modules/warranty_claims/api/portal/attachments/route.js +24 -27
  118. package/dist/modules/warranty_claims/api/portal/attachments/route.js.map +2 -2
  119. package/dist/modules/warranty_claims/api/portal/claims/[id]/route.js +8 -10
  120. package/dist/modules/warranty_claims/api/portal/claims/[id]/route.js.map +2 -2
  121. package/dist/modules/warranty_claims/api/portal/claims/[id]/shared.js +4 -9
  122. package/dist/modules/warranty_claims/api/portal/claims/[id]/shared.js.map +2 -2
  123. package/dist/modules/warranty_claims/api/portal/claims/[id]/submit/route.js +8 -3
  124. package/dist/modules/warranty_claims/api/portal/claims/[id]/submit/route.js.map +2 -2
  125. package/dist/modules/warranty_claims/api/portal/claims/[id]/withdraw/route.js +8 -3
  126. package/dist/modules/warranty_claims/api/portal/claims/[id]/withdraw/route.js.map +2 -2
  127. package/dist/modules/warranty_claims/api/portal/claims/route.js +9 -9
  128. package/dist/modules/warranty_claims/api/portal/claims/route.js.map +2 -2
  129. package/dist/modules/warranty_claims/api/portal/events/route.js +17 -13
  130. package/dist/modules/warranty_claims/api/portal/events/route.js.map +2 -2
  131. package/dist/modules/warranty_claims/api/receiving/route.js +19 -15
  132. package/dist/modules/warranty_claims/api/receiving/route.js.map +2 -2
  133. package/dist/modules/warranty_claims/api/registrations/route.js +9 -4
  134. package/dist/modules/warranty_claims/api/registrations/route.js.map +2 -2
  135. package/dist/modules/warranty_claims/api/replacement-order/route.js +16 -12
  136. package/dist/modules/warranty_claims/api/replacement-order/route.js.map +2 -2
  137. package/dist/modules/warranty_claims/api/return-label/route.js +25 -19
  138. package/dist/modules/warranty_claims/api/return-label/route.js.map +2 -2
  139. package/dist/modules/warranty_claims/api/risk/route.js +2 -1
  140. package/dist/modules/warranty_claims/api/risk/route.js.map +2 -2
  141. package/dist/modules/warranty_claims/api/sales-return/route.js +16 -12
  142. package/dist/modules/warranty_claims/api/sales-return/route.js.map +2 -2
  143. package/dist/modules/warranty_claims/api/settings-general/route.js +5 -0
  144. package/dist/modules/warranty_claims/api/settings-general/route.js.map +2 -2
  145. package/dist/modules/warranty_claims/api/submit/route.js +5 -0
  146. package/dist/modules/warranty_claims/api/submit/route.js.map +2 -2
  147. package/dist/modules/warranty_claims/api/transition/route.js +5 -0
  148. package/dist/modules/warranty_claims/api/transition/route.js.map +2 -2
  149. package/dist/modules/warranty_claims/api/troubleshooting-guides/route.js +22 -1
  150. package/dist/modules/warranty_claims/api/troubleshooting-guides/route.js.map +2 -2
  151. package/dist/modules/warranty_claims/api/vendor-recovery/route.js +5 -0
  152. package/dist/modules/warranty_claims/api/vendor-recovery/route.js.map +2 -2
  153. package/dist/modules/warranty_claims/api/vendor-recovery-suggestions/route.js +2 -1
  154. package/dist/modules/warranty_claims/api/vendor-recovery-suggestions/route.js.map +2 -2
  155. package/dist/modules/warranty_claims/commands/shared.js +2 -2
  156. package/dist/modules/warranty_claims/commands/shared.js.map +2 -2
  157. package/dist/modules/warranty_claims/lib/portalAuthGuard.js +31 -0
  158. package/dist/modules/warranty_claims/lib/portalAuthGuard.js.map +7 -0
  159. package/dist/modules/wms/api/sales-orders/[salesOrderId]/warehouse-assignment/route.js +5 -0
  160. package/dist/modules/wms/api/sales-orders/[salesOrderId]/warehouse-assignment/route.js.map +2 -2
  161. package/package.json +7 -7
  162. package/src/helpers/integration/communicationChannelsFixtures.ts +13 -0
  163. package/src/modules/audit_logs/api/audit-logs/actions/redo/route.ts +5 -0
  164. package/src/modules/auth/api/profile/route.ts +5 -0
  165. package/src/modules/communication_channels/api/post/test-seed/route.ts +47 -2
  166. package/src/modules/communication_channels/lib/access-control.ts +28 -0
  167. package/src/modules/communication_channels/lib/system-user.ts +40 -22
  168. package/src/modules/customers/api/activities/route.ts +13 -0
  169. package/src/modules/customers/api/dictionaries/[kind]/[id]/route.ts +9 -0
  170. package/src/modules/customers/api/dictionaries/[kind]/route.ts +5 -0
  171. package/src/modules/customers/api/dictionaries/kind-settings/route.ts +5 -0
  172. package/src/modules/customers/api/interactions/cancel/route.ts +5 -0
  173. package/src/modules/customers/api/interactions/complete/route.ts +5 -0
  174. package/src/modules/customers/api/labels/assign/route.ts +5 -0
  175. package/src/modules/customers/api/labels/route.ts +5 -0
  176. package/src/modules/customers/api/labels/unassign/route.ts +5 -0
  177. package/src/modules/customers/api/people/[id]/companies/[linkId]/route.ts +9 -0
  178. package/src/modules/customers/api/people/[id]/companies/route.ts +5 -0
  179. package/src/modules/customers/api/pipeline-stages/reorder/route.ts +5 -0
  180. package/src/modules/customers/api/pipeline-stages/route.ts +13 -0
  181. package/src/modules/customers/api/pipelines/route.ts +13 -0
  182. package/src/modules/customers/api/settings/address-format/route.ts +5 -0
  183. package/src/modules/customers/api/settings/dictionary-sort-modes/route.ts +5 -0
  184. package/src/modules/customers/api/settings/stuck-threshold/route.ts +5 -0
  185. package/src/modules/customers/api/tags/assign/route.ts +5 -0
  186. package/src/modules/customers/api/tags/unassign/route.ts +5 -0
  187. package/src/modules/customers/api/todos/route.ts +13 -0
  188. package/src/modules/dictionaries/api/[dictionaryId]/entries/[entryId]/route.ts +9 -0
  189. package/src/modules/dictionaries/api/[dictionaryId]/entries/reorder/route.ts +5 -0
  190. package/src/modules/dictionaries/api/[dictionaryId]/entries/route.ts +5 -0
  191. package/src/modules/dictionaries/api/[dictionaryId]/entries/set-default/route.ts +5 -0
  192. package/src/modules/directory/api/organization-branding/route.ts +5 -0
  193. package/src/modules/eudr/api/plots/import/route.ts +7 -0
  194. package/src/modules/eudr/api/product-mappings/suggestions/apply/route.ts +7 -0
  195. package/src/modules/feature_toggles/api/overrides/route.ts +5 -0
  196. package/src/modules/messages/api/[id]/actions/[actionId]/route.ts +5 -0
  197. package/src/modules/messages/api/[id]/conversation/archive/route.ts +5 -0
  198. package/src/modules/messages/api/[id]/conversation/read/route.ts +5 -0
  199. package/src/modules/messages/api/[id]/conversation/route.ts +5 -0
  200. package/src/modules/messages/api/[id]/forward/route.ts +5 -0
  201. package/src/modules/messages/api/[id]/reply/route.ts +5 -0
  202. package/src/modules/messages/api/[id]/route.ts +9 -0
  203. package/src/modules/messages/api/token/[token]/route.ts +5 -0
  204. package/src/modules/resources/api/resources/tags/assign/route.ts +5 -0
  205. package/src/modules/resources/api/resources/tags/unassign/route.ts +5 -0
  206. package/src/modules/sales/api/quotes/accept/route.ts +5 -0
  207. package/src/modules/sales/api/quotes/convert/route.ts +5 -0
  208. package/src/modules/sales/api/settings/document-numbers/route.ts +5 -0
  209. package/src/modules/sales/api/settings/order-editing/route.ts +5 -0
  210. package/src/modules/staff/api/leave-requests/accept/route.ts +5 -0
  211. package/src/modules/staff/api/leave-requests/reject/route.ts +5 -0
  212. package/src/modules/staff/api/team-members/self/route.ts +5 -0
  213. package/src/modules/staff/api/team-members/tags/assign/route.ts +5 -0
  214. package/src/modules/staff/api/team-members/tags/unassign/route.ts +5 -0
  215. package/src/modules/staff/api/timesheets/time-entries/start-timer/route.ts +5 -0
  216. package/src/modules/translations/api/[entityType]/[entityId]/route.ts +9 -0
  217. package/src/modules/warranty_claims/api/ai/assess/route.ts +28 -17
  218. package/src/modules/warranty_claims/api/assign/route.ts +5 -0
  219. package/src/modules/warranty_claims/api/credit-memo/route.ts +19 -13
  220. package/src/modules/warranty_claims/api/events/route.ts +6 -1
  221. package/src/modules/warranty_claims/api/portal/attachments/route.ts +29 -27
  222. package/src/modules/warranty_claims/api/portal/claims/[id]/route.ts +9 -10
  223. package/src/modules/warranty_claims/api/portal/claims/[id]/shared.ts +5 -9
  224. package/src/modules/warranty_claims/api/portal/claims/[id]/submit/route.ts +8 -3
  225. package/src/modules/warranty_claims/api/portal/claims/[id]/withdraw/route.ts +8 -3
  226. package/src/modules/warranty_claims/api/portal/claims/route.ts +10 -9
  227. package/src/modules/warranty_claims/api/portal/events/route.ts +18 -13
  228. package/src/modules/warranty_claims/api/receiving/route.ts +22 -15
  229. package/src/modules/warranty_claims/api/registrations/route.ts +9 -3
  230. package/src/modules/warranty_claims/api/replacement-order/route.ts +19 -13
  231. package/src/modules/warranty_claims/api/return-label/route.ts +26 -17
  232. package/src/modules/warranty_claims/api/risk/route.ts +3 -1
  233. package/src/modules/warranty_claims/api/sales-return/route.ts +19 -13
  234. package/src/modules/warranty_claims/api/settings-general/route.ts +5 -0
  235. package/src/modules/warranty_claims/api/submit/route.ts +5 -0
  236. package/src/modules/warranty_claims/api/transition/route.ts +5 -0
  237. package/src/modules/warranty_claims/api/troubleshooting-guides/route.ts +23 -1
  238. package/src/modules/warranty_claims/api/vendor-recovery/route.ts +5 -0
  239. package/src/modules/warranty_claims/api/vendor-recovery-suggestions/route.ts +3 -1
  240. package/src/modules/warranty_claims/commands/shared.ts +7 -1
  241. package/src/modules/warranty_claims/lib/portalAuthGuard.ts +30 -0
  242. package/src/modules/wms/api/sales-orders/[salesOrderId]/warehouse-assignment/route.ts +5 -0
@@ -2,6 +2,7 @@ import { NextResponse } from 'next/server'
2
2
  import { z } from 'zod'
3
3
  import type { CommandBus } from '@open-mercato/shared/lib/commands'
4
4
  import { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'
5
+ import { getCommandInterceptorHttpRejection } from '@open-mercato/shared/lib/commands/errors'
5
6
  import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
6
7
  import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
7
8
  import type { TransitionClaimInput, WarrantyClaimStatus } from '../../../../../data/validators'
@@ -23,16 +24,16 @@ export async function POST(req: Request, ctx: PortalClaimActionRouteContext) {
23
24
  const contextOrResponse = await resolvePortalActionContext(req)
24
25
  if (contextOrResponse instanceof Response) return contextOrResponse
25
26
  const context = contextOrResponse
27
+ const { translate } = await resolveTranslations()
26
28
  const claimId = await resolvePortalClaimId(ctx)
27
29
  if (!claimId) {
28
- return NextResponse.json({ ok: false, error: 'warranty_claims.errors.notFound' }, { status: 404 })
30
+ return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.notFound', 'Claim not found.') }, { status: 404 })
29
31
  }
30
32
  const claim = await loadOwnedClaim(context, claimId)
31
33
  if (!claim) {
32
- return NextResponse.json({ ok: false, error: 'warranty_claims.errors.notFound' }, { status: 404 })
34
+ return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.notFound', 'Claim not found.') }, { status: 404 })
33
35
  }
34
36
  if (!PORTAL_WITHDRAWABLE_STATUSES.has(claim.status)) {
35
- const { translate } = await resolveTranslations()
36
37
  return NextResponse.json(
37
38
  { ok: false, error: translate('warranty_claims.errors.invalidTransition', 'That status change is not allowed.') },
38
39
  { status: 400 },
@@ -61,6 +62,10 @@ export async function POST(req: Request, ctx: PortalClaimActionRouteContext) {
61
62
  if (isCrudHttpError(err)) {
62
63
  return NextResponse.json(err.body, { status: err.status })
63
64
  }
65
+ const interceptorRejection = getCommandInterceptorHttpRejection(err)
66
+ if (interceptorRejection) {
67
+ return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status })
68
+ }
64
69
  throw err
65
70
  }
66
71
 
@@ -5,12 +5,14 @@ import type { AuthContext } from '@open-mercato/shared/lib/auth/server'
5
5
  import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
6
6
  import type { CommandBus, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'
7
7
  import { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'
8
+ import { getCommandInterceptorHttpRejection } from '@open-mercato/shared/lib/commands/errors'
8
9
  import { escapeLikePattern } from '@open-mercato/shared/lib/db/escapeLikePattern'
9
10
  import { runRouteMutationGuards, type RouteMutationGuardResult } from '@open-mercato/shared/lib/crud/route-mutation-guard'
10
11
  import { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
11
12
  import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
12
13
  import { createLogger } from '@open-mercato/shared/lib/logger'
13
- import { getCustomerAuthFromRequest, type CustomerAuthContext } from '@open-mercato/core/modules/customer_accounts/lib/customerAuth'
14
+ import type { CustomerAuthContext } from '@open-mercato/core/modules/customer_accounts/lib/customerAuth'
15
+ import { resolveLinkedCustomerAuth } from '../../../lib/portalAuthGuard'
14
16
  import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
15
17
  import { WarrantyClaim, WarrantyClaimLine } from '../../../data/entities'
16
18
  import {
@@ -153,14 +155,9 @@ function serializePortalClaim(claim: WarrantyClaim, lines: WarrantyClaimLine[])
153
155
  }
154
156
 
155
157
  async function resolvePortalContext(req: Request): Promise<PortalContext | Response> {
156
- const { translate } = await resolveTranslations()
157
- const auth = await getCustomerAuthFromRequest(req)
158
- if (!auth) {
159
- return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.unauthorized', 'Unauthorized') }, { status: 401 })
160
- }
161
- if (!auth.customerEntityId) {
162
- return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.customerAccountNotLinked', 'Customer account is not linked to a customer record') }, { status: 403 })
163
- }
158
+ const authOrResponse = await resolveLinkedCustomerAuth(req)
159
+ if (authOrResponse instanceof Response) return authOrResponse
160
+ const auth = authOrResponse
164
161
  const container = await createRequestContainer()
165
162
  const em = container.resolve('em') as EntityManager
166
163
  const commandAuth: NonNullable<AuthContext> = {
@@ -522,6 +519,10 @@ export async function POST(req: Request) {
522
519
  if (isCrudHttpError(err)) {
523
520
  return NextResponse.json(err.body, { status: err.status })
524
521
  }
522
+ const interceptorRejection = getCommandInterceptorHttpRejection(err)
523
+ if (interceptorRejection) {
524
+ return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status })
525
+ }
525
526
  throw err
526
527
  }
527
528
  }
@@ -5,14 +5,17 @@ import type { AuthContext } from '@open-mercato/shared/lib/auth/server'
5
5
  import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
6
6
  import type { CommandBus, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'
7
7
  import { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'
8
+ import { getCommandInterceptorHttpRejection } from '@open-mercato/shared/lib/commands/errors'
8
9
  import { runRouteMutationGuards, type RouteMutationGuardResult } from '@open-mercato/shared/lib/crud/route-mutation-guard'
10
+ import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
9
11
  import { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
10
12
  import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
11
- import { getCustomerAuthFromRequest, type CustomerAuthContext } from '@open-mercato/core/modules/customer_accounts/lib/customerAuth'
13
+ import type { CustomerAuthContext } from '@open-mercato/core/modules/customer_accounts/lib/customerAuth'
12
14
  import { WarrantyClaim, WarrantyClaimEvent } from '../../../data/entities'
13
15
  import type { CommentClaimInput } from '../../../data/validators'
14
16
  import { WARRANTY_CLAIM_RESOURCE_KIND } from '../../../commands/shared'
15
17
  import { loadPortalOwnedClaim } from '../../../lib/portalClaimAccess'
18
+ import { resolveLinkedCustomerAuth } from '../../../lib/portalAuthGuard'
16
19
 
17
20
  export const metadata = {
18
21
  GET: { requireAuth: false },
@@ -70,13 +73,9 @@ function serializeEvent(event: WarrantyClaimEvent) {
70
73
  }
71
74
 
72
75
  async function resolvePortalContext(req: Request): Promise<PortalContext | Response> {
73
- const auth = await getCustomerAuthFromRequest(req)
74
- if (!auth) {
75
- return NextResponse.json({ ok: false, error: 'warranty_claims.errors.unauthorized' }, { status: 401 })
76
- }
77
- if (!auth.customerEntityId) {
78
- return NextResponse.json({ ok: false, error: 'warranty_claims.errors.customerAccountNotLinked' }, { status: 403 })
79
- }
76
+ const authOrResponse = await resolveLinkedCustomerAuth(req)
77
+ if (authOrResponse instanceof Response) return authOrResponse
78
+ const auth = authOrResponse
80
79
  const container = await createRequestContainer()
81
80
  const em = container.resolve('em') as EntityManager
82
81
  const commandAuth: NonNullable<AuthContext> = {
@@ -145,14 +144,15 @@ export async function GET(req: Request) {
145
144
  const contextOrResponse = await resolvePortalContext(req)
146
145
  if (contextOrResponse instanceof Response) return contextOrResponse
147
146
  const context = contextOrResponse
147
+ const { translate } = await resolveTranslations()
148
148
  const url = new URL(req.url)
149
149
  const parsed = eventQuerySchema.safeParse({ claimId: url.searchParams.get('claimId') ?? undefined })
150
150
  if (!parsed.success) {
151
- return NextResponse.json({ ok: false, error: 'warranty_claims.errors.invalidInput' }, { status: 400 })
151
+ return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.invalidInput', 'Invalid input') }, { status: 400 })
152
152
  }
153
153
  const claim = await loadOwnedClaim(context, parsed.data.claimId)
154
154
  if (!claim) {
155
- return NextResponse.json({ ok: false, error: 'warranty_claims.errors.notFound' }, { status: 404 })
155
+ return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.notFound', 'Claim not found.') }, { status: 404 })
156
156
  }
157
157
  const events = await findWithDecryption(
158
158
  context.em,
@@ -168,19 +168,20 @@ export async function POST(req: Request) {
168
168
  const contextOrResponse = await resolvePortalContext(req)
169
169
  if (contextOrResponse instanceof Response) return contextOrResponse
170
170
  const context = contextOrResponse
171
+ const { translate } = await resolveTranslations()
171
172
  let body: unknown
172
173
  try {
173
174
  body = await req.json()
174
175
  } catch {
175
- return NextResponse.json({ ok: false, error: 'warranty_claims.errors.invalidInput' }, { status: 400 })
176
+ return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.invalidInput', 'Invalid input') }, { status: 400 })
176
177
  }
177
178
  const parsed = portalCommentSchema.safeParse(body)
178
179
  if (!parsed.success) {
179
- return NextResponse.json({ ok: false, error: 'warranty_claims.errors.invalidInput' }, { status: 400 })
180
+ return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.invalidInput', 'Invalid input') }, { status: 400 })
180
181
  }
181
182
  const claim = await loadOwnedClaim(context, parsed.data.claimId)
182
183
  if (!claim) {
183
- return NextResponse.json({ ok: false, error: 'warranty_claims.errors.notFound' }, { status: 404 })
184
+ return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.notFound', 'Claim not found.') }, { status: 404 })
184
185
  }
185
186
  const input: CommentClaimInput = {
186
187
  claimId: claim.id,
@@ -213,6 +214,10 @@ export async function POST(req: Request) {
213
214
  if (isCrudHttpError(err)) {
214
215
  return NextResponse.json(err.body, { status: err.status })
215
216
  }
217
+ const interceptorRejection = getCommandInterceptorHttpRejection(err)
218
+ if (interceptorRejection) {
219
+ return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status })
220
+ }
216
221
  throw err
217
222
  }
218
223
 
@@ -5,7 +5,9 @@ import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
5
5
  import { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'
6
6
  import type { CommandBus, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'
7
7
  import { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'
8
+ import { getCommandInterceptorHttpRejection } from '@open-mercato/shared/lib/commands/errors'
8
9
  import { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'
10
+ import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
9
11
  import { runRouteMutationGuards, type RouteMutationGuardResult } from '@open-mercato/shared/lib/crud/route-mutation-guard'
10
12
  import { withScopedPayload } from '@open-mercato/shared/lib/api/scoped'
11
13
  import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
@@ -62,20 +64,19 @@ function toRecord(value: unknown): Record<string, unknown> {
62
64
  return value && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : {}
63
65
  }
64
66
 
65
- function translateKey(key: string): string {
66
- return key
67
- }
68
-
69
- async function resolveActionContext(req: Request): Promise<ActionRouteContext> {
67
+ async function resolveActionContext(
68
+ req: Request,
69
+ translate: ActionRouteContext['translate'],
70
+ ): Promise<ActionRouteContext> {
70
71
  const container = await createRequestContainer()
71
72
  const auth = await getAuthFromRequest(req)
72
73
  if (!auth || !auth.tenantId) {
73
- throw new CrudHttpError(401, { error: 'warranty_claims.errors.unauthorized' })
74
+ throw new CrudHttpError(401, { error: translate('warranty_claims.errors.unauthorized', 'Unauthorized') })
74
75
  }
75
76
  const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })
76
77
  const organizationId = scope?.selectedId ?? auth.orgId ?? null
77
78
  if (!organizationId) {
78
- throw new CrudHttpError(400, { error: 'warranty_claims.errors.organization_required' })
79
+ throw new CrudHttpError(400, { error: translate('warranty_claims.errors.organization_required', 'Organization context is required.') })
79
80
  }
80
81
  return {
81
82
  ctx: {
@@ -88,7 +89,7 @@ async function resolveActionContext(req: Request): Promise<ActionRouteContext> {
88
89
  },
89
90
  tenantId: auth.tenantId,
90
91
  organizationId,
91
- translate: translateKey,
92
+ translate,
92
93
  }
93
94
  }
94
95
 
@@ -125,7 +126,7 @@ async function runGuard(
125
126
  ): Promise<RouteMutationGuardResult> {
126
127
  const userId = context.ctx.auth?.sub
127
128
  if (!userId) {
128
- throw new CrudHttpError(401, { error: 'warranty_claims.errors.unauthorized' })
129
+ throw new CrudHttpError(401, { error: context.translate('warranty_claims.errors.unauthorized', 'Unauthorized') })
129
130
  }
130
131
  return runRouteMutationGuards({
131
132
  container: context.ctx.container,
@@ -148,26 +149,28 @@ async function executeReceivingAction(
148
149
  commandBus: CommandBus,
149
150
  action: ReceivingAction,
150
151
  ctx: CommandRuntimeContext,
152
+ translate: ActionRouteContext['translate'],
151
153
  ): Promise<{ lineId: string; claimId: string }> {
152
154
  if (action.kind === 'release') {
153
155
  const { result } = await commandBus.execute<ClaimLineReleaseQuarantineInput, { lineId: string; claimId: string }>(
154
156
  'warranty_claims.claim_line.release_quarantine',
155
157
  { input: action.input, ctx },
156
158
  )
157
- if (!result) throw new CrudHttpError(400, { error: 'warranty_claims.errors.save_failed' })
159
+ if (!result) throw new CrudHttpError(400, { error: translate('warranty_claims.errors.save_failed', 'Failed to save warranty claim.') })
158
160
  return result
159
161
  }
160
162
  const { result } = await commandBus.execute<ClaimLineReceiveInput, { lineId: string; claimId: string }>(
161
163
  'warranty_claims.claim_line.receive',
162
164
  { input: action.input, ctx },
163
165
  )
164
- if (!result) throw new CrudHttpError(400, { error: 'warranty_claims.errors.save_failed' })
166
+ if (!result) throw new CrudHttpError(400, { error: translate('warranty_claims.errors.save_failed', 'Failed to save warranty claim.') })
165
167
  return result
166
168
  }
167
169
 
168
170
  export async function POST(req: Request) {
171
+ const { translate } = await resolveTranslations()
169
172
  try {
170
- const context = await resolveActionContext(req)
173
+ const context = await resolveActionContext(req, translate)
171
174
  const payload = toRecord(await readJsonSafe(req, {}))
172
175
  const action = toReceivingAction(payload, context)
173
176
  const guarded = await runGuard(req, context, action)
@@ -177,18 +180,22 @@ export async function POST(req: Request) {
177
180
  const commandAction = guarded.modifiedPayload ? toGuardedAction(action.kind, guarded.modifiedPayload) : action
178
181
 
179
182
  const commandBus = context.ctx.container.resolve('commandBus') as CommandBus
180
- const result = await executeReceivingAction(commandBus, commandAction, context.ctx)
183
+ const result = await executeReceivingAction(commandBus, commandAction, context.ctx, translate)
181
184
 
182
185
  await guarded.runAfterSuccess()
183
186
 
184
187
  return NextResponse.json({ ok: true, lineId: result.lineId, claimId: result.claimId })
185
188
  } catch (err) {
186
189
  if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status })
190
+ const interceptorRejection = getCommandInterceptorHttpRejection(err)
191
+ if (interceptorRejection) {
192
+ return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status })
193
+ }
187
194
  if (err instanceof z.ZodError) {
188
- return NextResponse.json({ error: 'warranty_claims.errors.invalidInput' }, { status: 400 })
195
+ return NextResponse.json({ error: translate('warranty_claims.errors.invalidInput', 'Invalid input') }, { status: 400 })
189
196
  }
190
197
  logger.error('warranty_claims.receiving.post failed', { err })
191
- return NextResponse.json({ error: 'warranty_claims.errors.save_failed' }, { status: 500 })
198
+ return NextResponse.json({ error: translate('warranty_claims.errors.save_failed', 'Failed to save warranty claim.') }, { status: 500 })
192
199
  }
193
200
  }
194
201
 
@@ -5,6 +5,7 @@ import { makeCrudRoute } from '@open-mercato/shared/lib/crud/factory'
5
5
  import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
6
6
  import { buildIlikeTerm } from '@open-mercato/shared/lib/db/buildIlikeTerm'
7
7
  import { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
8
+ import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
8
9
  import { E } from '#generated/entities.ids.generated'
9
10
  import * as F from '#generated/entities/warranty_claim_registration'
10
11
  import { WarrantyClaimRegistration } from '../../data/entities'
@@ -92,6 +93,7 @@ async function assertRegistrationSerialUnique(
92
93
  scope: { organizationId: string; tenantId: string },
93
94
  serialNumber: string | null | undefined,
94
95
  excludeId: string | null,
96
+ translate: (key: string, fallback?: string) => string,
95
97
  ): Promise<void> {
96
98
  if (!serialNumber) return
97
99
  const where: FilterQuery<WarrantyClaimRegistration> = {
@@ -103,7 +105,9 @@ async function assertRegistrationSerialUnique(
103
105
  if (excludeId) where.id = { $ne: excludeId }
104
106
  const existing = await em.count(WarrantyClaimRegistration, where)
105
107
  if (existing > 0) {
106
- throw new CrudHttpError(400, { error: 'warranty_claims.errors.duplicateSerial' })
108
+ throw new CrudHttpError(400, {
109
+ error: translate('warranty_claims.errors.duplicateSerial', 'A registration with this serial number already exists.'),
110
+ })
107
111
  }
108
112
  }
109
113
 
@@ -320,8 +324,9 @@ const crud = makeCrudRoute<RawRegistrationInput, RawRegistrationInput, Registrat
320
324
  const result = registrationCreateSchema.safeParse({ ...input, ...scope })
321
325
  if (!result.success) return
322
326
  const em = (ctx.container.resolve('em') as EntityManager).fork()
327
+ const { translate } = await resolveTranslations()
323
328
  await assertOrderBelongsToCustomer(em, scope, result.data.orderId ?? null, result.data.customerId ?? null)
324
- await assertRegistrationSerialUnique(em, scope, result.data.serialNumber ?? null, null)
329
+ await assertRegistrationSerialUnique(em, scope, result.data.serialNumber ?? null, null, translate)
325
330
  },
326
331
  beforeUpdate: async (input, ctx) => {
327
332
  const scope = scopeFromContext(ctx)
@@ -346,7 +351,8 @@ const crud = makeCrudRoute<RawRegistrationInput, RawRegistrationInput, Registrat
346
351
  const effectiveOrderId = hasOwn(parsed, 'orderId') ? (parsed.orderId ?? null) : (existing.orderId ?? null)
347
352
  await assertOrderBelongsToCustomer(em, scope, effectiveOrderId, effectiveCustomerId)
348
353
  if (hasOwn(parsed, 'serialNumber') && (parsed.serialNumber ?? null) !== (existing.serialNumber ?? null)) {
349
- await assertRegistrationSerialUnique(em, scope, parsed.serialNumber ?? null, existing.id)
354
+ const { translate } = await resolveTranslations()
355
+ await assertRegistrationSerialUnique(em, scope, parsed.serialNumber ?? null, existing.id, translate)
350
356
  }
351
357
  },
352
358
  afterCreate: async (entity) => {
@@ -5,7 +5,9 @@ import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
5
5
  import { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'
6
6
  import type { CommandBus, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'
7
7
  import { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'
8
+ import { getCommandInterceptorHttpRejection } from '@open-mercato/shared/lib/commands/errors'
8
9
  import { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'
10
+ import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
9
11
  import { runRouteMutationGuards, type RouteMutationGuardResult } from '@open-mercato/shared/lib/crud/route-mutation-guard'
10
12
  import { withScopedPayload } from '@open-mercato/shared/lib/api/scoped'
11
13
  import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
@@ -56,24 +58,23 @@ export const metadata = {
56
58
  POST: { requireAuth: true, requireFeatures: ['warranty_claims.claim.manage', 'sales.orders.manage'] },
57
59
  }
58
60
 
59
- function translateKey(key: string): string {
60
- return key
61
- }
62
-
63
61
  function toRecord(value: unknown): Record<string, unknown> {
64
62
  return value && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : {}
65
63
  }
66
64
 
67
- async function resolveActionContext(req: Request): Promise<ActionRouteContext> {
65
+ async function resolveActionContext(
66
+ req: Request,
67
+ translate: ActionRouteContext['translate'],
68
+ ): Promise<ActionRouteContext> {
68
69
  const container = await createRequestContainer()
69
70
  const auth = await getAuthFromRequest(req)
70
71
  if (!auth || !auth.tenantId) {
71
- throw new CrudHttpError(401, { error: 'warranty_claims.errors.unauthorized' })
72
+ throw new CrudHttpError(401, { error: translate('warranty_claims.errors.unauthorized', 'Unauthorized') })
72
73
  }
73
74
  const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })
74
75
  const organizationId = scope?.selectedId ?? auth.orgId ?? null
75
76
  if (!organizationId) {
76
- throw new CrudHttpError(400, { error: 'warranty_claims.errors.organization_required' })
77
+ throw new CrudHttpError(400, { error: translate('warranty_claims.errors.organization_required', 'Organization context is required.') })
77
78
  }
78
79
  return {
79
80
  ctx: {
@@ -86,7 +87,7 @@ async function resolveActionContext(req: Request): Promise<ActionRouteContext> {
86
87
  },
87
88
  tenantId: auth.tenantId,
88
89
  organizationId,
89
- translate: translateKey,
90
+ translate,
90
91
  }
91
92
  }
92
93
 
@@ -113,7 +114,7 @@ async function runGuard(
113
114
  ): Promise<RouteMutationGuardResult> {
114
115
  const userId = context.ctx.auth?.sub
115
116
  if (!userId) {
116
- throw new CrudHttpError(401, { error: 'warranty_claims.errors.unauthorized' })
117
+ throw new CrudHttpError(401, { error: context.translate('warranty_claims.errors.unauthorized', 'Unauthorized') })
117
118
  }
118
119
  return runRouteMutationGuards({
119
120
  container: context.ctx.container,
@@ -133,8 +134,9 @@ async function runGuard(
133
134
  }
134
135
 
135
136
  export async function POST(req: Request) {
137
+ const { translate } = await resolveTranslations()
136
138
  try {
137
- const context = await resolveActionContext(req)
139
+ const context = await resolveActionContext(req, translate)
138
140
  const requestInput = toReplacementOrderRequest(toRecord(await readJsonSafe(req, {})))
139
141
  const guarded = await runGuard(req, context, requestInput)
140
142
  if (!guarded.ok) {
@@ -148,7 +150,7 @@ export async function POST(req: Request) {
148
150
  'warranty_claims.claim.create_replacement_order',
149
151
  { input: toCommandInput(guardedInput, context), ctx: context.ctx },
150
152
  )
151
- if (!result) throw new CrudHttpError(400, { error: 'warranty_claims.errors.save_failed' })
153
+ if (!result) throw new CrudHttpError(400, { error: translate('warranty_claims.errors.save_failed', 'Failed to save warranty claim.') })
152
154
  await guarded.runAfterSuccess()
153
155
  return NextResponse.json({
154
156
  replacementOrderId: result.replacementOrderId,
@@ -156,11 +158,15 @@ export async function POST(req: Request) {
156
158
  })
157
159
  } catch (err) {
158
160
  if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status })
161
+ const interceptorRejection = getCommandInterceptorHttpRejection(err)
162
+ if (interceptorRejection) {
163
+ return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status })
164
+ }
159
165
  if (err instanceof z.ZodError) {
160
- return NextResponse.json({ error: 'warranty_claims.errors.invalidInput' }, { status: 400 })
166
+ return NextResponse.json({ error: translate('warranty_claims.errors.invalidInput', 'Invalid input') }, { status: 400 })
161
167
  }
162
168
  logger.error('warranty_claims.replacement-order.post failed', { err })
163
- return NextResponse.json({ error: 'warranty_claims.errors.save_failed' }, { status: 500 })
169
+ return NextResponse.json({ error: translate('warranty_claims.errors.save_failed', 'Failed to save warranty claim.') }, { status: 500 })
164
170
  }
165
171
  }
166
172
 
@@ -6,7 +6,9 @@ import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
6
6
  import { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'
7
7
  import type { CommandBus, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'
8
8
  import { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'
9
+ import { getCommandInterceptorHttpRejection } from '@open-mercato/shared/lib/commands/errors'
9
10
  import { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'
11
+ import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
10
12
  import { runRouteMutationGuards, type RouteMutationGuardResult } from '@open-mercato/shared/lib/crud/route-mutation-guard'
11
13
  import { withScopedPayload } from '@open-mercato/shared/lib/api/scoped'
12
14
  import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
@@ -69,24 +71,23 @@ export const metadata = {
69
71
  POST: { requireAuth: true, requireFeatures: ['warranty_claims.claim.manage'] },
70
72
  }
71
73
 
72
- function translateKey(key: string): string {
73
- return key
74
- }
75
-
76
74
  function toRecord(value: unknown): Record<string, unknown> {
77
75
  return value && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : {}
78
76
  }
79
77
 
80
- async function resolveActionContext(req: Request): Promise<ActionRouteContext> {
78
+ async function resolveActionContext(
79
+ req: Request,
80
+ translate: ActionRouteContext['translate'],
81
+ ): Promise<ActionRouteContext> {
81
82
  const container = await createRequestContainer()
82
83
  const auth = await getAuthFromRequest(req)
83
84
  if (!auth || !auth.tenantId) {
84
- throw new CrudHttpError(401, { error: 'warranty_claims.errors.unauthorized' })
85
+ throw new CrudHttpError(401, { error: translate('warranty_claims.errors.unauthorized', 'Unauthorized') })
85
86
  }
86
87
  const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })
87
88
  const organizationId = scope?.selectedId ?? auth.orgId ?? null
88
89
  if (!organizationId) {
89
- throw new CrudHttpError(400, { error: 'warranty_claims.errors.organization_required' })
90
+ throw new CrudHttpError(400, { error: translate('warranty_claims.errors.organization_required', 'Organization context is required.') })
90
91
  }
91
92
  return {
92
93
  ctx: {
@@ -99,7 +100,7 @@ async function resolveActionContext(req: Request): Promise<ActionRouteContext> {
99
100
  },
100
101
  tenantId: auth.tenantId,
101
102
  organizationId,
102
- translate: translateKey,
103
+ translate,
103
104
  }
104
105
  }
105
106
 
@@ -142,7 +143,7 @@ async function runGuard(
142
143
  ): Promise<RouteMutationGuardResult> {
143
144
  const userId = context.ctx.auth?.sub
144
145
  if (!userId) {
145
- throw new CrudHttpError(401, { error: 'warranty_claims.errors.unauthorized' })
146
+ throw new CrudHttpError(401, { error: context.translate('warranty_claims.errors.unauthorized', 'Unauthorized') })
146
147
  }
147
148
  return runRouteMutationGuards({
148
149
  container: context.ctx.container,
@@ -167,7 +168,7 @@ async function loadClaimAndLines(
167
168
  ): Promise<{ claim: WarrantyClaim; lines: WarrantyClaimLine[] }> {
168
169
  const scope = { tenantId: context.tenantId, organizationId: context.organizationId }
169
170
  const em = (context.ctx.container.resolve('em') as EntityManager).fork()
170
- const claim = await requireScopedClaim(em, claimId, scope)
171
+ const claim = await requireScopedClaim(em, claimId, scope, {}, context.translate('warranty_claims.errors.notFound', 'Claim not found.'))
171
172
  const lines = await findWithDecryption(
172
173
  em,
173
174
  WarrantyClaimLine,
@@ -178,9 +179,9 @@ async function loadClaimAndLines(
178
179
  return { claim, lines }
179
180
  }
180
181
 
181
- function assertGeneratableStatus(claim: WarrantyClaim): void {
182
+ function assertGeneratableStatus(claim: WarrantyClaim, translate: ActionRouteContext['translate']): void {
182
183
  if (claim.status !== 'approved' && claim.status !== 'awaiting_return') {
183
- throw new CrudHttpError(400, { error: 'warranty_claims.errors.returnLabelInvalidStatus' })
184
+ throw new CrudHttpError(400, { error: translate('warranty_claims.errors.returnLabelInvalidStatus', 'Return labels can only be created for approved claims that are awaiting return.') })
184
185
  }
185
186
  }
186
187
 
@@ -188,12 +189,13 @@ async function executeSetReturnLabel(
188
189
  commandBus: CommandBus,
189
190
  input: ClaimSetReturnLabelInput,
190
191
  ctx: CommandRuntimeContext,
192
+ translate: ActionRouteContext['translate'],
191
193
  ): Promise<ReturnLabelCreatedResponse> {
192
194
  const { result } = await commandBus.execute<ClaimSetReturnLabelInput, ReturnLabelCommandResult>(
193
195
  'warranty_claims.claim.set_return_label',
194
196
  { input, ctx },
195
197
  )
196
- if (!result) throw new CrudHttpError(400, { error: 'warranty_claims.errors.save_failed' })
198
+ if (!result) throw new CrudHttpError(400, { error: translate('warranty_claims.errors.save_failed', 'Failed to save warranty claim.') })
197
199
  return {
198
200
  status: 'created',
199
201
  labelUrl: result.labelUrl,
@@ -210,8 +212,9 @@ export function firstTranslatableIssueKey(err: z.ZodError): string {
210
212
  }
211
213
 
212
214
  export async function POST(req: Request) {
215
+ const { translate } = await resolveTranslations()
213
216
  try {
214
- const context = await resolveActionContext(req)
217
+ const context = await resolveActionContext(req, translate)
215
218
  const requestInput = toReturnLabelRequest(toRecord(await readJsonSafe(req, {})))
216
219
  const guarded = await runGuard(req, context, requestInput)
217
220
  if (!guarded.ok) {
@@ -225,13 +228,14 @@ export async function POST(req: Request) {
225
228
  commandBus,
226
229
  toSetReturnLabelInput(guardedInput, context),
227
230
  context.ctx,
231
+ translate,
228
232
  )
229
233
  await guarded.runAfterSuccess()
230
234
  return NextResponse.json(response)
231
235
  }
232
236
 
233
237
  const { claim, lines } = await loadClaimAndLines(context, guardedInput.claimId)
234
- assertGeneratableStatus(claim)
238
+ assertGeneratableStatus(claim, translate)
235
239
  await enforceWarrantyClaimOptimisticLock(
236
240
  context.ctx,
237
241
  claim,
@@ -259,16 +263,21 @@ export async function POST(req: Request) {
259
263
  carrier: result.carrier,
260
264
  }, context),
261
265
  context.ctx,
266
+ translate,
262
267
  )
263
268
  await guarded.runAfterSuccess()
264
269
  return NextResponse.json(response)
265
270
  } catch (err) {
266
271
  if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status })
272
+ const interceptorRejection = getCommandInterceptorHttpRejection(err)
273
+ if (interceptorRejection) {
274
+ return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status })
275
+ }
267
276
  if (err instanceof z.ZodError) {
268
- return NextResponse.json({ error: firstTranslatableIssueKey(err) }, { status: 400 })
277
+ return NextResponse.json({ error: translate(firstTranslatableIssueKey(err), 'Invalid input') }, { status: 400 })
269
278
  }
270
279
  logger.error('warranty_claims.return-label.post failed', { err })
271
- return NextResponse.json({ error: 'warranty_claims.errors.save_failed' }, { status: 500 })
280
+ return NextResponse.json({ error: translate('warranty_claims.errors.save_failed', 'Failed to save warranty claim.') }, { status: 500 })
272
281
  }
273
282
  }
274
283
 
@@ -39,6 +39,7 @@ type RiskRouteContext = {
39
39
  tenantId: string
40
40
  organizationId: string
41
41
  scope: WarrantyClaimScope
42
+ translate: (key: string, fallback?: string) => string
42
43
  em: EntityManager
43
44
  }
44
45
 
@@ -63,6 +64,7 @@ async function resolveRiskContext(req: Request): Promise<RiskRouteContext> {
63
64
  tenantId: auth.tenantId,
64
65
  organizationId,
65
66
  scope: { tenantId: auth.tenantId, organizationId },
67
+ translate,
66
68
  em,
67
69
  }
68
70
  }
@@ -72,7 +74,7 @@ export async function GET(req: Request) {
72
74
  const context = await resolveRiskContext(req)
73
75
  const url = new URL(req.url)
74
76
  const query = querySchema.parse(Object.fromEntries(url.searchParams))
75
- const claim = await requireScopedClaim(context.em, query.claimId, context.scope)
77
+ const claim = await requireScopedClaim(context.em, query.claimId, context.scope, {}, context.translate('warranty_claims.errors.notFound', 'Claim not found.'))
76
78
  const lines = await findWithDecryption(
77
79
  context.em,
78
80
  WarrantyClaimLine,