@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
@@ -1,6 +1,7 @@
1
1
  import { NextResponse } from "next/server";
2
2
  import { z } from "zod";
3
3
  import { isCrudHttpError } from "@open-mercato/shared/lib/crud/errors";
4
+ import { getCommandInterceptorHttpRejection } from "@open-mercato/shared/lib/commands/errors";
4
5
  import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
5
6
  import {
6
7
  loadOwnedClaim,
@@ -16,16 +17,16 @@ async function POST(req, ctx) {
16
17
  const contextOrResponse = await resolvePortalActionContext(req);
17
18
  if (contextOrResponse instanceof Response) return contextOrResponse;
18
19
  const context = contextOrResponse;
20
+ const { translate } = await resolveTranslations();
19
21
  const claimId = await resolvePortalClaimId(ctx);
20
22
  if (!claimId) {
21
- return NextResponse.json({ ok: false, error: "warranty_claims.errors.notFound" }, { status: 404 });
23
+ return NextResponse.json({ ok: false, error: translate("warranty_claims.errors.notFound", "Claim not found.") }, { status: 404 });
22
24
  }
23
25
  const claim = await loadOwnedClaim(context, claimId);
24
26
  if (!claim) {
25
- return NextResponse.json({ ok: false, error: "warranty_claims.errors.notFound" }, { status: 404 });
27
+ return NextResponse.json({ ok: false, error: translate("warranty_claims.errors.notFound", "Claim not found.") }, { status: 404 });
26
28
  }
27
29
  if (!PORTAL_WITHDRAWABLE_STATUSES.has(claim.status)) {
28
- const { translate } = await resolveTranslations();
29
30
  return NextResponse.json(
30
31
  { ok: false, error: translate("warranty_claims.errors.invalidTransition", "That status change is not allowed.") },
31
32
  { status: 400 }
@@ -52,6 +53,10 @@ async function POST(req, ctx) {
52
53
  if (isCrudHttpError(err)) {
53
54
  return NextResponse.json(err.body, { status: err.status });
54
55
  }
56
+ const interceptorRejection = getCommandInterceptorHttpRejection(err);
57
+ if (interceptorRejection) {
58
+ return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status });
59
+ }
55
60
  throw err;
56
61
  }
57
62
  await guarded.runAfterSuccess();
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../../../src/modules/warranty_claims/api/portal/claims/%5Bid%5D/withdraw/route.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { CommandBus } from '@open-mercato/shared/lib/commands'\nimport { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport type { TransitionClaimInput, WarrantyClaimStatus } from '../../../../../data/validators'\nimport {\n loadOwnedClaim,\n resolvePortalActionContext,\n resolvePortalClaimId,\n runPortalClaimActionGuard,\n type PortalClaimActionRouteContext,\n} from '../shared'\n\nexport const metadata = {\n POST: { requireAuth: false },\n}\n\nconst PORTAL_WITHDRAWABLE_STATUSES = new Set<WarrantyClaimStatus>(['draft', 'submitted'])\n\nexport async function POST(req: Request, ctx: PortalClaimActionRouteContext) {\n const contextOrResponse = await resolvePortalActionContext(req)\n if (contextOrResponse instanceof Response) return contextOrResponse\n const context = contextOrResponse\n const claimId = await resolvePortalClaimId(ctx)\n if (!claimId) {\n return NextResponse.json({ ok: false, error: 'warranty_claims.errors.notFound' }, { status: 404 })\n }\n const claim = await loadOwnedClaim(context, claimId)\n if (!claim) {\n return NextResponse.json({ ok: false, error: 'warranty_claims.errors.notFound' }, { status: 404 })\n }\n if (!PORTAL_WITHDRAWABLE_STATUSES.has(claim.status)) {\n const { translate } = await resolveTranslations()\n return NextResponse.json(\n { ok: false, error: translate('warranty_claims.errors.invalidTransition', 'That status change is not allowed.') },\n { status: 400 },\n )\n }\n\n const guarded = await runPortalClaimActionGuard(req, context, claim.id, {\n id: claim.id,\n action: 'withdraw',\n toStatus: 'cancelled',\n })\n if (!guarded.ok) {\n return guarded.response\n }\n\n const commandBus = context.commandCtx.container.resolve('commandBus') as CommandBus\n try {\n await commandBus.execute<TransitionClaimInput, { claimId: string }>(\n 'warranty_claims.claim.transition',\n {\n input: { id: claim.id, toStatus: 'cancelled', actorCustomerId: context.customerId },\n ctx: context.commandCtx,\n },\n )\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n throw err\n }\n\n await guarded.runAfterSuccess()\n\n return NextResponse.json({ ok: true, claimId: claim.id, status: 'cancelled' })\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Warranty Claims Portal',\n summary: 'Customer portal warranty claim withdraw action',\n methods: {\n POST: {\n summary: 'Withdraw (cancel) an owned claim while it is draft or submitted',\n responses: [\n {\n status: 200,\n description: 'Claim withdrawn',\n schema: z.object({ ok: z.boolean(), claimId: z.string().uuid(), status: z.literal('cancelled') }),\n },\n ],\n },\n },\n}\n"],
5
- "mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAElB,SAAS,uBAAuB;AAChC,SAAS,2BAA2B;AAGpC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AAEA,MAAM,WAAW;AAAA,EACtB,MAAM,EAAE,aAAa,MAAM;AAC7B;AAEA,MAAM,+BAA+B,oBAAI,IAAyB,CAAC,SAAS,WAAW,CAAC;AAExF,eAAsB,KAAK,KAAc,KAAoC;AAC3E,QAAM,oBAAoB,MAAM,2BAA2B,GAAG;AAC9D,MAAI,6BAA6B,SAAU,QAAO;AAClD,QAAM,UAAU;AAChB,QAAM,UAAU,MAAM,qBAAqB,GAAG;AAC9C,MAAI,CAAC,SAAS;AACZ,WAAO,aAAa,KAAK,EAAE,IAAI,OAAO,OAAO,kCAAkC,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACnG;AACA,QAAM,QAAQ,MAAM,eAAe,SAAS,OAAO;AACnD,MAAI,CAAC,OAAO;AACV,WAAO,aAAa,KAAK,EAAE,IAAI,OAAO,OAAO,kCAAkC,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACnG;AACA,MAAI,CAAC,6BAA6B,IAAI,MAAM,MAAM,GAAG;AACnD,UAAM,EAAE,UAAU,IAAI,MAAM,oBAAoB;AAChD,WAAO,aAAa;AAAA,MAClB,EAAE,IAAI,OAAO,OAAO,UAAU,4CAA4C,oCAAoC,EAAE;AAAA,MAChH,EAAE,QAAQ,IAAI;AAAA,IAChB;AAAA,EACF;AAEA,QAAM,UAAU,MAAM,0BAA0B,KAAK,SAAS,MAAM,IAAI;AAAA,IACtE,IAAI,MAAM;AAAA,IACV,QAAQ;AAAA,IACR,UAAU;AAAA,EACZ,CAAC;AACD,MAAI,CAAC,QAAQ,IAAI;AACf,WAAO,QAAQ;AAAA,EACjB;AAEA,QAAM,aAAa,QAAQ,WAAW,UAAU,QAAQ,YAAY;AACpE,MAAI;AACF,UAAM,WAAW;AAAA,MACf;AAAA,MACA;AAAA,QACE,OAAO,EAAE,IAAI,MAAM,IAAI,UAAU,aAAa,iBAAiB,QAAQ,WAAW;AAAA,QAClF,KAAK,QAAQ;AAAA,MACf;AAAA,IACF;AAAA,EACF,SAAS,KAAK;AACZ,QAAI,gBAAgB,GAAG,GAAG;AACxB,aAAO,aAAa,KAAK,IAAI,MAAM,EAAE,QAAQ,IAAI,OAAO,CAAC;AAAA,IAC3D;AACA,UAAM;AAAA,EACR;AAEA,QAAM,QAAQ,gBAAgB;AAE9B,SAAO,aAAa,KAAK,EAAE,IAAI,MAAM,SAAS,MAAM,IAAI,QAAQ,YAAY,CAAC;AAC/E;AAEO,MAAM,UAA2B;AAAA,EACtC,KAAK;AAAA,EACL,SAAS;AAAA,EACT,SAAS;AAAA,IACP,MAAM;AAAA,MACJ,SAAS;AAAA,MACT,WAAW;AAAA,QACT;AAAA,UACE,QAAQ;AAAA,UACR,aAAa;AAAA,UACb,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,GAAG,SAAS,EAAE,OAAO,EAAE,KAAK,GAAG,QAAQ,EAAE,QAAQ,WAAW,EAAE,CAAC;AAAA,QAClG;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;",
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { CommandBus } from '@open-mercato/shared/lib/commands'\nimport { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { getCommandInterceptorHttpRejection } from '@open-mercato/shared/lib/commands/errors'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport type { TransitionClaimInput, WarrantyClaimStatus } from '../../../../../data/validators'\nimport {\n loadOwnedClaim,\n resolvePortalActionContext,\n resolvePortalClaimId,\n runPortalClaimActionGuard,\n type PortalClaimActionRouteContext,\n} from '../shared'\n\nexport const metadata = {\n POST: { requireAuth: false },\n}\n\nconst PORTAL_WITHDRAWABLE_STATUSES = new Set<WarrantyClaimStatus>(['draft', 'submitted'])\n\nexport async function POST(req: Request, ctx: PortalClaimActionRouteContext) {\n const contextOrResponse = await resolvePortalActionContext(req)\n if (contextOrResponse instanceof Response) return contextOrResponse\n const context = contextOrResponse\n const { translate } = await resolveTranslations()\n const claimId = await resolvePortalClaimId(ctx)\n if (!claimId) {\n return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.notFound', 'Claim not found.') }, { status: 404 })\n }\n const claim = await loadOwnedClaim(context, claimId)\n if (!claim) {\n return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.notFound', 'Claim not found.') }, { status: 404 })\n }\n if (!PORTAL_WITHDRAWABLE_STATUSES.has(claim.status)) {\n return NextResponse.json(\n { ok: false, error: translate('warranty_claims.errors.invalidTransition', 'That status change is not allowed.') },\n { status: 400 },\n )\n }\n\n const guarded = await runPortalClaimActionGuard(req, context, claim.id, {\n id: claim.id,\n action: 'withdraw',\n toStatus: 'cancelled',\n })\n if (!guarded.ok) {\n return guarded.response\n }\n\n const commandBus = context.commandCtx.container.resolve('commandBus') as CommandBus\n try {\n await commandBus.execute<TransitionClaimInput, { claimId: string }>(\n 'warranty_claims.claim.transition',\n {\n input: { id: claim.id, toStatus: 'cancelled', actorCustomerId: context.customerId },\n ctx: context.commandCtx,\n },\n )\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n const interceptorRejection = getCommandInterceptorHttpRejection(err)\n if (interceptorRejection) {\n return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status })\n }\n throw err\n }\n\n await guarded.runAfterSuccess()\n\n return NextResponse.json({ ok: true, claimId: claim.id, status: 'cancelled' })\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Warranty Claims Portal',\n summary: 'Customer portal warranty claim withdraw action',\n methods: {\n POST: {\n summary: 'Withdraw (cancel) an owned claim while it is draft or submitted',\n responses: [\n {\n status: 200,\n description: 'Claim withdrawn',\n schema: z.object({ ok: z.boolean(), claimId: z.string().uuid(), status: z.literal('cancelled') }),\n },\n ],\n },\n },\n}\n"],
5
+ "mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAElB,SAAS,uBAAuB;AAChC,SAAS,0CAA0C;AACnD,SAAS,2BAA2B;AAGpC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AAEA,MAAM,WAAW;AAAA,EACtB,MAAM,EAAE,aAAa,MAAM;AAC7B;AAEA,MAAM,+BAA+B,oBAAI,IAAyB,CAAC,SAAS,WAAW,CAAC;AAExF,eAAsB,KAAK,KAAc,KAAoC;AAC3E,QAAM,oBAAoB,MAAM,2BAA2B,GAAG;AAC9D,MAAI,6BAA6B,SAAU,QAAO;AAClD,QAAM,UAAU;AAChB,QAAM,EAAE,UAAU,IAAI,MAAM,oBAAoB;AAChD,QAAM,UAAU,MAAM,qBAAqB,GAAG;AAC9C,MAAI,CAAC,SAAS;AACZ,WAAO,aAAa,KAAK,EAAE,IAAI,OAAO,OAAO,UAAU,mCAAmC,kBAAkB,EAAE,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EAClI;AACA,QAAM,QAAQ,MAAM,eAAe,SAAS,OAAO;AACnD,MAAI,CAAC,OAAO;AACV,WAAO,aAAa,KAAK,EAAE,IAAI,OAAO,OAAO,UAAU,mCAAmC,kBAAkB,EAAE,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EAClI;AACA,MAAI,CAAC,6BAA6B,IAAI,MAAM,MAAM,GAAG;AACnD,WAAO,aAAa;AAAA,MAClB,EAAE,IAAI,OAAO,OAAO,UAAU,4CAA4C,oCAAoC,EAAE;AAAA,MAChH,EAAE,QAAQ,IAAI;AAAA,IAChB;AAAA,EACF;AAEA,QAAM,UAAU,MAAM,0BAA0B,KAAK,SAAS,MAAM,IAAI;AAAA,IACtE,IAAI,MAAM;AAAA,IACV,QAAQ;AAAA,IACR,UAAU;AAAA,EACZ,CAAC;AACD,MAAI,CAAC,QAAQ,IAAI;AACf,WAAO,QAAQ;AAAA,EACjB;AAEA,QAAM,aAAa,QAAQ,WAAW,UAAU,QAAQ,YAAY;AACpE,MAAI;AACF,UAAM,WAAW;AAAA,MACf;AAAA,MACA;AAAA,QACE,OAAO,EAAE,IAAI,MAAM,IAAI,UAAU,aAAa,iBAAiB,QAAQ,WAAW;AAAA,QAClF,KAAK,QAAQ;AAAA,MACf;AAAA,IACF;AAAA,EACF,SAAS,KAAK;AACZ,QAAI,gBAAgB,GAAG,GAAG;AACxB,aAAO,aAAa,KAAK,IAAI,MAAM,EAAE,QAAQ,IAAI,OAAO,CAAC;AAAA,IAC3D;AACA,UAAM,uBAAuB,mCAAmC,GAAG;AACnE,QAAI,sBAAsB;AACxB,aAAO,aAAa,KAAK,qBAAqB,MAAM,EAAE,QAAQ,qBAAqB,OAAO,CAAC;AAAA,IAC7F;AACA,UAAM;AAAA,EACR;AAEA,QAAM,QAAQ,gBAAgB;AAE9B,SAAO,aAAa,KAAK,EAAE,IAAI,MAAM,SAAS,MAAM,IAAI,QAAQ,YAAY,CAAC;AAC/E;AAEO,MAAM,UAA2B;AAAA,EACtC,KAAK;AAAA,EACL,SAAS;AAAA,EACT,SAAS;AAAA,IACP,MAAM;AAAA,MACJ,SAAS;AAAA,MACT,WAAW;AAAA,QACT;AAAA,UACE,QAAQ;AAAA,UACR,aAAa;AAAA,UACb,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,GAAG,SAAS,EAAE,OAAO,EAAE,KAAK,GAAG,QAAQ,EAAE,QAAQ,WAAW,EAAE,CAAC;AAAA,QAClG;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;",
6
6
  "names": []
7
7
  }
