@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
@@ -13,6 +13,7 @@ import { serializeOperationMetadata } from '@open-mercato/shared/lib/commands/op
13
13
  import { enforceCommandOptimisticLockWithGuards } from '@open-mercato/shared/lib/crud/optimistic-lock-command'
14
14
  import type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
15
15
  import { createLogger } from '@open-mercato/shared/lib/logger'
16
+ import { getCommandInterceptorHttpRejection } from '@open-mercato/shared/lib/commands/errors'
16
17
 
17
18
  const logger = createLogger('translations').child({ component: 'entity-translations' })
18
19
 
@@ -226,6 +227,10 @@ export async function PUT(req: Request, ctx: { params?: { entityType?: string; e
226
227
  if (isCrudHttpError(err)) {
227
228
  return NextResponse.json(err.body, { status: err.status })
228
229
  }
230
+ const interceptorRejection = getCommandInterceptorHttpRejection(err)
231
+ if (interceptorRejection) {
232
+ return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status })
233
+ }
229
234
  if (err instanceof z.ZodError) {
230
235
  return NextResponse.json({ error: 'Validation failed', details: err.issues }, { status: 400 })
231
236
  }
@@ -331,6 +336,10 @@ export async function DELETE(req: Request, ctx: { params?: { entityType?: string
331
336
  if (isCrudHttpError(err)) {
332
337
  return NextResponse.json(err.body, { status: err.status })
333
338
  }
339
+ const interceptorRejection = getCommandInterceptorHttpRejection(err)
340
+ if (interceptorRejection) {
341
+ return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status })
342
+ }
334
343
  if (err instanceof z.ZodError) {
335
344
  return NextResponse.json({ error: 'Invalid parameters', details: err.issues }, { status: 400 })
336
345
  }
@@ -7,7 +7,9 @@ import { getAuthFromRequest, type AuthContext } from '@open-mercato/shared/lib/a
7
7
  import { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'
8
8
  import type { CommandBus, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'
9
9
  import { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'
10
+ import { getCommandInterceptorHttpRejection } from '@open-mercato/shared/lib/commands/errors'
10
11
  import { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'
12
+ import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
11
13
  import { runRouteMutationGuards, type RouteMutationGuardResult } from '@open-mercato/shared/lib/crud/route-mutation-guard'
12
14
  import { withScopedPayload } from '@open-mercato/shared/lib/api/scoped'
13
15
  import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
@@ -109,7 +111,7 @@ function isSuperAdmin(auth: CommandRuntimeContext['auth']): boolean {
109
111
  function buildAuthContext(context: AssessRouteContext): AiChatRequestContext {
110
112
  const userId = context.ctx.auth?.sub
111
113
  if (!userId) {
112
- throw new CrudHttpError(401, { error: 'warranty_claims.errors.unauthorized' })
114
+ throw new CrudHttpError(401, { error: context.translate('warranty_claims.errors.unauthorized', 'Unauthorized') })
113
115
  }
114
116
  return {
115
117
  tenantId: context.tenantId,
@@ -123,7 +125,7 @@ function buildAuthContext(context: AssessRouteContext): AiChatRequestContext {
123
125
  function buildAttachmentAuthContext(context: AssessRouteContext): Exclude<AuthContext, null> {
124
126
  const auth = context.ctx.auth
125
127
  if (!auth?.sub) {
126
- throw new CrudHttpError(401, { error: 'warranty_claims.errors.unauthorized' })
128
+ throw new CrudHttpError(401, { error: context.translate('warranty_claims.errors.unauthorized', 'Unauthorized') })
127
129
  }
128
130
  return {
129
131
  ...auth,
@@ -133,16 +135,19 @@ function buildAttachmentAuthContext(context: AssessRouteContext): Exclude<AuthCo
133
135
  }
134
136
  }
135
137
 
136
- async function resolveAssessContext(req: Request): Promise<AssessRouteContext> {
138
+ async function resolveAssessContext(
139
+ req: Request,
140
+ translate: AssessRouteContext['translate'],
141
+ ): Promise<AssessRouteContext> {
137
142
  const container = await createRequestContainer()
138
143
  const auth = await getAuthFromRequest(req)
139
144
  if (!auth || !auth.tenantId) {
140
- throw new CrudHttpError(401, { error: 'warranty_claims.errors.unauthorized' })
145
+ throw new CrudHttpError(401, { error: translate('warranty_claims.errors.unauthorized', 'Unauthorized') })
141
146
  }
142
147
  const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })
143
148
  const organizationId = scope?.selectedId ?? auth.orgId ?? null
144
149
  if (!organizationId) {
145
- throw new CrudHttpError(400, { error: 'warranty_claims.errors.organization_required' })
150
+ throw new CrudHttpError(400, { error: translate('warranty_claims.errors.organization_required', 'Organization context is required.') })
146
151
  }
147
152
  return {
148
153
  ctx: {
@@ -157,7 +162,7 @@ async function resolveAssessContext(req: Request): Promise<AssessRouteContext> {
157
162
  organizationId,
158
163
  container,
159
164
  em: container.resolve<EntityManager>('em').fork(),
160
- translate: (key: string) => key,
165
+ translate,
161
166
  }
162
167
  }
163
168
 
@@ -172,7 +177,7 @@ async function runGuard(
172
177
  ): Promise<RouteMutationGuardResult> {
173
178
  const userId = context.ctx.auth?.sub
174
179
  if (!userId) {
175
- throw new CrudHttpError(401, { error: 'warranty_claims.errors.unauthorized' })
180
+ throw new CrudHttpError(401, { error: context.translate('warranty_claims.errors.unauthorized', 'Unauthorized') })
176
181
  }
177
182
  return runRouteMutationGuards({
178
183
  container: context.ctx.container,
@@ -196,6 +201,7 @@ async function loadScopedLine(
196
201
  scope: WarrantyClaimScope,
197
202
  claimId: string,
198
203
  lineId: string,
204
+ translate: AssessRouteContext['translate'],
199
205
  ): Promise<WarrantyClaimLine> {
200
206
  const line = await findOneWithDecryption(
201
207
  em,
@@ -211,7 +217,7 @@ async function loadScopedLine(
211
217
  scope,
212
218
  )
213
219
  if (!line) {
214
- throw new CrudHttpError(404, { error: 'warranty_claims.errors.notFound' })
220
+ throw new CrudHttpError(404, { error: translate('warranty_claims.errors.notFound', 'Claim not found.') })
215
221
  }
216
222
  return line
217
223
  }
@@ -237,7 +243,7 @@ async function verifyAttachmentLinkedToTarget(
237
243
  ],
238
244
  }) : false
239
245
  if (!linked) {
240
- throw new CrudHttpError(400, { error: 'warranty_claims.errors.attachmentNotLinked' })
246
+ throw new CrudHttpError(400, { error: context.translate('warranty_claims.errors.attachmentNotLinked', 'The attachment is not linked to this claim.') })
241
247
  }
242
248
  }
243
249
 
@@ -278,12 +284,13 @@ async function persistAssessmentPayload(
278
284
  }
279
285
 
280
286
  export async function POST(req: Request) {
287
+ const { translate } = await resolveTranslations()
281
288
  try {
282
- const context = await resolveAssessContext(req)
289
+ const context = await resolveAssessContext(req, translate)
283
290
  const payload = toRecord(await readJsonSafe(req, {}))
284
291
  const parsed = toAssessInput(payload, context)
285
292
  if (parsed.kind === 'damage' && !parsed.lineId) {
286
- throw new CrudHttpError(400, { error: 'warranty_claims.errors.invalidInput' })
293
+ throw new CrudHttpError(400, { error: translate('warranty_claims.errors.invalidInput', 'Invalid input') })
287
294
  }
288
295
 
289
296
  const guarded = parsed.lineId ? await runGuard(req, context, parsed as AssessBodyInput & { lineId: string }) : null
@@ -295,9 +302,9 @@ export async function POST(req: Request) {
295
302
 
296
303
  if (input.kind === 'damage') {
297
304
  if (!input.lineId) {
298
- throw new CrudHttpError(400, { error: 'warranty_claims.errors.invalidInput' })
305
+ throw new CrudHttpError(400, { error: translate('warranty_claims.errors.invalidInput', 'Invalid input') })
299
306
  }
300
- const line = await loadScopedLine(context.em, scope, input.claimId, input.lineId)
307
+ const line = await loadScopedLine(context.em, scope, input.claimId, input.lineId, translate)
301
308
  await verifyAttachmentLinkedToTarget(context, {
302
309
  attachmentId: input.attachmentId,
303
310
  claimId: input.claimId,
@@ -317,8 +324,8 @@ export async function POST(req: Request) {
317
324
  return NextResponse.json({ status: 'ok', assessment })
318
325
  }
319
326
 
320
- await requireScopedClaim(context.em, input.claimId, scope)
321
- const line = input.lineId ? await loadScopedLine(context.em, scope, input.claimId, input.lineId) : null
327
+ await requireScopedClaim(context.em, input.claimId, scope, {}, translate('warranty_claims.errors.notFound', 'Claim not found.'))
328
+ const line = input.lineId ? await loadScopedLine(context.em, scope, input.claimId, input.lineId, translate) : null
322
329
  await verifyAttachmentLinkedToTarget(context, {
323
330
  attachmentId: input.attachmentId,
324
331
  claimId: input.claimId,
@@ -338,6 +345,10 @@ export async function POST(req: Request) {
338
345
  return NextResponse.json({ status: 'ok', extraction })
339
346
  } catch (err) {
340
347
  if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status })
348
+ const interceptorRejection = getCommandInterceptorHttpRejection(err)
349
+ if (interceptorRejection) {
350
+ return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status })
351
+ }
341
352
  if (isWarrantyAiNotConfiguredError(err)) {
342
353
  return NextResponse.json({ status: 'notConfigured' })
343
354
  }
@@ -345,10 +356,10 @@ export async function POST(req: Request) {
345
356
  return NextResponse.json({ status: 'aiUnavailable' })
346
357
  }
347
358
  if (err instanceof z.ZodError) {
348
- return NextResponse.json({ error: 'warranty_claims.errors.invalidInput' }, { status: 400 })
359
+ return NextResponse.json({ error: translate('warranty_claims.errors.invalidInput', 'Invalid input') }, { status: 400 })
349
360
  }
350
361
  logger.error('warranty_claims.ai.assess.post failed', { err })
351
- return NextResponse.json({ error: 'warranty_claims.errors.save_failed' }, { status: 500 })
362
+ return NextResponse.json({ error: translate('warranty_claims.errors.save_failed', 'Failed to save warranty claim.') }, { status: 500 })
352
363
  }
353
364
  }
354
365
 
@@ -6,6 +6,7 @@ import { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/d
6
6
  import type { CommandBus, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'
7
7
  import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
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'
10
11
  import { runRouteMutationGuards, type RouteMutationGuardResult } from '@open-mercato/shared/lib/crud/route-mutation-guard'
11
12
  import { withScopedPayload } from '@open-mercato/shared/lib/api/scoped'
@@ -114,6 +115,10 @@ export async function POST(req: Request) {
114
115
  return NextResponse.json({ ok: true, claimId: result?.claimId ?? commandInput.id })
115
116
  } catch (err) {
116
117
  if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status })
118
+ const interceptorRejection = getCommandInterceptorHttpRejection(err)
119
+ if (interceptorRejection) {
120
+ return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status })
121
+ }
117
122
  const { translate } = await resolveTranslations()
118
123
  if (err instanceof z.ZodError) {
119
124
  return NextResponse.json({ error: translate('warranty_claims.errors.invalidInput', 'Invalid input') }, { status: 400 })
@@ -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'
@@ -58,24 +60,23 @@ export const metadata = {
58
60
  POST: { requireAuth: true, requireFeatures: ['warranty_claims.claim.manage', 'sales.credit_memos.manage'] },
59
61
  }
60
62
 
61
- function translateKey(key: string): string {
62
- return key
63
- }
64
-
65
63
  function toRecord(value: unknown): Record<string, unknown> {
66
64
  return value && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : {}
67
65
  }
68
66
 
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
 
@@ -114,7 +115,7 @@ async function runGuard(
114
115
  ): Promise<RouteMutationGuardResult> {
115
116
  const userId = context.ctx.auth?.sub
116
117
  if (!userId) {
117
- throw new CrudHttpError(401, { error: 'warranty_claims.errors.unauthorized' })
118
+ throw new CrudHttpError(401, { error: context.translate('warranty_claims.errors.unauthorized', 'Unauthorized') })
118
119
  }
119
120
  return runRouteMutationGuards({
120
121
  container: context.ctx.container,
@@ -134,8 +135,9 @@ async function runGuard(
134
135
  }
135
136
 
136
137
  export async function POST(req: Request) {
138
+ const { translate } = await resolveTranslations()
137
139
  try {
138
- const context = await resolveActionContext(req)
140
+ const context = await resolveActionContext(req, translate)
139
141
  const requestInput = toCreditMemoRequest(toRecord(await readJsonSafe(req, {})))
140
142
  const guarded = await runGuard(req, context, requestInput)
141
143
  if (!guarded.ok) {
@@ -149,7 +151,7 @@ export async function POST(req: Request) {
149
151
  'warranty_claims.claim.create_credit_memo',
150
152
  { input: toCommandInput(guardedInput, context), ctx: context.ctx },
151
153
  )
152
- if (!result) throw new CrudHttpError(400, { error: 'warranty_claims.errors.save_failed' })
154
+ if (!result) throw new CrudHttpError(400, { error: translate('warranty_claims.errors.save_failed', 'Failed to save warranty claim.') })
153
155
  await guarded.runAfterSuccess()
154
156
  return NextResponse.json({
155
157
  creditMemoId: result.creditMemoId,
@@ -159,11 +161,15 @@ export async function POST(req: Request) {
159
161
  })
160
162
  } catch (err) {
161
163
  if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status })
164
+ const interceptorRejection = getCommandInterceptorHttpRejection(err)
165
+ if (interceptorRejection) {
166
+ return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status })
167
+ }
162
168
  if (err instanceof z.ZodError) {
163
- return NextResponse.json({ error: 'warranty_claims.errors.invalidInput' }, { status: 400 })
169
+ return NextResponse.json({ error: translate('warranty_claims.errors.invalidInput', 'Invalid input') }, { status: 400 })
164
170
  }
165
171
  logger.error('warranty_claims.credit-memo.post failed', { err })
166
- return NextResponse.json({ error: 'warranty_claims.errors.save_failed' }, { status: 500 })
172
+ return NextResponse.json({ error: translate('warranty_claims.errors.save_failed', 'Failed to save warranty claim.') }, { status: 500 })
167
173
  }
168
174
  }
169
175
 
@@ -7,6 +7,7 @@ import { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/d
7
7
  import type { CommandBus, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'
8
8
  import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
9
9
  import { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'
10
+ import { getCommandInterceptorHttpRejection } from '@open-mercato/shared/lib/commands/errors'
10
11
  import { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'
11
12
  import { runRouteMutationGuards, type RouteMutationGuardResult } from '@open-mercato/shared/lib/crud/route-mutation-guard'
12
13
  import { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
@@ -130,7 +131,7 @@ export async function GET(req: Request) {
130
131
  const context = await resolveEventContext(req)
131
132
  const url = new URL(req.url)
132
133
  const query = eventListQuerySchema.parse({ claimId: url.searchParams.get('claimId') ?? undefined })
133
- const claim = await requireScopedClaim(context.em, query.claimId, context.scope)
134
+ const claim = await requireScopedClaim(context.em, query.claimId, context.scope, {}, context.translate('warranty_claims.errors.notFound', 'Claim not found.'))
134
135
  const events = await findWithDecryption(
135
136
  context.em,
136
137
  WarrantyClaimEvent,
@@ -175,6 +176,10 @@ export async function POST(req: Request) {
175
176
  return NextResponse.json({ ok: true, claimId: result?.claimId ?? commandInput.claimId })
176
177
  } catch (err) {
177
178
  if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status })
179
+ const interceptorRejection = getCommandInterceptorHttpRejection(err)
180
+ if (interceptorRejection) {
181
+ return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status })
182
+ }
178
183
  const { translate } = await resolveTranslations()
179
184
  if (err instanceof z.ZodError) {
180
185
  return NextResponse.json({ error: translate('warranty_claims.errors.invalidInput', 'Invalid input') }, { status: 400 })
@@ -9,7 +9,7 @@ import { runRouteMutationGuards, type RouteMutationGuardResult } from '@open-mer
9
9
  import { emitCrudSideEffects } from '@open-mercato/shared/lib/commands/helpers'
10
10
  import { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
11
11
  import { createLogger } from '@open-mercato/shared/lib/logger'
12
- import { getCustomerAuthFromRequest, type CustomerAuthContext } from '@open-mercato/core/modules/customer_accounts/lib/customerAuth'
12
+ import type { CustomerAuthContext } from '@open-mercato/core/modules/customer_accounts/lib/customerAuth'
13
13
  import { Attachment, AttachmentPartition } from '@open-mercato/core/modules/attachments/data/entities'
14
14
  import { buildAttachmentImageUrl, slugifyAttachmentFileName } from '@open-mercato/core/modules/attachments/lib/imageUrls'
15
15
  import { StorageDriverFactory } from '@open-mercato/core/modules/attachments/lib/drivers'
@@ -28,6 +28,7 @@ import { WARRANTY_CLAIM_RESOURCE_KIND } from '../../../commands/shared'
28
28
  import { CUSTOMER_VISIBLE_ATTACHMENT_TAG, isCustomerVisibleAttachment } from '../../../lib/attachmentVisibility'
29
29
  import { loadPortalOwnedClaim } from '../../../lib/portalClaimAccess'
30
30
  import { resolvePortalAttachmentUploadService } from '../../../lib/portalAttachmentUpload'
31
+ import { resolveLinkedCustomerAuth } from '../../../lib/portalAuthGuard'
31
32
 
32
33
  const CLAIM_ATTACHMENT_ENTITY_ID = 'warranty_claims:warranty_claim'
33
34
  const logger = createLogger('warranty_claims').child({ route: 'portal-attachments' })
@@ -80,13 +81,9 @@ function toIso(value: Date | string | null | undefined): string | null {
80
81
  }
81
82
 
82
83
  async function resolvePortalContext(req: Request): Promise<PortalContext | Response> {
83
- const auth = await getCustomerAuthFromRequest(req)
84
- if (!auth) {
85
- return NextResponse.json({ ok: false, error: 'warranty_claims.errors.unauthorized' }, { status: 401 })
86
- }
87
- if (!auth.customerEntityId) {
88
- return NextResponse.json({ ok: false, error: 'warranty_claims.errors.customerAccountNotLinked' }, { status: 403 })
89
- }
84
+ const authOrResponse = await resolveLinkedCustomerAuth(req)
85
+ if (authOrResponse instanceof Response) return authOrResponse
86
+ const auth = authOrResponse
90
87
  const container = await createRequestContainer()
91
88
  const em = container.resolve('em') as EntityManager
92
89
  return {
@@ -240,19 +237,23 @@ async function deletePortalAttachment(context: PortalContext, attachment: Attach
240
237
  }
241
238
  }
242
239
 
243
- async function streamOwnedAttachment(context: PortalContext, attachmentId: string): Promise<Response> {
240
+ async function streamOwnedAttachment(
241
+ context: PortalContext,
242
+ attachmentId: string,
243
+ translate: (key: string, fallback?: string) => string,
244
+ ): Promise<Response> {
244
245
  const owned = await loadOwnedVisibleAttachment(context, attachmentId)
245
246
  if (!owned) {
246
- return NextResponse.json({ ok: false, error: 'warranty_claims.errors.notFound' }, { status: 404 })
247
+ return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.notFound', 'Claim not found.') }, { status: 404 })
247
248
  }
248
249
  const { attachment } = owned
249
250
  const partition = await context.em.findOne(AttachmentPartition, { code: attachment.partitionCode })
250
251
  if (!partition) {
251
- return NextResponse.json({ ok: false, error: 'warranty_claims.errors.load_failed' }, { status: 500 })
252
+ return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.load_failed', 'Failed to load warranty claim data.') }, { status: 500 })
252
253
  }
253
254
  const access = checkAttachmentAccess(attachmentAuth(context), attachment, partition, { requireAuthForPublic: true })
254
255
  if (!access.ok) {
255
- return NextResponse.json({ ok: false, error: 'warranty_claims.errors.notFound' }, { status: 404 })
256
+ return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.notFound', 'Claim not found.') }, { status: 404 })
256
257
  }
257
258
  const driver = await (await resolveStorageDriverFactory(context)).resolveForPartition(attachment.partitionCode, {
258
259
  tenantId: attachment.tenantId ?? '',
@@ -262,7 +263,7 @@ async function streamOwnedAttachment(context: PortalContext, attachmentId: strin
262
263
  try {
263
264
  buffer = (await driver.read(attachment.partitionCode, attachment.storagePath)).buffer
264
265
  } catch {
265
- return NextResponse.json({ ok: false, error: 'warranty_claims.errors.notFound' }, { status: 404 })
266
+ return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.notFound', 'Claim not found.') }, { status: 404 })
266
267
  }
267
268
  const headers: Record<string, string> = {
268
269
  'Cache-Control': 'private, max-age=60',
@@ -279,14 +280,15 @@ export async function GET(req: Request) {
279
280
  const contextOrResponse = await resolvePortalContext(req)
280
281
  if (contextOrResponse instanceof Response) return contextOrResponse
281
282
  const context = contextOrResponse
283
+ const { translate } = await resolveTranslations()
282
284
  const url = new URL(req.url)
283
285
  const attachmentId = url.searchParams.get('attachmentId')
284
286
  if (attachmentId) {
285
287
  const attachmentIdParsed = z.string().uuid().safeParse(attachmentId)
286
288
  if (!attachmentIdParsed.success) {
287
- return NextResponse.json({ ok: false, error: 'warranty_claims.errors.invalidInput' }, { status: 400 })
289
+ return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.invalidInput', 'Invalid input') }, { status: 400 })
288
290
  }
289
- return streamOwnedAttachment(context, attachmentIdParsed.data)
291
+ return streamOwnedAttachment(context, attachmentIdParsed.data, translate)
290
292
  }
291
293
  const parsed = attachmentQuerySchema.safeParse({
292
294
  claimId: url.searchParams.get('claimId') ?? undefined,
@@ -294,11 +296,11 @@ export async function GET(req: Request) {
294
296
  pageSize: url.searchParams.get('pageSize') ?? undefined,
295
297
  })
296
298
  if (!parsed.success) {
297
- return NextResponse.json({ ok: false, error: 'warranty_claims.errors.invalidInput' }, { status: 400 })
299
+ return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.invalidInput', 'Invalid input') }, { status: 400 })
298
300
  }
299
301
  const claim = await loadOwnedClaim(context, parsed.data.claimId)
300
302
  if (!claim) {
301
- return NextResponse.json({ ok: false, error: 'warranty_claims.errors.notFound' }, { status: 404 })
303
+ return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.notFound', 'Claim not found.') }, { status: 404 })
302
304
  }
303
305
  const filter = {
304
306
  entityId: CLAIM_ATTACHMENT_ENTITY_ID,
@@ -343,12 +345,12 @@ async function handleUpload(req: Request, replacement: boolean): Promise<Respons
343
345
  const contextOrResponse = await resolvePortalContext(req)
344
346
  if (contextOrResponse instanceof Response) return contextOrResponse
345
347
  const context = contextOrResponse
348
+ const { t, translate } = await resolveTranslations()
346
349
  const contentType = req.headers.get('content-type') || ''
347
350
  if (!contentType.toLowerCase().includes('multipart/form-data')) {
348
- return NextResponse.json({ ok: false, error: 'warranty_claims.errors.invalidInput' }, { status: 400 })
351
+ return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.invalidInput', 'Invalid input') }, { status: 400 })
349
352
  }
350
353
  if (!isMultipartRequestWithinUploadLimit(req.headers.get('content-length'))) {
351
- const { t } = await resolveTranslations()
352
354
  return NextResponse.json({
353
355
  ok: false,
354
356
  error: t('attachments.errors.maxUploadSize', 'Attachment exceeds the maximum upload size.'),
@@ -364,15 +366,15 @@ async function handleUpload(req: Request, replacement: boolean): Promise<Respons
364
366
  ? z.string().uuid().safeParse(form.get('attachmentId') ?? undefined)
365
367
  : null
366
368
  if (!parsed.success || (replacement && !parsedAttachmentId?.success)) {
367
- return NextResponse.json({ ok: false, error: 'warranty_claims.errors.invalidInput' }, { status: 400 })
369
+ return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.invalidInput', 'Invalid input') }, { status: 400 })
368
370
  }
369
371
  const claim = await loadOwnedClaim(context, parsed.data.claimId)
370
372
  if (!claim) {
371
- return NextResponse.json({ ok: false, error: 'warranty_claims.errors.notFound' }, { status: 404 })
373
+ return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.notFound', 'Claim not found.') }, { status: 404 })
372
374
  }
373
375
  const file = form.get('file')
374
376
  if (!(file instanceof File)) {
375
- return NextResponse.json({ ok: false, error: 'warranty_claims.portal.detail.fileRequired' }, { status: 400 })
377
+ return NextResponse.json({ ok: false, error: translate('warranty_claims.portal.detail.fileRequired', 'Choose a file before uploading.') }, { status: 400 })
376
378
  }
377
379
  const buffer = Buffer.from(await file.arrayBuffer())
378
380
 
@@ -381,7 +383,7 @@ async function handleUpload(req: Request, replacement: boolean): Promise<Respons
381
383
  ? await loadOwnedVisibleAttachment(context, attachmentId)
382
384
  : null
383
385
  if (replacement && (!replacementTarget || replacementTarget.claim.id !== claim.id)) {
384
- return NextResponse.json({ ok: false, error: 'warranty_claims.errors.notFound' }, { status: 404 })
386
+ return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.notFound', 'Claim not found.') }, { status: 404 })
385
387
  }
386
388
 
387
389
  const guarded = await runPortalAttachmentGuard(req, context, claim.id, replacement ? 'update' : 'create', {
@@ -397,7 +399,7 @@ async function handleUpload(req: Request, replacement: boolean): Promise<Respons
397
399
 
398
400
  const uploadService = resolvePortalAttachmentUploadService(context.container)
399
401
  if (!uploadService) {
400
- return NextResponse.json({ ok: false, error: 'warranty_claims.portal.detail.attachmentError' }, { status: 503 })
402
+ return NextResponse.json({ ok: false, error: translate('warranty_claims.portal.detail.attachmentError', 'Attachment upload failed.') }, { status: 503 })
401
403
  }
402
404
 
403
405
  let attachment: Attachment
@@ -414,7 +416,6 @@ async function handleUpload(req: Request, replacement: boolean): Promise<Respons
414
416
  })
415
417
  } catch (error) {
416
418
  if (isScopedAttachmentUploadError(error)) {
417
- const { t } = await resolveTranslations()
418
419
  const [key, fallback] = ATTACHMENT_ERROR_TRANSLATIONS[error.code]
419
420
  ?? ['warranty_claims.portal.detail.attachmentError', 'Attachment upload failed.']
420
421
  return NextResponse.json(
@@ -451,16 +452,17 @@ export async function DELETE(req: Request) {
451
452
  const contextOrResponse = await resolvePortalContext(req)
452
453
  if (contextOrResponse instanceof Response) return contextOrResponse
453
454
  const context = contextOrResponse
455
+ const { translate } = await resolveTranslations()
454
456
  const url = new URL(req.url)
455
457
  const parsed = deleteQuerySchema.safeParse({
456
458
  attachmentId: url.searchParams.get('attachmentId') ?? undefined,
457
459
  })
458
460
  if (!parsed.success) {
459
- return NextResponse.json({ ok: false, error: 'warranty_claims.errors.invalidInput' }, { status: 400 })
461
+ return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.invalidInput', 'Invalid input') }, { status: 400 })
460
462
  }
461
463
  const owned = await loadOwnedVisibleAttachment(context, parsed.data.attachmentId)
462
464
  if (!owned) {
463
- return NextResponse.json({ ok: false, error: 'warranty_claims.errors.notFound' }, { status: 404 })
465
+ return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.notFound', 'Claim not found.') }, { status: 404 })
464
466
  }
465
467
  const guarded = await runPortalAttachmentGuard(req, context, owned.claim.id, 'delete', {
466
468
  claimId: owned.claim.id,
@@ -3,10 +3,12 @@ import { z } from 'zod'
3
3
  import type { EntityManager } from '@mikro-orm/postgresql'
4
4
  import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
5
5
  import { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
6
- import { getCustomerAuthFromRequest, type CustomerAuthContext } from '@open-mercato/core/modules/customer_accounts/lib/customerAuth'
6
+ import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
7
+ import type { CustomerAuthContext } from '@open-mercato/core/modules/customer_accounts/lib/customerAuth'
7
8
  import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
8
9
  import { WarrantyClaim, WarrantyClaimLine } from '../../../../data/entities'
9
10
  import { loadPortalOwnedClaim } from '../../../../lib/portalClaimAccess'
11
+ import { resolveLinkedCustomerAuth } from '../../../../lib/portalAuthGuard'
10
12
 
11
13
  export const metadata = {
12
14
  GET: { requireAuth: false },
@@ -40,13 +42,9 @@ async function resolveClaimId(ctx: RouteContext): Promise<string | null> {
40
42
  }
41
43
 
42
44
  async function resolvePortalContext(req: Request): Promise<PortalContext | Response> {
43
- const auth = await getCustomerAuthFromRequest(req)
44
- if (!auth) {
45
- return NextResponse.json({ ok: false, error: 'warranty_claims.errors.unauthorized' }, { status: 401 })
46
- }
47
- if (!auth.customerEntityId) {
48
- return NextResponse.json({ ok: false, error: 'warranty_claims.errors.customerAccountNotLinked' }, { status: 403 })
49
- }
45
+ const authOrResponse = await resolveLinkedCustomerAuth(req)
46
+ if (authOrResponse instanceof Response) return authOrResponse
47
+ const auth = authOrResponse
50
48
  const container = await createRequestContainer()
51
49
  const em = container.resolve('em') as EntityManager
52
50
  return {
@@ -108,9 +106,10 @@ export async function GET(req: Request, ctx: RouteContext) {
108
106
  const contextOrResponse = await resolvePortalContext(req)
109
107
  if (contextOrResponse instanceof Response) return contextOrResponse
110
108
  const context = contextOrResponse
109
+ const { translate } = await resolveTranslations()
111
110
  const claimId = await resolveClaimId(ctx)
112
111
  if (!claimId) {
113
- return NextResponse.json({ ok: false, error: 'warranty_claims.errors.notFound' }, { status: 404 })
112
+ return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.notFound', 'Claim not found.') }, { status: 404 })
114
113
  }
115
114
  const scope = { tenantId: context.tenantId, organizationId: context.organizationId }
116
115
  const claim = await loadPortalOwnedClaim(
@@ -123,7 +122,7 @@ export async function GET(req: Request, ctx: RouteContext) {
123
122
  claimId,
124
123
  )
125
124
  if (!claim) {
126
- return NextResponse.json({ ok: false, error: 'warranty_claims.errors.notFound' }, { status: 404 })
125
+ return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.notFound', 'Claim not found.') }, { status: 404 })
127
126
  }
128
127
  const lines = await findWithDecryption(
129
128
  context.em,
@@ -1,14 +1,14 @@
1
- import { NextResponse } from 'next/server'
2
1
  import { z } from 'zod'
3
2
  import type { EntityManager } from '@mikro-orm/postgresql'
4
3
  import type { AuthContext } from '@open-mercato/shared/lib/auth/server'
5
4
  import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
6
5
  import type { CommandRuntimeContext } from '@open-mercato/shared/lib/commands'
7
6
  import { runRouteMutationGuards, type RouteMutationGuardResult } from '@open-mercato/shared/lib/crud/route-mutation-guard'
8
- import { getCustomerAuthFromRequest, type CustomerAuthContext } from '@open-mercato/core/modules/customer_accounts/lib/customerAuth'
7
+ import type { CustomerAuthContext } from '@open-mercato/core/modules/customer_accounts/lib/customerAuth'
9
8
  import { WarrantyClaim } from '../../../../data/entities'
10
9
  import { WARRANTY_CLAIM_RESOURCE_KIND } from '../../../../commands/shared'
11
10
  import { loadPortalOwnedClaim } from '../../../../lib/portalClaimAccess'
11
+ import { resolveLinkedCustomerAuth } from '../../../../lib/portalAuthGuard'
12
12
 
13
13
  export type PortalClaimActionRouteContext = { params: Promise<{ id: string }> }
14
14
 
@@ -34,13 +34,9 @@ export async function resolvePortalClaimId(ctx: PortalClaimActionRouteContext):
34
34
  }
35
35
 
36
36
  export async function resolvePortalActionContext(req: Request): Promise<PortalClaimActionContext | Response> {
37
- const auth = await getCustomerAuthFromRequest(req)
38
- if (!auth) {
39
- return NextResponse.json({ ok: false, error: 'warranty_claims.errors.unauthorized' }, { status: 401 })
40
- }
41
- if (!auth.customerEntityId) {
42
- return NextResponse.json({ ok: false, error: 'warranty_claims.errors.customerAccountNotLinked' }, { status: 403 })
43
- }
37
+ const authOrResponse = await resolveLinkedCustomerAuth(req)
38
+ if (authOrResponse instanceof Response) return authOrResponse
39
+ const auth = authOrResponse
44
40
  const container = await createRequestContainer()
45
41
  const em = container.resolve('em') as EntityManager
46
42
  const commandAuth: NonNullable<AuthContext> = {
@@ -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 {
@@ -21,16 +22,16 @@ export async function POST(req: Request, ctx: PortalClaimActionRouteContext) {
21
22
  const contextOrResponse = await resolvePortalActionContext(req)
22
23
  if (contextOrResponse instanceof Response) return contextOrResponse
23
24
  const context = contextOrResponse
25
+ const { translate } = await resolveTranslations()
24
26
  const claimId = await resolvePortalClaimId(ctx)
25
27
  if (!claimId) {
26
- return NextResponse.json({ ok: false, error: 'warranty_claims.errors.notFound' }, { status: 404 })
28
+ return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.notFound', 'Claim not found.') }, { status: 404 })
27
29
  }
28
30
  const claim = await loadOwnedClaim(context, claimId)
29
31
  if (!claim) {
30
- return NextResponse.json({ ok: false, error: 'warranty_claims.errors.notFound' }, { status: 404 })
32
+ return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.notFound', 'Claim not found.') }, { status: 404 })
31
33
  }
32
34
  if (claim.status !== 'draft') {
33
- const { translate } = await resolveTranslations()
34
35
  return NextResponse.json(
35
36
  { ok: false, error: translate('warranty_claims.errors.invalidTransition', 'That status change is not allowed.') },
36
37
  { status: 400 },
@@ -55,6 +56,10 @@ export async function POST(req: Request, ctx: PortalClaimActionRouteContext) {
55
56
  if (isCrudHttpError(err)) {
56
57
  return NextResponse.json(err.body, { status: err.status })
57
58
  }
59
+ const interceptorRejection = getCommandInterceptorHttpRejection(err)
60
+ if (interceptorRejection) {
61
+ return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status })
62
+ }
58
63
  throw err
59
64
  }
60
65