@@ -2,12 +2,13 @@ import { NextResponse } from "next/server";
2
2
  import { z } from "zod";
3
3
  import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
4
4
  import { isCrudHttpError } from "@open-mercato/shared/lib/crud/errors";
5
+ import { getCommandInterceptorHttpRejection } from "@open-mercato/shared/lib/commands/errors";
5
6
  import { escapeLikePattern } from "@open-mercato/shared/lib/db/escapeLikePattern";
6
7
  import { runRouteMutationGuards } from "@open-mercato/shared/lib/crud/route-mutation-guard";
7
8
  import { findWithDecryption } from "@open-mercato/shared/lib/encryption/find";
8
9
  import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
9
10
  import { createLogger } from "@open-mercato/shared/lib/logger";
10
- import { getCustomerAuthFromRequest } from "@open-mercato/core/modules/customer_accounts/lib/customerAuth";
11
+ import { resolveLinkedCustomerAuth } from "../../../lib/portalAuthGuard.js";
11
12
  import { WarrantyClaim, WarrantyClaimLine } from "../../../data/entities.js";
12
13
  import {
13
14
  claimStatusSchema,
@@ -99,14 +100,9 @@ function serializePortalClaim(claim, lines) {
99
100
  };
100
101
  }
101
102
  async function resolvePortalContext(req) {
102
- const { translate } = await resolveTranslations();
103
- const auth = await getCustomerAuthFromRequest(req);
104
- if (!auth) {
105
- return NextResponse.json({ ok: false, error: translate("warranty_claims.errors.unauthorized", "Unauthorized") }, { status: 401 });
106
- }
107
- if (!auth.customerEntityId) {
108
- return NextResponse.json({ ok: false, error: translate("warranty_claims.errors.customerAccountNotLinked", "Customer account is not linked to a customer record") }, { status: 403 });
109
- }
103
+ const authOrResponse = await resolveLinkedCustomerAuth(req);
104
+ if (authOrResponse instanceof Response) return authOrResponse;
105
+ const auth = authOrResponse;
110
106
  const container = await createRequestContainer();
111
107
  const em = container.resolve("em");
112
108
  const commandAuth = {
@@ -401,6 +397,10 @@ async function POST(req) {
401
397
  if (isCrudHttpError(err)) {
402
398
  return NextResponse.json(err.body, { status: err.status });
403
399
  }
400
+ const interceptorRejection = getCommandInterceptorHttpRejection(err);
401
+ if (interceptorRejection) {
402
+ return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status });
403
+ }
404
404
  throw err;
405
405
  }
406
406
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/warranty_claims/api/portal/claims/route.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { AuthContext } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport type { CommandBus, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { escapeLikePattern } from '@open-mercato/shared/lib/db/escapeLikePattern'\nimport { runRouteMutationGuards, type RouteMutationGuardResult } from '@open-mercato/shared/lib/crud/route-mutation-guard'\nimport { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport { getCustomerAuthFromRequest, type CustomerAuthContext } from '@open-mercato/core/modules/customer_accounts/lib/customerAuth'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { WarrantyClaim, WarrantyClaimLine } from '../../../data/entities'\nimport {\n claimStatusSchema,\n portalIntakeInputSchema,\n type ClaimCreateInput,\n type PortalIntakeInput,\n} from '../../../data/validators'\nimport { WARRANTY_CLAIM_RESOURCE_KIND } from '../../../commands/shared'\nimport { portalClaimStatusesForStateGroup } from '../../../lib/listSegments'\nimport { buildPortalOwnedClaimWhere } from '../../../lib/portalClaimAccess'\n\nexport const metadata = {\n GET: { requireAuth: false },\n POST: { requireAuth: false },\n}\n\nconst logger = createLogger('warranty_claims')\n\nconst emptyQueryValueToUndefined = (value: unknown): unknown => {\n if (typeof value !== 'string') return value\n const trimmed = value.trim()\n return trimmed.length ? trimmed : undefined\n}\n\nconst optionalTrimmedQueryString = (max: number) =>\n z.preprocess(emptyQueryValueToUndefined, z.string().trim().min(1).max(max).optional())\n\nconst listQuerySchema = z.object({\n page: z.coerce.number().int().min(1).default(1),\n pageSize: z.coerce.number().int().min(1).max(100).default(20),\n search: optionalTrimmedQueryString(190),\n status: z.preprocess(emptyQueryValueToUndefined, claimStatusSchema.optional()),\n stateGroup: z.enum(['open', 'resolved']).optional(),\n serialNumber: optionalTrimmedQueryString(191),\n})\n\ntype PortalContext = {\n auth: CustomerAuthContext\n customerId: string\n tenantId: string\n organizationId: string\n em: EntityManager\n commandCtx: CommandRuntimeContext\n}\n\ntype LineSummary = {\n count: number\n creditTotal: string\n statuses: Record<string, number>\n}\n\ntype OrderValidationResult = {\n orderId: string | null\n currencyCode: string | null\n}\n\ntype PortalOrderLookup = {\n id: string\n currencyCode: string | null\n}\n\ntype PortalOrderLineLookup = {\n id: string\n orderId: string\n}\n\nfunction toRecord(value: unknown): Record<string, unknown> {\n return value && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : {}\n}\n\nfunction stringField(record: Record<string, unknown>, key: string): string | null {\n const value = record[key]\n return typeof value === 'string' ? value : null\n}\n\nfunction toIso(value: Date | string | null | undefined): string | null {\n if (!value) return null\n if (value instanceof Date) return value.toISOString()\n const date = new Date(value)\n return Number.isNaN(date.getTime()) ? value : date.toISOString()\n}\n\nfunction relationId(value: unknown): string | null {\n if (typeof value === 'string') return value\n const record = toRecord(value)\n return typeof record.id === 'string' ? record.id : null\n}\n\nconst CREDIT_SCALE = 10_000n\n\nfunction decimalUnits(value: unknown): bigint {\n const normalized = typeof value === 'number'\n ? (Number.isFinite(value) ? value.toFixed(4) : '')\n : typeof value === 'string' ? value.trim() : ''\n const match = /^([+-]?)(\\d+)(?:\\.(\\d+))?$/.exec(normalized)\n if (!match) return 0n\n const [, sign, whole, fraction = ''] = match\n let units = BigInt(whole) * CREDIT_SCALE + BigInt(fraction.slice(0, 4).padEnd(4, '0'))\n if (fraction.length > 4 && fraction[4] >= '5') units += 1n\n return sign === '-' ? -units : units\n}\n\nfunction decimalString(value: bigint): string {\n const sign = value < 0n ? '-' : ''\n const absolute = value < 0n ? -value : value\n const whole = absolute / CREDIT_SCALE\n const fraction = String(absolute % CREDIT_SCALE).padStart(4, '0').replace(/0+$/, '')\n return `${sign}${whole}${fraction ? `.${fraction}` : ''}`\n}\n\nfunction summarizeLines(lines: WarrantyClaimLine[]): LineSummary {\n let creditTotal = 0n\n const statuses: Record<string, number> = {}\n for (const line of lines) {\n creditTotal += decimalUnits(line.creditAmount)\n statuses[line.lineStatus] = (statuses[line.lineStatus] ?? 0) + 1\n }\n return {\n count: lines.length,\n creditTotal: decimalString(creditTotal),\n statuses,\n }\n}\n\nfunction serializePortalClaim(claim: WarrantyClaim, lines: WarrantyClaimLine[]) {\n return {\n id: claim.id,\n claimNumber: claim.claimNumber,\n claimType: claim.claimType,\n status: claim.status,\n orderId: claim.orderId ?? null,\n orderNumber: claim.orderNumber ?? null,\n reasonCode: claim.reasonCode ?? null,\n resolutionSummary: claim.resolutionSummary ?? null,\n createdAt: toIso(claim.createdAt),\n updatedAt: toIso(claim.updatedAt),\n lines: summarizeLines(lines),\n }\n}\n\nasync function resolvePortalContext(req: Request): Promise<PortalContext | Response> {\n const { translate } = await resolveTranslations()\n const auth = await getCustomerAuthFromRequest(req)\n if (!auth) {\n return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.unauthorized', 'Unauthorized') }, { status: 401 })\n }\n if (!auth.customerEntityId) {\n return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.customerAccountNotLinked', 'Customer account is not linked to a customer record') }, { status: 403 })\n }\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n const commandAuth: NonNullable<AuthContext> = {\n sub: auth.sub,\n sid: auth.sid,\n tenantId: auth.tenantId,\n orgId: auth.orgId,\n email: auth.email,\n customerEntityId: auth.customerEntityId ?? null,\n personEntityId: auth.personEntityId ?? null,\n }\n return {\n auth,\n customerId: auth.customerEntityId,\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n em,\n commandCtx: {\n container,\n auth: commandAuth,\n organizationScope: null,\n selectedOrganizationId: auth.orgId,\n organizationIds: [auth.orgId],\n request: req,\n },\n }\n}\n\ntype PortalSalesDb = {\n sales_orders: {\n id: string\n currency_code: string\n customer_entity_id: string | null\n tenant_id: string | null\n organization_id: string | null\n deleted_at: Date | null\n }\n sales_order_lines: {\n id: string\n order_id: string\n tenant_id: string | null\n organization_id: string | null\n deleted_at: Date | null\n }\n}\n\nfunction isMissingSalesTableError(err: unknown): boolean {\n if (typeof err !== 'object' || err === null) return false\n const candidate = err as { code?: unknown; message?: unknown }\n return candidate.code === '42P01'\n || (typeof candidate.message === 'string'\n && candidate.message.includes('does not exist')\n && (candidate.message.includes('sales_orders') || candidate.message.includes('sales_order_lines')))\n}\n\nasync function loadOwnedOrders(\n context: PortalContext,\n orderIds: string[],\n): Promise<Map<string, PortalOrderLookup>> {\n if (orderIds.length === 0) return new Map()\n try {\n const db = context.em.fork().getKysely<PortalSalesDb>()\n const rows = await db\n .selectFrom('sales_orders')\n .select(['id', 'currency_code'])\n .where('id', 'in', orderIds)\n .where('tenant_id', '=', context.tenantId)\n .where('organization_id', '=', context.organizationId)\n .where('customer_entity_id', '=', context.customerId)\n .where('deleted_at', 'is', null)\n .execute()\n return new Map(rows.map((row) => [row.id, { id: row.id, currencyCode: row.currency_code ?? null }]))\n } catch (err) {\n if (isMissingSalesTableError(err)) return new Map()\n throw err\n }\n}\n\nasync function loadOwnedOrderLines(\n context: PortalContext,\n orderLineIds: string[],\n): Promise<Map<string, PortalOrderLineLookup>> {\n if (orderLineIds.length === 0) return new Map()\n try {\n const db = context.em.fork().getKysely<PortalSalesDb>()\n const rows = await db\n .selectFrom('sales_order_lines')\n .select(['id', 'order_id'])\n .where('id', 'in', orderLineIds)\n .where('tenant_id', '=', context.tenantId)\n .where('organization_id', '=', context.organizationId)\n .where('deleted_at', 'is', null)\n .execute()\n return new Map(rows.map((row) => [row.id, { id: row.id, orderId: row.order_id }]))\n } catch (err) {\n if (isMissingSalesTableError(err)) return new Map()\n throw err\n }\n}\n\nasync function validatePortalOrderOwnership(\n context: PortalContext,\n input: PortalIntakeInput,\n): Promise<OrderValidationResult | Response> {\n const { translate } = await resolveTranslations()\n const orderLineIds = Array.from(new Set(input.lines.flatMap((line) => line.orderLineId ? [line.orderLineId] : [])))\n const orderLinesById = await loadOwnedOrderLines(context, orderLineIds)\n const orderIds = new Set<string>()\n if (input.orderId) orderIds.add(input.orderId)\n for (const orderLine of orderLinesById.values()) orderIds.add(orderLine.orderId)\n const ordersById = await loadOwnedOrders(context, Array.from(orderIds))\n let resolvedOrder = input.orderId ? (ordersById.get(input.orderId) ?? null) : null\n if (input.orderId) {\n if (!resolvedOrder) {\n return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.orderNotOwned', 'Order not found.') }, { status: 404 })\n }\n }\n\n for (const line of input.lines) {\n if (!line.orderLineId) continue\n const resolvedLine = orderLinesById.get(line.orderLineId)\n const lineOrder = resolvedLine ? ordersById.get(resolvedLine.orderId) : null\n if (!resolvedLine || !lineOrder) {\n return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.orderLineMismatch', 'Order line does not belong to the selected order') }, { status: 404 })\n }\n if (resolvedOrder && lineOrder.id !== resolvedOrder.id) {\n return NextResponse.json(\n { ok: false, error: translate('warranty_claims.errors.orderLineMismatch', 'Order line does not belong to the selected order') },\n { status: 400 },\n )\n }\n if (!resolvedOrder) resolvedOrder = lineOrder\n }\n\n return {\n orderId: resolvedOrder?.id ?? input.orderId ?? null,\n currencyCode: resolvedOrder?.currencyCode ?? null,\n }\n}\n\nasync function runPortalCreateGuard(\n req: Request,\n context: PortalContext,\n mutationPayload: Record<string, unknown>,\n): Promise<RouteMutationGuardResult> {\n return runRouteMutationGuards({\n container: context.commandCtx.container,\n req,\n auth: {\n userId: context.auth.sub,\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n userFeatures: [],\n },\n input: {\n resourceKind: WARRANTY_CLAIM_RESOURCE_KIND,\n resourceId: null,\n operation: 'create',\n mutationPayload,\n },\n })\n}\n\nexport async function GET(req: Request) {\n const contextOrResponse = await resolvePortalContext(req)\n if (contextOrResponse instanceof Response) return contextOrResponse\n const context = contextOrResponse\n const url = new URL(req.url)\n const query = listQuerySchema.safeParse({\n page: url.searchParams.get('page') ?? undefined,\n pageSize: url.searchParams.get('pageSize') ?? undefined,\n search: url.searchParams.get('search') ?? undefined,\n status: url.searchParams.get('status') ?? undefined,\n stateGroup: url.searchParams.get('stateGroup') ?? undefined,\n serialNumber: url.searchParams.get('serialNumber') ?? undefined,\n })\n if (!query.success) {\n const { translate } = await resolveTranslations()\n return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.invalidInput', 'Invalid input') }, { status: 400 })\n }\n const { page, pageSize, search, status, stateGroup, serialNumber } = query.data\n const where = buildPortalOwnedClaimWhere(context)\n if (status) where.status = status\n else if (stateGroup) where.status = { $in: portalClaimStatusesForStateGroup(stateGroup) }\n if (search) {\n const pattern = `%${escapeLikePattern(search)}%`\n where.$or = [\n { claimNumber: { $ilike: pattern } },\n { orderNumber: { $ilike: pattern } },\n ]\n }\n if (serialNumber) {\n const matchingLines = await findWithDecryption(\n context.em,\n WarrantyClaimLine,\n {\n serialNumber,\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n deletedAt: null,\n claim: buildPortalOwnedClaimWhere(context),\n },\n {},\n { tenantId: context.tenantId, organizationId: context.organizationId },\n )\n const matchingClaimIds = Array.from(new Set(\n matchingLines.map((line) => relationId(line.claim)).filter((id): id is string => Boolean(id)),\n ))\n if (matchingClaimIds.length === 0) {\n return NextResponse.json({ items: [], total: 0, page, pageSize, totalPages: 1 })\n }\n where.id = { $in: matchingClaimIds }\n }\n const total = await context.em.count(WarrantyClaim, where)\n const claims = await findWithDecryption(\n context.em,\n WarrantyClaim,\n where,\n {\n orderBy: { createdAt: 'DESC' },\n limit: pageSize,\n offset: (page - 1) * pageSize,\n },\n { tenantId: context.tenantId, organizationId: context.organizationId },\n )\n const claimIds = claims.map((claim) => claim.id)\n const lines = claimIds.length\n ? await findWithDecryption(\n context.em,\n WarrantyClaimLine,\n { claim: { $in: claimIds }, tenantId: context.tenantId, organizationId: context.organizationId, deletedAt: null },\n {},\n { tenantId: context.tenantId, organizationId: context.organizationId },\n )\n : []\n const linesByClaim = new Map<string, WarrantyClaimLine[]>()\n for (const line of lines) {\n const claimId = relationId(line.claim)\n if (!claimId) continue\n const bucket = linesByClaim.get(claimId) ?? []\n bucket.push(line)\n linesByClaim.set(claimId, bucket)\n }\n return NextResponse.json({\n items: claims.map((claim) => serializePortalClaim(claim, linesByClaim.get(claim.id) ?? [])),\n total,\n page,\n pageSize,\n totalPages: Math.max(1, Math.ceil(total / pageSize)),\n })\n}\n\nexport async function POST(req: Request) {\n const contextOrResponse = await resolvePortalContext(req)\n if (contextOrResponse instanceof Response) return contextOrResponse\n const context = contextOrResponse\n const { translate } = await resolveTranslations()\n let body: unknown\n try {\n body = await req.json()\n } catch {\n return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.invalidInput', 'Invalid input') }, { status: 400 })\n }\n const parsed = portalIntakeInputSchema.safeParse(body)\n if (!parsed.success) {\n return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.invalidInput', 'Invalid input') }, { status: 400 })\n }\n const ownership = await validatePortalOrderOwnership(context, parsed.data)\n if (ownership instanceof Response) return ownership\n\n const createInput: ClaimCreateInput = {\n organizationId: context.organizationId,\n tenantId: context.tenantId,\n claimType: 'warranty',\n channel: 'portal',\n priority: 'normal',\n customerId: context.customerId,\n customerName: context.auth.displayName || null,\n orderId: ownership.orderId,\n // A resolved order supplies its own number; otherwise persist the typed reference (WQA-002).\n orderNumber: ownership.orderId ? null : (parsed.data.orderReference ?? null),\n reasonCode: parsed.data.reasonCode,\n notes: parsed.data.notes ?? null,\n currencyCode: ownership.currencyCode,\n lines: parsed.data.lines.map((line, index) => ({\n lineNo: index + 1,\n orderLineId: line.orderLineId ?? null,\n productId: line.productId ?? null,\n productName: line.productName ?? null,\n sku: line.sku ?? null,\n serialNumber: line.serialNumber ?? null,\n faultCode: line.faultCode ?? null,\n faultDescription: line.faultDescription,\n qtyClaimed: line.qtyClaimed ?? 1,\n // Carry warranty metadata so order-backed portal lines keep their entitlement basis\n // instead of persisting warrantyStatus='unknown' (WQA-004 / LINE-06).\n purchaseDate: line.purchaseDate ?? null,\n warrantyMonths: line.warrantyMonths ?? null,\n })),\n }\n\n const guarded = await runPortalCreateGuard(req, context, { ...createInput })\n if (!guarded.ok) {\n return guarded.response\n }\n const effectiveInput: ClaimCreateInput = guarded.modifiedPayload\n ? {\n ...createInput,\n ...guarded.modifiedPayload,\n tenantId: createInput.tenantId,\n organizationId: createInput.organizationId,\n customerId: createInput.customerId,\n channel: createInput.channel,\n claimType: createInput.claimType,\n orderId: createInput.orderId,\n orderNumber: createInput.orderNumber,\n currencyCode: createInput.currencyCode,\n lines: createInput.lines,\n }\n : createInput\n\n const commandBus = context.commandCtx.container.resolve('commandBus') as CommandBus\n try {\n const createResult = await commandBus.execute<ClaimCreateInput, { claimId: string }>(\n 'warranty_claims.claim.create',\n { input: effectiveInput, ctx: context.commandCtx },\n )\n const claimId = createResult.result?.claimId\n if (typeof claimId !== 'string') {\n return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.save_failed', 'Failed to save warranty claim.') }, { status: 500 })\n }\n try {\n await commandBus.execute<{ id: string; actorCustomerId: string }, { claimId: string }>(\n 'warranty_claims.claim.submit',\n { input: { id: claimId, actorCustomerId: context.customerId }, ctx: context.commandCtx },\n )\n } catch (submitError) {\n try {\n await commandBus.execute<{ id: string }, { claimId: string }>(\n 'warranty_claims.claim.delete',\n { input: { id: claimId }, ctx: context.commandCtx },\n )\n } catch (compensationError) {\n logger.error('warranty_claims.portal.claim.submit compensation failed \u2014 orphaned draft claim', {\n err: compensationError,\n claimId,\n customerId: context.customerId,\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n })\n }\n throw submitError\n }\n\n await guarded.runAfterSuccess()\n\n return NextResponse.json({ ok: true, claimId }, { status: 201 })\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n throw err\n }\n}\n\nconst lineSummarySchema = z.object({\n count: z.number().int().nonnegative(),\n creditTotal: z.string(),\n statuses: z.record(z.string(), z.number().int().nonnegative()),\n})\n\nconst portalClaimSchema = z.object({\n id: z.string().uuid(),\n claimNumber: z.string(),\n claimType: z.string(),\n status: z.string(),\n orderId: z.string().uuid().nullable(),\n orderNumber: z.string().nullable(),\n reasonCode: z.string().nullable(),\n resolutionSummary: z.string().nullable(),\n createdAt: z.string().nullable(),\n updatedAt: z.string().nullable(),\n lines: lineSummarySchema,\n})\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Warranty Claims Portal',\n summary: 'Customer portal warranty claims',\n methods: {\n GET: {\n summary: 'List the authenticated customer account claims',\n query: listQuerySchema,\n responses: [\n {\n status: 200,\n description: 'Customer claims',\n schema: z.object({\n items: z.array(portalClaimSchema),\n total: z.number().int().nonnegative(),\n page: z.number().int().min(1),\n pageSize: z.number().int().min(1),\n totalPages: z.number().int().min(1),\n }),\n },\n ],\n },\n POST: {\n summary: 'Submit a customer portal claim intake',\n requestBody: { contentType: 'application/json', schema: portalIntakeInputSchema },\n responses: [\n {\n status: 201,\n description: 'Claim created and submitted',\n schema: z.object({ ok: z.boolean(), claimId: z.string().uuid() }),\n },\n ],\n },\n },\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { AuthContext } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport type { CommandBus, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { getCommandInterceptorHttpRejection } from '@open-mercato/shared/lib/commands/errors'\nimport { escapeLikePattern } from '@open-mercato/shared/lib/db/escapeLikePattern'\nimport { runRouteMutationGuards, type RouteMutationGuardResult } from '@open-mercato/shared/lib/crud/route-mutation-guard'\nimport { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport type { CustomerAuthContext } from '@open-mercato/core/modules/customer_accounts/lib/customerAuth'\nimport { resolveLinkedCustomerAuth } from '../../../lib/portalAuthGuard'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { WarrantyClaim, WarrantyClaimLine } from '../../../data/entities'\nimport {\n claimStatusSchema,\n portalIntakeInputSchema,\n type ClaimCreateInput,\n type PortalIntakeInput,\n} from '../../../data/validators'\nimport { WARRANTY_CLAIM_RESOURCE_KIND } from '../../../commands/shared'\nimport { portalClaimStatusesForStateGroup } from '../../../lib/listSegments'\nimport { buildPortalOwnedClaimWhere } from '../../../lib/portalClaimAccess'\n\nexport const metadata = {\n GET: { requireAuth: false },\n POST: { requireAuth: false },\n}\n\nconst logger = createLogger('warranty_claims')\n\nconst emptyQueryValueToUndefined = (value: unknown): unknown => {\n if (typeof value !== 'string') return value\n const trimmed = value.trim()\n return trimmed.length ? trimmed : undefined\n}\n\nconst optionalTrimmedQueryString = (max: number) =>\n z.preprocess(emptyQueryValueToUndefined, z.string().trim().min(1).max(max).optional())\n\nconst listQuerySchema = z.object({\n page: z.coerce.number().int().min(1).default(1),\n pageSize: z.coerce.number().int().min(1).max(100).default(20),\n search: optionalTrimmedQueryString(190),\n status: z.preprocess(emptyQueryValueToUndefined, claimStatusSchema.optional()),\n stateGroup: z.enum(['open', 'resolved']).optional(),\n serialNumber: optionalTrimmedQueryString(191),\n})\n\ntype PortalContext = {\n auth: CustomerAuthContext\n customerId: string\n tenantId: string\n organizationId: string\n em: EntityManager\n commandCtx: CommandRuntimeContext\n}\n\ntype LineSummary = {\n count: number\n creditTotal: string\n statuses: Record<string, number>\n}\n\ntype OrderValidationResult = {\n orderId: string | null\n currencyCode: string | null\n}\n\ntype PortalOrderLookup = {\n id: string\n currencyCode: string | null\n}\n\ntype PortalOrderLineLookup = {\n id: string\n orderId: string\n}\n\nfunction toRecord(value: unknown): Record<string, unknown> {\n return value && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : {}\n}\n\nfunction stringField(record: Record<string, unknown>, key: string): string | null {\n const value = record[key]\n return typeof value === 'string' ? value : null\n}\n\nfunction toIso(value: Date | string | null | undefined): string | null {\n if (!value) return null\n if (value instanceof Date) return value.toISOString()\n const date = new Date(value)\n return Number.isNaN(date.getTime()) ? value : date.toISOString()\n}\n\nfunction relationId(value: unknown): string | null {\n if (typeof value === 'string') return value\n const record = toRecord(value)\n return typeof record.id === 'string' ? record.id : null\n}\n\nconst CREDIT_SCALE = 10_000n\n\nfunction decimalUnits(value: unknown): bigint {\n const normalized = typeof value === 'number'\n ? (Number.isFinite(value) ? value.toFixed(4) : '')\n : typeof value === 'string' ? value.trim() : ''\n const match = /^([+-]?)(\\d+)(?:\\.(\\d+))?$/.exec(normalized)\n if (!match) return 0n\n const [, sign, whole, fraction = ''] = match\n let units = BigInt(whole) * CREDIT_SCALE + BigInt(fraction.slice(0, 4).padEnd(4, '0'))\n if (fraction.length > 4 && fraction[4] >= '5') units += 1n\n return sign === '-' ? -units : units\n}\n\nfunction decimalString(value: bigint): string {\n const sign = value < 0n ? '-' : ''\n const absolute = value < 0n ? -value : value\n const whole = absolute / CREDIT_SCALE\n const fraction = String(absolute % CREDIT_SCALE).padStart(4, '0').replace(/0+$/, '')\n return `${sign}${whole}${fraction ? `.${fraction}` : ''}`\n}\n\nfunction summarizeLines(lines: WarrantyClaimLine[]): LineSummary {\n let creditTotal = 0n\n const statuses: Record<string, number> = {}\n for (const line of lines) {\n creditTotal += decimalUnits(line.creditAmount)\n statuses[line.lineStatus] = (statuses[line.lineStatus] ?? 0) + 1\n }\n return {\n count: lines.length,\n creditTotal: decimalString(creditTotal),\n statuses,\n }\n}\n\nfunction serializePortalClaim(claim: WarrantyClaim, lines: WarrantyClaimLine[]) {\n return {\n id: claim.id,\n claimNumber: claim.claimNumber,\n claimType: claim.claimType,\n status: claim.status,\n orderId: claim.orderId ?? null,\n orderNumber: claim.orderNumber ?? null,\n reasonCode: claim.reasonCode ?? null,\n resolutionSummary: claim.resolutionSummary ?? null,\n createdAt: toIso(claim.createdAt),\n updatedAt: toIso(claim.updatedAt),\n lines: summarizeLines(lines),\n }\n}\n\nasync function resolvePortalContext(req: Request): Promise<PortalContext | Response> {\n const authOrResponse = await resolveLinkedCustomerAuth(req)\n if (authOrResponse instanceof Response) return authOrResponse\n const auth = authOrResponse\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n const commandAuth: NonNullable<AuthContext> = {\n sub: auth.sub,\n sid: auth.sid,\n tenantId: auth.tenantId,\n orgId: auth.orgId,\n email: auth.email,\n customerEntityId: auth.customerEntityId ?? null,\n personEntityId: auth.personEntityId ?? null,\n }\n return {\n auth,\n customerId: auth.customerEntityId,\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n em,\n commandCtx: {\n container,\n auth: commandAuth,\n organizationScope: null,\n selectedOrganizationId: auth.orgId,\n organizationIds: [auth.orgId],\n request: req,\n },\n }\n}\n\ntype PortalSalesDb = {\n sales_orders: {\n id: string\n currency_code: string\n customer_entity_id: string | null\n tenant_id: string | null\n organization_id: string | null\n deleted_at: Date | null\n }\n sales_order_lines: {\n id: string\n order_id: string\n tenant_id: string | null\n organization_id: string | null\n deleted_at: Date | null\n }\n}\n\nfunction isMissingSalesTableError(err: unknown): boolean {\n if (typeof err !== 'object' || err === null) return false\n const candidate = err as { code?: unknown; message?: unknown }\n return candidate.code === '42P01'\n || (typeof candidate.message === 'string'\n && candidate.message.includes('does not exist')\n && (candidate.message.includes('sales_orders') || candidate.message.includes('sales_order_lines')))\n}\n\nasync function loadOwnedOrders(\n context: PortalContext,\n orderIds: string[],\n): Promise<Map<string, PortalOrderLookup>> {\n if (orderIds.length === 0) return new Map()\n try {\n const db = context.em.fork().getKysely<PortalSalesDb>()\n const rows = await db\n .selectFrom('sales_orders')\n .select(['id', 'currency_code'])\n .where('id', 'in', orderIds)\n .where('tenant_id', '=', context.tenantId)\n .where('organization_id', '=', context.organizationId)\n .where('customer_entity_id', '=', context.customerId)\n .where('deleted_at', 'is', null)\n .execute()\n return new Map(rows.map((row) => [row.id, { id: row.id, currencyCode: row.currency_code ?? null }]))\n } catch (err) {\n if (isMissingSalesTableError(err)) return new Map()\n throw err\n }\n}\n\nasync function loadOwnedOrderLines(\n context: PortalContext,\n orderLineIds: string[],\n): Promise<Map<string, PortalOrderLineLookup>> {\n if (orderLineIds.length === 0) return new Map()\n try {\n const db = context.em.fork().getKysely<PortalSalesDb>()\n const rows = await db\n .selectFrom('sales_order_lines')\n .select(['id', 'order_id'])\n .where('id', 'in', orderLineIds)\n .where('tenant_id', '=', context.tenantId)\n .where('organization_id', '=', context.organizationId)\n .where('deleted_at', 'is', null)\n .execute()\n return new Map(rows.map((row) => [row.id, { id: row.id, orderId: row.order_id }]))\n } catch (err) {\n if (isMissingSalesTableError(err)) return new Map()\n throw err\n }\n}\n\nasync function validatePortalOrderOwnership(\n context: PortalContext,\n input: PortalIntakeInput,\n): Promise<OrderValidationResult | Response> {\n const { translate } = await resolveTranslations()\n const orderLineIds = Array.from(new Set(input.lines.flatMap((line) => line.orderLineId ? [line.orderLineId] : [])))\n const orderLinesById = await loadOwnedOrderLines(context, orderLineIds)\n const orderIds = new Set<string>()\n if (input.orderId) orderIds.add(input.orderId)\n for (const orderLine of orderLinesById.values()) orderIds.add(orderLine.orderId)\n const ordersById = await loadOwnedOrders(context, Array.from(orderIds))\n let resolvedOrder = input.orderId ? (ordersById.get(input.orderId) ?? null) : null\n if (input.orderId) {\n if (!resolvedOrder) {\n return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.orderNotOwned', 'Order not found.') }, { status: 404 })\n }\n }\n\n for (const line of input.lines) {\n if (!line.orderLineId) continue\n const resolvedLine = orderLinesById.get(line.orderLineId)\n const lineOrder = resolvedLine ? ordersById.get(resolvedLine.orderId) : null\n if (!resolvedLine || !lineOrder) {\n return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.orderLineMismatch', 'Order line does not belong to the selected order') }, { status: 404 })\n }\n if (resolvedOrder && lineOrder.id !== resolvedOrder.id) {\n return NextResponse.json(\n { ok: false, error: translate('warranty_claims.errors.orderLineMismatch', 'Order line does not belong to the selected order') },\n { status: 400 },\n )\n }\n if (!resolvedOrder) resolvedOrder = lineOrder\n }\n\n return {\n orderId: resolvedOrder?.id ?? input.orderId ?? null,\n currencyCode: resolvedOrder?.currencyCode ?? null,\n }\n}\n\nasync function runPortalCreateGuard(\n req: Request,\n context: PortalContext,\n mutationPayload: Record<string, unknown>,\n): Promise<RouteMutationGuardResult> {\n return runRouteMutationGuards({\n container: context.commandCtx.container,\n req,\n auth: {\n userId: context.auth.sub,\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n userFeatures: [],\n },\n input: {\n resourceKind: WARRANTY_CLAIM_RESOURCE_KIND,\n resourceId: null,\n operation: 'create',\n mutationPayload,\n },\n })\n}\n\nexport async function GET(req: Request) {\n const contextOrResponse = await resolvePortalContext(req)\n if (contextOrResponse instanceof Response) return contextOrResponse\n const context = contextOrResponse\n const url = new URL(req.url)\n const query = listQuerySchema.safeParse({\n page: url.searchParams.get('page') ?? undefined,\n pageSize: url.searchParams.get('pageSize') ?? undefined,\n search: url.searchParams.get('search') ?? undefined,\n status: url.searchParams.get('status') ?? undefined,\n stateGroup: url.searchParams.get('stateGroup') ?? undefined,\n serialNumber: url.searchParams.get('serialNumber') ?? undefined,\n })\n if (!query.success) {\n const { translate } = await resolveTranslations()\n return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.invalidInput', 'Invalid input') }, { status: 400 })\n }\n const { page, pageSize, search, status, stateGroup, serialNumber } = query.data\n const where = buildPortalOwnedClaimWhere(context)\n if (status) where.status = status\n else if (stateGroup) where.status = { $in: portalClaimStatusesForStateGroup(stateGroup) }\n if (search) {\n const pattern = `%${escapeLikePattern(search)}%`\n where.$or = [\n { claimNumber: { $ilike: pattern } },\n { orderNumber: { $ilike: pattern } },\n ]\n }\n if (serialNumber) {\n const matchingLines = await findWithDecryption(\n context.em,\n WarrantyClaimLine,\n {\n serialNumber,\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n deletedAt: null,\n claim: buildPortalOwnedClaimWhere(context),\n },\n {},\n { tenantId: context.tenantId, organizationId: context.organizationId },\n )\n const matchingClaimIds = Array.from(new Set(\n matchingLines.map((line) => relationId(line.claim)).filter((id): id is string => Boolean(id)),\n ))\n if (matchingClaimIds.length === 0) {\n return NextResponse.json({ items: [], total: 0, page, pageSize, totalPages: 1 })\n }\n where.id = { $in: matchingClaimIds }\n }\n const total = await context.em.count(WarrantyClaim, where)\n const claims = await findWithDecryption(\n context.em,\n WarrantyClaim,\n where,\n {\n orderBy: { createdAt: 'DESC' },\n limit: pageSize,\n offset: (page - 1) * pageSize,\n },\n { tenantId: context.tenantId, organizationId: context.organizationId },\n )\n const claimIds = claims.map((claim) => claim.id)\n const lines = claimIds.length\n ? await findWithDecryption(\n context.em,\n WarrantyClaimLine,\n { claim: { $in: claimIds }, tenantId: context.tenantId, organizationId: context.organizationId, deletedAt: null },\n {},\n { tenantId: context.tenantId, organizationId: context.organizationId },\n )\n : []\n const linesByClaim = new Map<string, WarrantyClaimLine[]>()\n for (const line of lines) {\n const claimId = relationId(line.claim)\n if (!claimId) continue\n const bucket = linesByClaim.get(claimId) ?? []\n bucket.push(line)\n linesByClaim.set(claimId, bucket)\n }\n return NextResponse.json({\n items: claims.map((claim) => serializePortalClaim(claim, linesByClaim.get(claim.id) ?? [])),\n total,\n page,\n pageSize,\n totalPages: Math.max(1, Math.ceil(total / pageSize)),\n })\n}\n\nexport async function POST(req: Request) {\n const contextOrResponse = await resolvePortalContext(req)\n if (contextOrResponse instanceof Response) return contextOrResponse\n const context = contextOrResponse\n const { translate } = await resolveTranslations()\n let body: unknown\n try {\n body = await req.json()\n } catch {\n return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.invalidInput', 'Invalid input') }, { status: 400 })\n }\n const parsed = portalIntakeInputSchema.safeParse(body)\n if (!parsed.success) {\n return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.invalidInput', 'Invalid input') }, { status: 400 })\n }\n const ownership = await validatePortalOrderOwnership(context, parsed.data)\n if (ownership instanceof Response) return ownership\n\n const createInput: ClaimCreateInput = {\n organizationId: context.organizationId,\n tenantId: context.tenantId,\n claimType: 'warranty',\n channel: 'portal',\n priority: 'normal',\n customerId: context.customerId,\n customerName: context.auth.displayName || null,\n orderId: ownership.orderId,\n // A resolved order supplies its own number; otherwise persist the typed reference (WQA-002).\n orderNumber: ownership.orderId ? null : (parsed.data.orderReference ?? null),\n reasonCode: parsed.data.reasonCode,\n notes: parsed.data.notes ?? null,\n currencyCode: ownership.currencyCode,\n lines: parsed.data.lines.map((line, index) => ({\n lineNo: index + 1,\n orderLineId: line.orderLineId ?? null,\n productId: line.productId ?? null,\n productName: line.productName ?? null,\n sku: line.sku ?? null,\n serialNumber: line.serialNumber ?? null,\n faultCode: line.faultCode ?? null,\n faultDescription: line.faultDescription,\n qtyClaimed: line.qtyClaimed ?? 1,\n // Carry warranty metadata so order-backed portal lines keep their entitlement basis\n // instead of persisting warrantyStatus='unknown' (WQA-004 / LINE-06).\n purchaseDate: line.purchaseDate ?? null,\n warrantyMonths: line.warrantyMonths ?? null,\n })),\n }\n\n const guarded = await runPortalCreateGuard(req, context, { ...createInput })\n if (!guarded.ok) {\n return guarded.response\n }\n const effectiveInput: ClaimCreateInput = guarded.modifiedPayload\n ? {\n ...createInput,\n ...guarded.modifiedPayload,\n tenantId: createInput.tenantId,\n organizationId: createInput.organizationId,\n customerId: createInput.customerId,\n channel: createInput.channel,\n claimType: createInput.claimType,\n orderId: createInput.orderId,\n orderNumber: createInput.orderNumber,\n currencyCode: createInput.currencyCode,\n lines: createInput.lines,\n }\n : createInput\n\n const commandBus = context.commandCtx.container.resolve('commandBus') as CommandBus\n try {\n const createResult = await commandBus.execute<ClaimCreateInput, { claimId: string }>(\n 'warranty_claims.claim.create',\n { input: effectiveInput, ctx: context.commandCtx },\n )\n const claimId = createResult.result?.claimId\n if (typeof claimId !== 'string') {\n return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.save_failed', 'Failed to save warranty claim.') }, { status: 500 })\n }\n try {\n await commandBus.execute<{ id: string; actorCustomerId: string }, { claimId: string }>(\n 'warranty_claims.claim.submit',\n { input: { id: claimId, actorCustomerId: context.customerId }, ctx: context.commandCtx },\n )\n } catch (submitError) {\n try {\n await commandBus.execute<{ id: string }, { claimId: string }>(\n 'warranty_claims.claim.delete',\n { input: { id: claimId }, ctx: context.commandCtx },\n )\n } catch (compensationError) {\n logger.error('warranty_claims.portal.claim.submit compensation failed \u2014 orphaned draft claim', {\n err: compensationError,\n claimId,\n customerId: context.customerId,\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n })\n }\n throw submitError\n }\n\n await guarded.runAfterSuccess()\n\n return NextResponse.json({ ok: true, claimId }, { status: 201 })\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n const interceptorRejection = getCommandInterceptorHttpRejection(err)\n if (interceptorRejection) {\n return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status })\n }\n throw err\n }\n}\n\nconst lineSummarySchema = z.object({\n count: z.number().int().nonnegative(),\n creditTotal: z.string(),\n statuses: z.record(z.string(), z.number().int().nonnegative()),\n})\n\nconst portalClaimSchema = z.object({\n id: z.string().uuid(),\n claimNumber: z.string(),\n claimType: z.string(),\n status: z.string(),\n orderId: z.string().uuid().nullable(),\n orderNumber: z.string().nullable(),\n reasonCode: z.string().nullable(),\n resolutionSummary: z.string().nullable(),\n createdAt: z.string().nullable(),\n updatedAt: z.string().nullable(),\n lines: lineSummarySchema,\n})\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Warranty Claims Portal',\n summary: 'Customer portal warranty claims',\n methods: {\n GET: {\n summary: 'List the authenticated customer account claims',\n query: listQuerySchema,\n responses: [\n {\n status: 200,\n description: 'Customer claims',\n schema: z.object({\n items: z.array(portalClaimSchema),\n total: z.number().int().nonnegative(),\n page: z.number().int().min(1),\n pageSize: z.number().int().min(1),\n totalPages: z.number().int().min(1),\n }),\n },\n ],\n },\n POST: {\n summary: 'Submit a customer portal claim intake',\n requestBody: { contentType: 'application/json', schema: portalIntakeInputSchema },\n responses: [\n {\n status: 201,\n description: 'Claim created and submitted',\n schema: z.object({ ok: z.boolean(), claimId: z.string().uuid() }),\n },\n ],\n },\n },\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -2,12 +2,14 @@ import { NextResponse } from "next/server";
2
2
  import { z } from "zod";
3
3
  import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
4
4
  import { isCrudHttpError } from "@open-mercato/shared/lib/crud/errors";
5
+ import { getCommandInterceptorHttpRejection } from "@open-mercato/shared/lib/commands/errors";
5
6
  import { runRouteMutationGuards } from "@open-mercato/shared/lib/crud/route-mutation-guard";
7
+ import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
6
8
  import { findWithDecryption } from "@open-mercato/shared/lib/encryption/find";
7
- import { getCustomerAuthFromRequest } from "@open-mercato/core/modules/customer_accounts/lib/customerAuth";
8
9
  import { WarrantyClaimEvent } from "../../../data/entities.js";
9
10
  import { WARRANTY_CLAIM_RESOURCE_KIND } from "../../../commands/shared.js";
10
11
  import { loadPortalOwnedClaim } from "../../../lib/portalClaimAccess.js";
12
+ import { resolveLinkedCustomerAuth } from "../../../lib/portalAuthGuard.js";
11
13
  const metadata = {
12
14
  GET: { requireAuth: false },
13
15
  POST: { requireAuth: false }
@@ -46,13 +48,9 @@ function serializeEvent(event) {
46
48
  };
47
49
  }
48
50
  async function resolvePortalContext(req) {
49
- const auth = await getCustomerAuthFromRequest(req);
50
- if (!auth) {
51
- return NextResponse.json({ ok: false, error: "warranty_claims.errors.unauthorized" }, { status: 401 });
52
- }
53
- if (!auth.customerEntityId) {
54
- return NextResponse.json({ ok: false, error: "warranty_claims.errors.customerAccountNotLinked" }, { status: 403 });
55
- }
51
+ const authOrResponse = await resolveLinkedCustomerAuth(req);
52
+ if (authOrResponse instanceof Response) return authOrResponse;
53
+ const auth = authOrResponse;
56
54
  const container = await createRequestContainer();
57
55
  const em = container.resolve("em");
58
56
  const commandAuth = {
@@ -113,14 +111,15 @@ async function GET(req) {
113
111
  const contextOrResponse = await resolvePortalContext(req);
114
112
  if (contextOrResponse instanceof Response) return contextOrResponse;
115
113
  const context = contextOrResponse;
114
+ const { translate } = await resolveTranslations();
116
115
  const url = new URL(req.url);
117
116
  const parsed = eventQuerySchema.safeParse({ claimId: url.searchParams.get("claimId") ?? void 0 });
118
117
  if (!parsed.success) {
119
- return NextResponse.json({ ok: false, error: "warranty_claims.errors.invalidInput" }, { status: 400 });
118
+ return NextResponse.json({ ok: false, error: translate("warranty_claims.errors.invalidInput", "Invalid input") }, { status: 400 });
120
119
  }
121
120
  const claim = await loadOwnedClaim(context, parsed.data.claimId);
122
121
  if (!claim) {
123
- return NextResponse.json({ ok: false, error: "warranty_claims.errors.notFound" }, { status: 404 });
122
+ return NextResponse.json({ ok: false, error: translate("warranty_claims.errors.notFound", "Claim not found.") }, { status: 404 });
124
123
  }
125
124
  const events = await findWithDecryption(
126
125
  context.em,
@@ -135,19 +134,20 @@ async function POST(req) {
135
134
  const contextOrResponse = await resolvePortalContext(req);
136
135
  if (contextOrResponse instanceof Response) return contextOrResponse;
137
136
  const context = contextOrResponse;
137
+ const { translate } = await resolveTranslations();
138
138
  let body;
139
139
  try {
140
140
  body = await req.json();
141
141
  } catch {
142
- return NextResponse.json({ ok: false, error: "warranty_claims.errors.invalidInput" }, { status: 400 });
142
+ return NextResponse.json({ ok: false, error: translate("warranty_claims.errors.invalidInput", "Invalid input") }, { status: 400 });
143
143
  }
144
144
  const parsed = portalCommentSchema.safeParse(body);
145
145
  if (!parsed.success) {
146
- return NextResponse.json({ ok: false, error: "warranty_claims.errors.invalidInput" }, { status: 400 });
146
+ return NextResponse.json({ ok: false, error: translate("warranty_claims.errors.invalidInput", "Invalid input") }, { status: 400 });
147
147
  }
148
148
  const claim = await loadOwnedClaim(context, parsed.data.claimId);
149
149
  if (!claim) {
150
- return NextResponse.json({ ok: false, error: "warranty_claims.errors.notFound" }, { status: 404 });
150
+ return NextResponse.json({ ok: false, error: translate("warranty_claims.errors.notFound", "Claim not found.") }, { status: 404 });
151
151
  }
152
152
  const input = {
153
153
  claimId: claim.id,
@@ -177,6 +177,10 @@ async function POST(req) {
177
177
  if (isCrudHttpError(err)) {
178
178
  return NextResponse.json(err.body, { status: err.status });
179
179
  }
180
+ const interceptorRejection = getCommandInterceptorHttpRejection(err);
181
+ if (interceptorRejection) {
182
+ return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status });
183
+ }
180
184
  throw err;
181
185
  }
182
186
  return NextResponse.json({ ok: true, claimId: claim.id });
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/warranty_claims/api/portal/events/route.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { AuthContext } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport type { CommandBus, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { runRouteMutationGuards, type RouteMutationGuardResult } from '@open-mercato/shared/lib/crud/route-mutation-guard'\nimport { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { getCustomerAuthFromRequest, type CustomerAuthContext } from '@open-mercato/core/modules/customer_accounts/lib/customerAuth'\nimport { WarrantyClaim, WarrantyClaimEvent } from '../../../data/entities'\nimport type { CommentClaimInput } from '../../../data/validators'\nimport { WARRANTY_CLAIM_RESOURCE_KIND } from '../../../commands/shared'\nimport { loadPortalOwnedClaim } from '../../../lib/portalClaimAccess'\n\nexport const metadata = {\n GET: { requireAuth: false },\n POST: { requireAuth: false },\n}\n\nconst eventQuerySchema = z.object({\n claimId: z.string().uuid(),\n})\n\nconst portalCommentSchema = z\n .object({\n claimId: z.string().uuid(),\n body: z.string().trim().min(1).max(8000),\n })\n .strict()\n\ntype PortalContext = {\n auth: CustomerAuthContext\n customerId: string\n tenantId: string\n organizationId: string\n em: EntityManager\n commandCtx: CommandRuntimeContext\n}\n\nfunction toRecord(value: unknown): Record<string, unknown> {\n return value && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : {}\n}\n\nfunction relationId(value: unknown): string | null {\n if (typeof value === 'string') return value\n const record = toRecord(value)\n return typeof record.id === 'string' ? record.id : null\n}\n\nfunction toIso(value: Date | string | null | undefined): string | null {\n if (!value) return null\n if (value instanceof Date) return value.toISOString()\n const date = new Date(value)\n return Number.isNaN(date.getTime()) ? value : date.toISOString()\n}\n\nfunction serializeEvent(event: WarrantyClaimEvent) {\n return {\n id: event.id,\n claimId: relationId(event.claim),\n kind: event.kind,\n visibility: event.visibility,\n body: event.body ?? null,\n payload: event.payload ?? null,\n actorCustomerId: event.actorCustomerId ?? null,\n createdAt: toIso(event.createdAt),\n }\n}\n\nasync function resolvePortalContext(req: Request): Promise<PortalContext | Response> {\n const auth = await getCustomerAuthFromRequest(req)\n if (!auth) {\n return NextResponse.json({ ok: false, error: 'warranty_claims.errors.unauthorized' }, { status: 401 })\n }\n if (!auth.customerEntityId) {\n return NextResponse.json({ ok: false, error: 'warranty_claims.errors.customerAccountNotLinked' }, { status: 403 })\n }\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n const commandAuth: NonNullable<AuthContext> = {\n sub: auth.sub,\n sid: auth.sid,\n tenantId: auth.tenantId,\n orgId: auth.orgId,\n email: auth.email,\n customerEntityId: auth.customerEntityId ?? null,\n personEntityId: auth.personEntityId ?? null,\n }\n return {\n auth,\n customerId: auth.customerEntityId,\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n em,\n commandCtx: {\n container,\n auth: commandAuth,\n organizationScope: null,\n selectedOrganizationId: auth.orgId,\n organizationIds: [auth.orgId],\n request: req,\n },\n }\n}\n\nasync function loadOwnedClaim(context: PortalContext, claimId: string): Promise<WarrantyClaim | null> {\n return loadPortalOwnedClaim(\n context.em,\n {\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n customerId: context.customerId,\n },\n claimId,\n )\n}\n\nasync function runPortalCommentGuard(\n req: Request,\n context: PortalContext,\n claimId: string,\n mutationPayload: Record<string, unknown>,\n): Promise<RouteMutationGuardResult> {\n return runRouteMutationGuards({\n container: context.commandCtx.container,\n req,\n auth: {\n userId: context.auth.sub,\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n userFeatures: [],\n },\n input: {\n resourceKind: WARRANTY_CLAIM_RESOURCE_KIND,\n resourceId: claimId,\n operation: 'create',\n mutationPayload,\n },\n })\n}\n\nexport async function GET(req: Request) {\n const contextOrResponse = await resolvePortalContext(req)\n if (contextOrResponse instanceof Response) return contextOrResponse\n const context = contextOrResponse\n const url = new URL(req.url)\n const parsed = eventQuerySchema.safeParse({ claimId: url.searchParams.get('claimId') ?? undefined })\n if (!parsed.success) {\n return NextResponse.json({ ok: false, error: 'warranty_claims.errors.invalidInput' }, { status: 400 })\n }\n const claim = await loadOwnedClaim(context, parsed.data.claimId)\n if (!claim) {\n return NextResponse.json({ ok: false, error: 'warranty_claims.errors.notFound' }, { status: 404 })\n }\n const events = await findWithDecryption(\n context.em,\n WarrantyClaimEvent,\n { claim: claim.id, visibility: 'customer', tenantId: context.tenantId, organizationId: context.organizationId },\n { orderBy: { createdAt: 'ASC' } },\n { tenantId: context.tenantId, organizationId: context.organizationId },\n )\n return NextResponse.json({ items: events.map(serializeEvent) })\n}\n\nexport async function POST(req: Request) {\n const contextOrResponse = await resolvePortalContext(req)\n if (contextOrResponse instanceof Response) return contextOrResponse\n const context = contextOrResponse\n let body: unknown\n try {\n body = await req.json()\n } catch {\n return NextResponse.json({ ok: false, error: 'warranty_claims.errors.invalidInput' }, { status: 400 })\n }\n const parsed = portalCommentSchema.safeParse(body)\n if (!parsed.success) {\n return NextResponse.json({ ok: false, error: 'warranty_claims.errors.invalidInput' }, { status: 400 })\n }\n const claim = await loadOwnedClaim(context, parsed.data.claimId)\n if (!claim) {\n return NextResponse.json({ ok: false, error: 'warranty_claims.errors.notFound' }, { status: 404 })\n }\n const input: CommentClaimInput = {\n claimId: claim.id,\n body: parsed.data.body,\n visibility: 'customer',\n actorCustomerId: context.customerId,\n }\n const guarded = await runPortalCommentGuard(req, context, claim.id, { ...input })\n if (!guarded.ok) {\n return guarded.response\n }\n const effectiveInput: CommentClaimInput = guarded.modifiedPayload\n ? {\n ...input,\n ...guarded.modifiedPayload,\n claimId: input.claimId,\n visibility: input.visibility,\n actorCustomerId: input.actorCustomerId,\n }\n : input\n\n const commandBus = context.commandCtx.container.resolve('commandBus') as CommandBus\n try {\n await commandBus.execute<CommentClaimInput, { claimId: string }>(\n 'warranty_claims.claim.comment',\n { input: effectiveInput, ctx: context.commandCtx },\n )\n await guarded.runAfterSuccess()\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n throw err\n }\n\n return NextResponse.json({ ok: true, claimId: claim.id })\n}\n\nconst eventSchema = z.object({\n id: z.string().uuid(),\n claimId: z.string().uuid().nullable(),\n kind: z.string(),\n visibility: z.literal('customer'),\n body: z.string().nullable(),\n payload: z.record(z.string(), z.unknown()).nullable(),\n actorCustomerId: z.string().uuid().nullable(),\n createdAt: z.string().nullable(),\n})\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Warranty Claims Portal',\n summary: 'Customer portal warranty claim timeline',\n methods: {\n GET: {\n summary: 'List customer-visible timeline events for an owned claim',\n query: eventQuerySchema,\n responses: [\n {\n status: 200,\n description: 'Customer-visible timeline events',\n schema: z.object({ items: z.array(eventSchema) }),\n },\n ],\n },\n POST: {\n summary: 'Append a customer-visible customer comment',\n requestBody: { contentType: 'application/json', schema: portalCommentSchema },\n responses: [\n {\n status: 200,\n description: 'Comment appended',\n schema: z.object({ ok: z.boolean(), claimId: z.string().uuid() }),\n },\n ],\n },\n },\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { AuthContext } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport type { CommandBus, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { getCommandInterceptorHttpRejection } from '@open-mercato/shared/lib/commands/errors'\nimport { runRouteMutationGuards, type RouteMutationGuardResult } from '@open-mercato/shared/lib/crud/route-mutation-guard'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport type { CustomerAuthContext } from '@open-mercato/core/modules/customer_accounts/lib/customerAuth'\nimport { WarrantyClaim, WarrantyClaimEvent } from '../../../data/entities'\nimport type { CommentClaimInput } from '../../../data/validators'\nimport { WARRANTY_CLAIM_RESOURCE_KIND } from '../../../commands/shared'\nimport { loadPortalOwnedClaim } from '../../../lib/portalClaimAccess'\nimport { resolveLinkedCustomerAuth } from '../../../lib/portalAuthGuard'\n\nexport const metadata = {\n GET: { requireAuth: false },\n POST: { requireAuth: false },\n}\n\nconst eventQuerySchema = z.object({\n claimId: z.string().uuid(),\n})\n\nconst portalCommentSchema = z\n .object({\n claimId: z.string().uuid(),\n body: z.string().trim().min(1).max(8000),\n })\n .strict()\n\ntype PortalContext = {\n auth: CustomerAuthContext\n customerId: string\n tenantId: string\n organizationId: string\n em: EntityManager\n commandCtx: CommandRuntimeContext\n}\n\nfunction toRecord(value: unknown): Record<string, unknown> {\n return value && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : {}\n}\n\nfunction relationId(value: unknown): string | null {\n if (typeof value === 'string') return value\n const record = toRecord(value)\n return typeof record.id === 'string' ? record.id : null\n}\n\nfunction toIso(value: Date | string | null | undefined): string | null {\n if (!value) return null\n if (value instanceof Date) return value.toISOString()\n const date = new Date(value)\n return Number.isNaN(date.getTime()) ? value : date.toISOString()\n}\n\nfunction serializeEvent(event: WarrantyClaimEvent) {\n return {\n id: event.id,\n claimId: relationId(event.claim),\n kind: event.kind,\n visibility: event.visibility,\n body: event.body ?? null,\n payload: event.payload ?? null,\n actorCustomerId: event.actorCustomerId ?? null,\n createdAt: toIso(event.createdAt),\n }\n}\n\nasync function resolvePortalContext(req: Request): Promise<PortalContext | Response> {\n const authOrResponse = await resolveLinkedCustomerAuth(req)\n if (authOrResponse instanceof Response) return authOrResponse\n const auth = authOrResponse\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n const commandAuth: NonNullable<AuthContext> = {\n sub: auth.sub,\n sid: auth.sid,\n tenantId: auth.tenantId,\n orgId: auth.orgId,\n email: auth.email,\n customerEntityId: auth.customerEntityId ?? null,\n personEntityId: auth.personEntityId ?? null,\n }\n return {\n auth,\n customerId: auth.customerEntityId,\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n em,\n commandCtx: {\n container,\n auth: commandAuth,\n organizationScope: null,\n selectedOrganizationId: auth.orgId,\n organizationIds: [auth.orgId],\n request: req,\n },\n }\n}\n\nasync function loadOwnedClaim(context: PortalContext, claimId: string): Promise<WarrantyClaim | null> {\n return loadPortalOwnedClaim(\n context.em,\n {\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n customerId: context.customerId,\n },\n claimId,\n )\n}\n\nasync function runPortalCommentGuard(\n req: Request,\n context: PortalContext,\n claimId: string,\n mutationPayload: Record<string, unknown>,\n): Promise<RouteMutationGuardResult> {\n return runRouteMutationGuards({\n container: context.commandCtx.container,\n req,\n auth: {\n userId: context.auth.sub,\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n userFeatures: [],\n },\n input: {\n resourceKind: WARRANTY_CLAIM_RESOURCE_KIND,\n resourceId: claimId,\n operation: 'create',\n mutationPayload,\n },\n })\n}\n\nexport async function GET(req: Request) {\n const contextOrResponse = await resolvePortalContext(req)\n if (contextOrResponse instanceof Response) return contextOrResponse\n const context = contextOrResponse\n const { translate } = await resolveTranslations()\n const url = new URL(req.url)\n const parsed = eventQuerySchema.safeParse({ claimId: url.searchParams.get('claimId') ?? undefined })\n if (!parsed.success) {\n return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.invalidInput', 'Invalid input') }, { status: 400 })\n }\n const claim = await loadOwnedClaim(context, parsed.data.claimId)\n if (!claim) {\n return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.notFound', 'Claim not found.') }, { status: 404 })\n }\n const events = await findWithDecryption(\n context.em,\n WarrantyClaimEvent,\n { claim: claim.id, visibility: 'customer', tenantId: context.tenantId, organizationId: context.organizationId },\n { orderBy: { createdAt: 'ASC' } },\n { tenantId: context.tenantId, organizationId: context.organizationId },\n )\n return NextResponse.json({ items: events.map(serializeEvent) })\n}\n\nexport async function POST(req: Request) {\n const contextOrResponse = await resolvePortalContext(req)\n if (contextOrResponse instanceof Response) return contextOrResponse\n const context = contextOrResponse\n const { translate } = await resolveTranslations()\n let body: unknown\n try {\n body = await req.json()\n } catch {\n return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.invalidInput', 'Invalid input') }, { status: 400 })\n }\n const parsed = portalCommentSchema.safeParse(body)\n if (!parsed.success) {\n return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.invalidInput', 'Invalid input') }, { status: 400 })\n }\n const claim = await loadOwnedClaim(context, parsed.data.claimId)\n if (!claim) {\n return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.notFound', 'Claim not found.') }, { status: 404 })\n }\n const input: CommentClaimInput = {\n claimId: claim.id,\n body: parsed.data.body,\n visibility: 'customer',\n actorCustomerId: context.customerId,\n }\n const guarded = await runPortalCommentGuard(req, context, claim.id, { ...input })\n if (!guarded.ok) {\n return guarded.response\n }\n const effectiveInput: CommentClaimInput = guarded.modifiedPayload\n ? {\n ...input,\n ...guarded.modifiedPayload,\n claimId: input.claimId,\n visibility: input.visibility,\n actorCustomerId: input.actorCustomerId,\n }\n : input\n\n const commandBus = context.commandCtx.container.resolve('commandBus') as CommandBus\n try {\n await commandBus.execute<CommentClaimInput, { claimId: string }>(\n 'warranty_claims.claim.comment',\n { input: effectiveInput, ctx: context.commandCtx },\n )\n await guarded.runAfterSuccess()\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n const interceptorRejection = getCommandInterceptorHttpRejection(err)\n if (interceptorRejection) {\n return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status })\n }\n throw err\n }\n\n return NextResponse.json({ ok: true, claimId: claim.id })\n}\n\nconst eventSchema = z.object({\n id: z.string().uuid(),\n claimId: z.string().uuid().nullable(),\n kind: z.string(),\n visibility: z.literal('customer'),\n body: z.string().nullable(),\n payload: z.record(z.string(), z.unknown()).nullable(),\n actorCustomerId: z.string().uuid().nullable(),\n createdAt: z.string().nullable(),\n})\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Warranty Claims Portal',\n summary: 'Customer portal warranty claim timeline',\n methods: {\n GET: {\n summary: 'List customer-visible timeline events for an owned claim',\n query: eventQuerySchema,\n responses: [\n {\n status: 200,\n description: 'Customer-visible timeline events',\n schema: z.object({ items: z.array(eventSchema) }),\n },\n ],\n },\n POST: {\n summary: 'Append a customer-visible customer comment',\n requestBody: { contentType: 'application/json', schema: portalCommentSchema },\n responses: [\n {\n status: 200,\n description: 'Comment appended',\n schema: z.object({ ok: z.boolean(), claimId: z.string().uuid() }),\n },\n ],\n },\n },\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -4,7 +4,9 @@ import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
4
4
  import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
5
5
  import { resolveOrganizationScopeForRequest } from "@open-mercato/core/modules/directory/utils/organizationScope";
6
6
  import { CrudHttpError, isCrudHttpError } from "@open-mercato/shared/lib/crud/errors";
7
+ import { getCommandInterceptorHttpRejection } from "@open-mercato/shared/lib/commands/errors";
7
8
  import { readJsonSafe } from "@open-mercato/shared/lib/http/readJsonSafe";
9
+ import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
8
10
  import { runRouteMutationGuards } from "@open-mercato/shared/lib/crud/route-mutation-guard";
9
11
  import { withScopedPayload } from "@open-mercato/shared/lib/api/scoped";
10
12
  import {
@@ -35,19 +37,16 @@ const metadata = {
35
37
  function toRecord(value) {
36
38
  return value && typeof value === "object" && !Array.isArray(value) ? value : {};
37
39
  }
38
- function translateKey(key) {
39
- return key;
40
- }
41
- async function resolveActionContext(req) {
40
+ async function resolveActionContext(req, translate) {
42
41
  const container = await createRequestContainer();
43
42
  const auth = await getAuthFromRequest(req);
44
43
  if (!auth || !auth.tenantId) {
45
- throw new CrudHttpError(401, { error: "warranty_claims.errors.unauthorized" });
44
+ throw new CrudHttpError(401, { error: translate("warranty_claims.errors.unauthorized", "Unauthorized") });
46
45
  }
47
46
  const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req });
48
47
  const organizationId = scope?.selectedId ?? auth.orgId ?? null;
49
48
  if (!organizationId) {
50
- throw new CrudHttpError(400, { error: "warranty_claims.errors.organization_required" });
49
+ throw new CrudHttpError(400, { error: translate("warranty_claims.errors.organization_required", "Organization context is required.") });
51
50
  }
52
51
  return {
53
52
  ctx: {
@@ -60,7 +59,7 @@ async function resolveActionContext(req) {
60
59
  },
61
60
  tenantId: auth.tenantId,
62
61
  organizationId,
63
- translate: translateKey
62
+ translate
64
63
  };
65
64
  }
66
65
  function toReceivingAction(payload, context) {
@@ -90,7 +89,7 @@ function toGuardedAction(kind, payload) {
90
89
  async function runGuard(req, context, action) {
91
90
  const userId = context.ctx.auth?.sub;
92
91
  if (!userId) {
93
- throw new CrudHttpError(401, { error: "warranty_claims.errors.unauthorized" });
92
+ throw new CrudHttpError(401, { error: context.translate("warranty_claims.errors.unauthorized", "Unauthorized") });
94
93
  }
95
94
  return runRouteMutationGuards({
96
95
  container: context.ctx.container,
@@ -108,25 +107,26 @@ async function runGuard(req, context, action) {
108
107
  }
109
108
  });
110
109
  }
111
- async function executeReceivingAction(commandBus, action, ctx) {
110
+ async function executeReceivingAction(commandBus, action, ctx, translate) {
112
111
  if (action.kind === "release") {
113
112
  const { result: result2 } = await commandBus.execute(
114
113
  "warranty_claims.claim_line.release_quarantine",
115
114
  { input: action.input, ctx }
116
115
  );
117
- if (!result2) throw new CrudHttpError(400, { error: "warranty_claims.errors.save_failed" });
116
+ if (!result2) throw new CrudHttpError(400, { error: translate("warranty_claims.errors.save_failed", "Failed to save warranty claim.") });
118
117
  return result2;
119
118
  }
120
119
  const { result } = await commandBus.execute(
121
120
  "warranty_claims.claim_line.receive",
122
121
  { input: action.input, ctx }
123
122
  );
124
- if (!result) throw new CrudHttpError(400, { error: "warranty_claims.errors.save_failed" });
123
+ if (!result) throw new CrudHttpError(400, { error: translate("warranty_claims.errors.save_failed", "Failed to save warranty claim.") });
125
124
  return result;
126
125
  }
127
126
  async function POST(req) {
127
+ const { translate } = await resolveTranslations();
128
128
  try {
129
- const context = await resolveActionContext(req);
129
+ const context = await resolveActionContext(req, translate);
130
130
  const payload = toRecord(await readJsonSafe(req, {}));
131
131
  const action = toReceivingAction(payload, context);
132
132
  const guarded = await runGuard(req, context, action);
@@ -135,16 +135,20 @@ async function POST(req) {
135
135
  }
136
136
  const commandAction = guarded.modifiedPayload ? toGuardedAction(action.kind, guarded.modifiedPayload) : action;
137
137
  const commandBus = context.ctx.container.resolve("commandBus");
138
- const result = await executeReceivingAction(commandBus, commandAction, context.ctx);
138
+ const result = await executeReceivingAction(commandBus, commandAction, context.ctx, translate);
139
139
  await guarded.runAfterSuccess();
140
140
  return NextResponse.json({ ok: true, lineId: result.lineId, claimId: result.claimId });
141
141
  } catch (err) {
142
142
  if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status });
143
+ const interceptorRejection = getCommandInterceptorHttpRejection(err);
144
+ if (interceptorRejection) {
145
+ return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status });
146
+ }
143
147
  if (err instanceof z.ZodError) {
144
- return NextResponse.json({ error: "warranty_claims.errors.invalidInput" }, { status: 400 });
148
+ return NextResponse.json({ error: translate("warranty_claims.errors.invalidInput", "Invalid input") }, { status: 400 });
145
149
  }
146
150
  logger.error("warranty_claims.receiving.post failed", { err });
147
- return NextResponse.json({ error: "warranty_claims.errors.save_failed" }, { status: 500 });
151
+ return NextResponse.json({ error: translate("warranty_claims.errors.save_failed", "Failed to save warranty claim.") }, { status: 500 });
148
152
  }
149
153
  }
150
154
  const openApi = {