@open-mercato/core 0.8.1-develop.7275.1.f772b944d4 → 0.8.1-develop.7294.1.0ef99fc518

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 (261) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/modules/auth/api/users/acl/route.js.map +2 -2
  3. package/dist/modules/auth/components/AclEditor.js +21 -3
  4. package/dist/modules/auth/components/AclEditor.js.map +2 -2
  5. package/dist/modules/customers/backend/customers/companies/page.js +6 -4
  6. package/dist/modules/customers/backend/customers/companies/page.js.map +2 -2
  7. package/dist/modules/customers/backend/customers/deals/map/components/DealsMapView.js +6 -4
  8. package/dist/modules/customers/backend/customers/deals/map/components/DealsMapView.js.map +2 -2
  9. package/dist/modules/customers/backend/customers/deals/page.js +6 -4
  10. package/dist/modules/customers/backend/customers/deals/page.js.map +2 -2
  11. package/dist/modules/customers/backend/customers/deals/pipeline/components/ChangeOwnerDialog.js +5 -3
  12. package/dist/modules/customers/backend/customers/deals/pipeline/components/ChangeOwnerDialog.js.map +2 -2
  13. package/dist/modules/customers/backend/customers/deals/pipeline/page.js +6 -4
  14. package/dist/modules/customers/backend/customers/deals/pipeline/page.js.map +2 -2
  15. package/dist/modules/customers/backend/customers/people/page.js +6 -4
  16. package/dist/modules/customers/backend/customers/people/page.js.map +2 -2
  17. package/dist/modules/customers/components/calendar/editor/PeopleField.js +9 -2
  18. package/dist/modules/customers/components/calendar/editor/PeopleField.js.map +2 -2
  19. package/dist/modules/customers/components/calendar/editor/hooks.js +4 -2
  20. package/dist/modules/customers/components/calendar/editor/hooks.js.map +2 -2
  21. package/dist/modules/customers/components/calendar/editor/lookups.js +7 -3
  22. package/dist/modules/customers/components/calendar/editor/lookups.js.map +2 -2
  23. package/dist/modules/customers/components/detail/AssignRoleDialog.js +5 -2
  24. package/dist/modules/customers/components/detail/AssignRoleDialog.js.map +2 -2
  25. package/dist/modules/customers/components/detail/RoleAssignmentRow.js +4 -2
  26. package/dist/modules/customers/components/detail/RoleAssignmentRow.js.map +2 -2
  27. package/dist/modules/customers/components/detail/ScheduleActivityDialog.js +8 -5
  28. package/dist/modules/customers/components/detail/ScheduleActivityDialog.js.map +2 -2
  29. package/dist/modules/customers/components/detail/schedule/DateTimeFields.js +10 -7
  30. package/dist/modules/customers/components/detail/schedule/DateTimeFields.js.map +2 -2
  31. package/dist/modules/customers/components/detail/schedule/ParticipantsField.js +4 -2
  32. package/dist/modules/customers/components/detail/schedule/ParticipantsField.js.map +2 -2
  33. package/dist/modules/customers/components/detail/schedule/fieldConfig.js +9 -0
  34. package/dist/modules/customers/components/detail/schedule/fieldConfig.js.map +2 -2
  35. package/dist/modules/customers/components/detail/schedule/index.js +3 -1
  36. package/dist/modules/customers/components/detail/schedule/index.js.map +2 -2
  37. package/dist/modules/customers/components/detail/schedule/useScheduleFormState.js +11 -4
  38. package/dist/modules/customers/components/detail/schedule/useScheduleFormState.js.map +2 -2
  39. package/dist/modules/customers/components/formConfig.js +17 -9
  40. package/dist/modules/customers/components/formConfig.js.map +2 -2
  41. package/dist/modules/customers/data/validators.js +12 -7
  42. package/dist/modules/customers/data/validators.js.map +2 -2
  43. package/dist/modules/customers/lib/assignableStaff.js +103 -22
  44. package/dist/modules/customers/lib/assignableStaff.js.map +2 -2
  45. package/dist/modules/customers/setup.js +10 -2
  46. package/dist/modules/customers/setup.js.map +2 -2
  47. package/dist/modules/data_sync/api/schedules/[id]/route.js +5 -0
  48. package/dist/modules/data_sync/api/schedules/[id]/route.js.map +2 -2
  49. package/dist/modules/data_sync/api/schedules/route.js +5 -0
  50. package/dist/modules/data_sync/api/schedules/route.js.map +2 -2
  51. package/dist/modules/data_sync/backend/data-sync/page.js +29 -5
  52. package/dist/modules/data_sync/backend/data-sync/page.js.map +2 -2
  53. package/dist/modules/data_sync/components/IntegrationScheduleTab.js +48 -11
  54. package/dist/modules/data_sync/components/IntegrationScheduleTab.js.map +2 -2
  55. package/dist/modules/data_sync/data/validators.js +13 -2
  56. package/dist/modules/data_sync/data/validators.js.map +2 -2
  57. package/dist/modules/data_sync/lib/schedule-value.js +30 -0
  58. package/dist/modules/data_sync/lib/schedule-value.js.map +7 -0
  59. package/dist/modules/eudr/data/validators.js.map +2 -2
  60. package/dist/modules/portal/frontend/[orgSlug]/portal/dashboard/page.js +3 -4
  61. package/dist/modules/portal/frontend/[orgSlug]/portal/dashboard/page.js.map +2 -2
  62. package/dist/modules/portal/frontend/[orgSlug]/portal/invite/page.js +3 -1
  63. package/dist/modules/portal/frontend/[orgSlug]/portal/invite/page.js.map +2 -2
  64. package/dist/modules/portal/frontend/[orgSlug]/portal/login/page.js +2 -1
  65. package/dist/modules/portal/frontend/[orgSlug]/portal/login/page.js.map +2 -2
  66. package/dist/modules/portal/frontend/[orgSlug]/portal/page.js +3 -4
  67. package/dist/modules/portal/frontend/[orgSlug]/portal/page.js.map +2 -2
  68. package/dist/modules/portal/frontend/[orgSlug]/portal/profile/page.js +3 -4
  69. package/dist/modules/portal/frontend/[orgSlug]/portal/profile/page.js.map +2 -2
  70. package/dist/modules/portal/lib/navigation.js +3 -4
  71. package/dist/modules/portal/lib/navigation.js.map +2 -2
  72. package/dist/modules/query_index/lib/engine.js +79 -14
  73. package/dist/modules/query_index/lib/engine.js.map +2 -2
  74. package/dist/modules/staff/backend/staff/time-tracking/page.js +122 -86
  75. package/dist/modules/staff/backend/staff/time-tracking/page.js.map +2 -2
  76. package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/page.js +38 -17
  77. package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/page.js.map +2 -2
  78. package/dist/modules/staff/backend/staff/time-tracking/reports/[id]/page.js +45 -18
  79. package/dist/modules/staff/backend/staff/time-tracking/reports/[id]/page.js.map +2 -2
  80. package/dist/modules/staff/extension-points.js +16 -0
  81. package/dist/modules/staff/extension-points.js.map +2 -2
  82. package/dist/modules/staff/frontend/[orgSlug]/portal/time-reports/[id]/page.js +3 -4
  83. package/dist/modules/staff/frontend/[orgSlug]/portal/time-reports/[id]/page.js.map +2 -2
  84. package/dist/modules/staff/frontend/[orgSlug]/portal/time-reports/page.js +3 -4
  85. package/dist/modules/staff/frontend/[orgSlug]/portal/time-reports/page.js.map +2 -2
  86. package/dist/modules/staff/lib/time-tracking-ui/ReportSheet.js +14 -13
  87. package/dist/modules/staff/lib/time-tracking-ui/ReportSheet.js.map +2 -2
  88. package/dist/modules/staff/lib/time-tracking-ui/TaskListView.js +60 -41
  89. package/dist/modules/staff/lib/time-tracking-ui/TaskListView.js.map +2 -2
  90. package/dist/modules/warranty_claims/api/ai/assess/route.js +2 -2
  91. package/dist/modules/warranty_claims/api/ai/assess/route.js.map +2 -2
  92. package/dist/modules/warranty_claims/api/ai/draft-reply/route.js +3 -4
  93. package/dist/modules/warranty_claims/api/ai/draft-reply/route.js.map +3 -3
  94. package/dist/modules/warranty_claims/api/ai/suggest/route.js +3 -3
  95. package/dist/modules/warranty_claims/api/ai/suggest/route.js.map +2 -2
  96. package/dist/modules/warranty_claims/api/assign/route.js +3 -3
  97. package/dist/modules/warranty_claims/api/assign/route.js.map +2 -2
  98. package/dist/modules/warranty_claims/api/credit-memo/route.js +2 -2
  99. package/dist/modules/warranty_claims/api/credit-memo/route.js.map +2 -2
  100. package/dist/modules/warranty_claims/api/entitlement/route.js +2 -2
  101. package/dist/modules/warranty_claims/api/entitlement/route.js.map +2 -2
  102. package/dist/modules/warranty_claims/api/events/route.js +4 -4
  103. package/dist/modules/warranty_claims/api/events/route.js.map +2 -2
  104. package/dist/modules/warranty_claims/api/external/claims/route.js +3 -3
  105. package/dist/modules/warranty_claims/api/external/claims/route.js.map +2 -2
  106. package/dist/modules/warranty_claims/api/portal/claims/[id]/submit/route.js +2 -2
  107. package/dist/modules/warranty_claims/api/portal/claims/[id]/submit/route.js.map +2 -2
  108. package/dist/modules/warranty_claims/api/portal/claims/[id]/withdraw/route.js +2 -2
  109. package/dist/modules/warranty_claims/api/portal/claims/[id]/withdraw/route.js.map +2 -2
  110. package/dist/modules/warranty_claims/api/portal/claims/route.js +2 -2
  111. package/dist/modules/warranty_claims/api/portal/claims/route.js.map +2 -2
  112. package/dist/modules/warranty_claims/api/portal/events/route.js +2 -2
  113. package/dist/modules/warranty_claims/api/portal/events/route.js.map +2 -2
  114. package/dist/modules/warranty_claims/api/receiving/route.js +2 -2
  115. package/dist/modules/warranty_claims/api/receiving/route.js.map +2 -2
  116. package/dist/modules/warranty_claims/api/replacement-order/route.js +2 -2
  117. package/dist/modules/warranty_claims/api/replacement-order/route.js.map +2 -2
  118. package/dist/modules/warranty_claims/api/return-label/route.js +2 -2
  119. package/dist/modules/warranty_claims/api/return-label/route.js.map +2 -2
  120. package/dist/modules/warranty_claims/api/risk/route.js +2 -2
  121. package/dist/modules/warranty_claims/api/risk/route.js.map +2 -2
  122. package/dist/modules/warranty_claims/api/sales-return/route.js +2 -2
  123. package/dist/modules/warranty_claims/api/sales-return/route.js.map +2 -2
  124. package/dist/modules/warranty_claims/api/settings-general/route.js +4 -4
  125. package/dist/modules/warranty_claims/api/settings-general/route.js.map +2 -2
  126. package/dist/modules/warranty_claims/api/stats/route.js +2 -2
  127. package/dist/modules/warranty_claims/api/stats/route.js.map +2 -2
  128. package/dist/modules/warranty_claims/api/submit/route.js +3 -3
  129. package/dist/modules/warranty_claims/api/submit/route.js.map +2 -2
  130. package/dist/modules/warranty_claims/api/transition/route.js +3 -3
  131. package/dist/modules/warranty_claims/api/transition/route.js.map +2 -2
  132. package/dist/modules/warranty_claims/api/vendor-recovery/route.js +3 -3
  133. package/dist/modules/warranty_claims/api/vendor-recovery/route.js.map +2 -2
  134. package/dist/modules/warranty_claims/api/vendor-recovery-suggestions/route.js +2 -2
  135. package/dist/modules/warranty_claims/api/vendor-recovery-suggestions/route.js.map +2 -2
  136. package/dist/modules/warranty_claims/backend/page.js +4 -2
  137. package/dist/modules/warranty_claims/backend/page.js.map +2 -2
  138. package/dist/modules/warranty_claims/backend/warranty_claims/[id]/page.js +9 -2
  139. package/dist/modules/warranty_claims/backend/warranty_claims/[id]/page.js.map +2 -2
  140. package/dist/modules/warranty_claims/backend/warranty_claims/settings/page.js +4 -2
  141. package/dist/modules/warranty_claims/backend/warranty_claims/settings/page.js.map +2 -2
  142. package/dist/modules/warranty_claims/frontend/[orgSlug]/portal/claims/[id]/page.js +3 -4
  143. package/dist/modules/warranty_claims/frontend/[orgSlug]/portal/claims/[id]/page.js.map +2 -2
  144. package/dist/modules/warranty_claims/frontend/[orgSlug]/portal/claims/new/page.js +3 -2
  145. package/dist/modules/warranty_claims/frontend/[orgSlug]/portal/claims/new/page.js.map +2 -2
  146. package/dist/modules/warranty_claims/frontend/[orgSlug]/portal/claims/page.js +3 -2
  147. package/dist/modules/warranty_claims/frontend/[orgSlug]/portal/claims/page.js.map +2 -2
  148. package/dist/modules/workflows/components/DefinitionTriggersEditor.js +1 -1
  149. package/dist/modules/workflows/components/DefinitionTriggersEditor.js.map +2 -2
  150. package/dist/modules/workflows/frontend/[orgSlug]/portal/tasks/[id]/page.js +2 -2
  151. package/dist/modules/workflows/frontend/[orgSlug]/portal/tasks/[id]/page.js.map +2 -2
  152. package/dist/modules/workflows/lib/event-trigger-service.js +56 -1
  153. package/dist/modules/workflows/lib/event-trigger-service.js.map +2 -2
  154. package/package.json +7 -7
  155. package/src/modules/auth/api/users/acl/route.ts +4 -3
  156. package/src/modules/auth/components/AclEditor.tsx +38 -2
  157. package/src/modules/auth/i18n/de.json +5 -1
  158. package/src/modules/auth/i18n/en.json +5 -1
  159. package/src/modules/auth/i18n/es.json +5 -1
  160. package/src/modules/auth/i18n/ko.json +5 -1
  161. package/src/modules/auth/i18n/pl.json +5 -1
  162. package/src/modules/customers/backend/customers/companies/page.tsx +6 -4
  163. package/src/modules/customers/backend/customers/deals/map/components/DealsMapView.tsx +6 -4
  164. package/src/modules/customers/backend/customers/deals/page.tsx +6 -4
  165. package/src/modules/customers/backend/customers/deals/pipeline/components/ChangeOwnerDialog.tsx +5 -3
  166. package/src/modules/customers/backend/customers/deals/pipeline/page.tsx +6 -4
  167. package/src/modules/customers/backend/customers/people/page.tsx +6 -4
  168. package/src/modules/customers/components/calendar/editor/PeopleField.tsx +9 -2
  169. package/src/modules/customers/components/calendar/editor/hooks.ts +4 -2
  170. package/src/modules/customers/components/calendar/editor/lookups.ts +12 -4
  171. package/src/modules/customers/components/detail/AssignRoleDialog.tsx +4 -1
  172. package/src/modules/customers/components/detail/RoleAssignmentRow.tsx +4 -2
  173. package/src/modules/customers/components/detail/ScheduleActivityDialog.tsx +15 -7
  174. package/src/modules/customers/components/detail/schedule/DateTimeFields.tsx +16 -7
  175. package/src/modules/customers/components/detail/schedule/ParticipantsField.tsx +4 -2
  176. package/src/modules/customers/components/detail/schedule/fieldConfig.ts +15 -0
  177. package/src/modules/customers/components/detail/schedule/index.ts +1 -1
  178. package/src/modules/customers/components/detail/schedule/useScheduleFormState.ts +16 -9
  179. package/src/modules/customers/components/formConfig.tsx +35 -12
  180. package/src/modules/customers/data/validators.ts +14 -8
  181. package/src/modules/customers/i18n/de.json +1 -0
  182. package/src/modules/customers/i18n/en.json +1 -0
  183. package/src/modules/customers/i18n/es.json +1 -0
  184. package/src/modules/customers/i18n/ko.json +1 -0
  185. package/src/modules/customers/i18n/pl.json +1 -0
  186. package/src/modules/customers/lib/assignableStaff.ts +172 -34
  187. package/src/modules/customers/setup.ts +8 -0
  188. package/src/modules/data_sync/api/schedules/[id]/route.ts +5 -0
  189. package/src/modules/data_sync/api/schedules/route.ts +5 -0
  190. package/src/modules/data_sync/backend/data-sync/page.tsx +48 -4
  191. package/src/modules/data_sync/components/IntegrationScheduleTab.tsx +61 -2
  192. package/src/modules/data_sync/data/validators.ts +26 -2
  193. package/src/modules/data_sync/i18n/de.json +2 -0
  194. package/src/modules/data_sync/i18n/en.json +2 -0
  195. package/src/modules/data_sync/i18n/es.json +2 -0
  196. package/src/modules/data_sync/i18n/ko.json +2 -0
  197. package/src/modules/data_sync/i18n/pl.json +2 -0
  198. package/src/modules/data_sync/lib/schedule-value.ts +45 -0
  199. package/src/modules/eudr/data/validators.ts +3 -4
  200. package/src/modules/portal/frontend/[orgSlug]/portal/dashboard/page.tsx +5 -4
  201. package/src/modules/portal/frontend/[orgSlug]/portal/invite/page.tsx +3 -1
  202. package/src/modules/portal/frontend/[orgSlug]/portal/login/page.tsx +2 -1
  203. package/src/modules/portal/frontend/[orgSlug]/portal/page.tsx +5 -5
  204. package/src/modules/portal/frontend/[orgSlug]/portal/profile/page.tsx +5 -4
  205. package/src/modules/portal/i18n/de.json +1 -0
  206. package/src/modules/portal/i18n/en.json +1 -0
  207. package/src/modules/portal/i18n/es.json +1 -0
  208. package/src/modules/portal/i18n/ko.json +1 -0
  209. package/src/modules/portal/i18n/pl.json +1 -0
  210. package/src/modules/portal/lib/navigation.ts +8 -3
  211. package/src/modules/query_index/lib/engine.ts +111 -16
  212. package/src/modules/staff/backend/staff/time-tracking/page.tsx +161 -130
  213. package/src/modules/staff/backend/staff/time-tracking/projects/[id]/page.tsx +55 -43
  214. package/src/modules/staff/backend/staff/time-tracking/reports/[id]/page.tsx +61 -38
  215. package/src/modules/staff/extension-points.ts +16 -0
  216. package/src/modules/staff/frontend/[orgSlug]/portal/time-reports/[id]/page.tsx +3 -4
  217. package/src/modules/staff/frontend/[orgSlug]/portal/time-reports/page.tsx +3 -4
  218. package/src/modules/staff/i18n/de.json +3 -0
  219. package/src/modules/staff/i18n/en.json +3 -0
  220. package/src/modules/staff/i18n/es.json +3 -0
  221. package/src/modules/staff/i18n/ko.json +3 -0
  222. package/src/modules/staff/i18n/pl.json +3 -0
  223. package/src/modules/staff/lib/time-tracking-ui/ReportSheet.tsx +32 -29
  224. package/src/modules/staff/lib/time-tracking-ui/TaskListView.tsx +89 -79
  225. package/src/modules/warranty_claims/api/ai/assess/route.ts +2 -2
  226. package/src/modules/warranty_claims/api/ai/draft-reply/route.ts +2 -3
  227. package/src/modules/warranty_claims/api/ai/suggest/route.ts +3 -3
  228. package/src/modules/warranty_claims/api/assign/route.ts +3 -3
  229. package/src/modules/warranty_claims/api/credit-memo/route.ts +2 -2
  230. package/src/modules/warranty_claims/api/entitlement/route.ts +2 -2
  231. package/src/modules/warranty_claims/api/events/route.ts +4 -4
  232. package/src/modules/warranty_claims/api/external/claims/route.ts +3 -3
  233. package/src/modules/warranty_claims/api/portal/claims/[id]/submit/route.ts +2 -2
  234. package/src/modules/warranty_claims/api/portal/claims/[id]/withdraw/route.ts +2 -2
  235. package/src/modules/warranty_claims/api/portal/claims/route.ts +2 -2
  236. package/src/modules/warranty_claims/api/portal/events/route.ts +2 -2
  237. package/src/modules/warranty_claims/api/receiving/route.ts +2 -2
  238. package/src/modules/warranty_claims/api/replacement-order/route.ts +2 -2
  239. package/src/modules/warranty_claims/api/return-label/route.ts +2 -2
  240. package/src/modules/warranty_claims/api/risk/route.ts +2 -2
  241. package/src/modules/warranty_claims/api/sales-return/route.ts +2 -2
  242. package/src/modules/warranty_claims/api/settings-general/route.ts +4 -4
  243. package/src/modules/warranty_claims/api/stats/route.ts +2 -2
  244. package/src/modules/warranty_claims/api/submit/route.ts +3 -3
  245. package/src/modules/warranty_claims/api/transition/route.ts +3 -3
  246. package/src/modules/warranty_claims/api/vendor-recovery/route.ts +3 -3
  247. package/src/modules/warranty_claims/api/vendor-recovery-suggestions/route.ts +2 -2
  248. package/src/modules/warranty_claims/backend/page.tsx +4 -2
  249. package/src/modules/warranty_claims/backend/warranty_claims/[id]/page.tsx +9 -2
  250. package/src/modules/warranty_claims/backend/warranty_claims/settings/page.tsx +4 -2
  251. package/src/modules/warranty_claims/frontend/[orgSlug]/portal/claims/[id]/page.tsx +5 -4
  252. package/src/modules/warranty_claims/frontend/[orgSlug]/portal/claims/new/page.tsx +5 -2
  253. package/src/modules/warranty_claims/frontend/[orgSlug]/portal/claims/page.tsx +5 -2
  254. package/src/modules/workflows/components/DefinitionTriggersEditor.tsx +1 -1
  255. package/src/modules/workflows/frontend/[orgSlug]/portal/tasks/[id]/page.tsx +2 -2
  256. package/src/modules/workflows/i18n/de.json +1 -1
  257. package/src/modules/workflows/i18n/en.json +1 -1
  258. package/src/modules/workflows/i18n/es.json +1 -1
  259. package/src/modules/workflows/i18n/ko.json +1 -1
  260. package/src/modules/workflows/i18n/pl.json +1 -1
  261. package/src/modules/workflows/lib/event-trigger-service.ts +139 -0
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/warranty_claims/api/stats/route.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { sql, type SelectQueryBuilder } from 'kysely'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport { CrudHttpError, 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 { resolveEffectiveWarrantyClaimSettings } from '../../lib/settings'\nimport { SLA_EXCLUDED_STATUSES } from '../../lib/deskFilters'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('warranty_claims')\n\ntype NumericAggregateValue = string | number | bigint | null\n\ntype WarrantyClaimsStatsTable = {\n id: string\n organization_id: string\n tenant_id: string\n status: string\n deleted_at: Date | null\n sla_due_at: Date | null\n sla_paused_at: Date | null\n assignee_user_id: string | null\n submitted_at: Date | null\n resolved_at: Date | null\n total_approved_amount: string | number | null\n total_recovered_amount: string | number | null\n currency_code: string | null\n}\n\ntype WarrantyClaimsStatsDb = {\n warranty_claims: WarrantyClaimsStatsTable\n}\n\ntype StatsRouteContext = {\n tenantId: string\n organizationIds: string[]\n userId: string | null\n em: EntityManager\n}\n\nconst querySchema = z.object({}).strict()\n\nconst OPEN_STATUSES = [\n 'submitted',\n 'in_review',\n 'info_requested',\n 'approved',\n 'awaiting_return',\n 'received',\n 'inspecting',\n]\n\nconst RESOLUTION_STATUSES = ['resolved', 'closed']\nconst THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1000\n\nconst recoveredByCurrencySchema = z.object({\n currencyCode: z.string().nullable(),\n total: z.number(),\n})\n\nconst resultSchema = z.object({\n openByStatus: z.record(z.string(), z.number().int().nonnegative()),\n overdue: z.number().int().nonnegative(),\n slaAtRisk: z.number().int().nonnegative(),\n assignedToMe: z.number().int().nonnegative(),\n resolvedLast30d: z.number().int().nonnegative(),\n avgResolutionDays: z.number().nullable(),\n approvalRatePct: z.number().nullable(),\n recoveredLast30dByCurrency: z.array(recoveredByCurrencySchema),\n slaAtRiskThresholdPct: z.number().int().min(1).max(100),\n})\n\nconst responseSchema = z.object({\n ok: z.literal(true),\n result: resultSchema,\n})\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['warranty_claims.claim.view'] },\n}\n\nfunction parseNumeric(value: NumericAggregateValue | undefined): number {\n if (value === undefined || value === null) return 0\n const parsed = Number(value)\n return Number.isFinite(parsed) ? parsed : 0\n}\n\nfunction applyOrganizationScope<O>(\n query: SelectQueryBuilder<WarrantyClaimsStatsDb, 'warranty_claims', O>,\n organizationIds: string[],\n): SelectQueryBuilder<WarrantyClaimsStatsDb, 'warranty_claims', O> {\n if (organizationIds.length === 1) {\n return query.where('organization_id', '=', organizationIds[0])\n }\n return query.where('organization_id', 'in', organizationIds)\n}\n\nfunction baseClaimsQuery<O>(\n query: SelectQueryBuilder<WarrantyClaimsStatsDb, 'warranty_claims', O>,\n context: StatsRouteContext,\n): SelectQueryBuilder<WarrantyClaimsStatsDb, 'warranty_claims', O> {\n return applyOrganizationScope(\n query\n .where('tenant_id', '=', context.tenantId)\n .where('deleted_at', 'is', null),\n context.organizationIds,\n )\n}\n\nasync function resolveStatsContext(req: Request): Promise<StatsRouteContext> {\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\n const { translate } = await resolveTranslations()\n if (!auth || !auth.tenantId) {\n throw new CrudHttpError(401, { error: translate('warranty_claims.errors.unauthorized', 'Unauthorized') })\n }\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n const organizationIds = Array.isArray(scope?.filterIds) && scope.filterIds.length > 0\n ? scope.filterIds\n : auth.orgId\n ? [auth.orgId]\n : []\n if (organizationIds.length === 0) {\n throw new CrudHttpError(400, { error: translate('warranty_claims.errors.organization_required', 'Organization context is required') })\n }\n return {\n tenantId: auth.tenantId,\n organizationIds,\n userId: typeof auth.sub === 'string' ? auth.sub : null,\n em: (container.resolve('em') as EntityManager).fork(),\n }\n}\n\nexport async function GET(req: Request) {\n try {\n const url = new URL(req.url)\n querySchema.parse(Object.fromEntries(url.searchParams))\n const context = await resolveStatsContext(req)\n const db = context.em.getKysely<WarrantyClaimsStatsDb>()\n const now = new Date()\n const thirtyDaysAgo = new Date(now.getTime() - THIRTY_DAYS_MS)\n const effectiveSettings = await resolveEffectiveWarrantyClaimSettings(context.em, {\n tenantId: context.tenantId,\n organizationId: context.organizationIds[0] ?? null,\n })\n\n const openRows = await baseClaimsQuery(\n db\n .selectFrom('warranty_claims')\n .select(['status', sql<NumericAggregateValue>`count(*)`.as('count')])\n .where('status', 'in', OPEN_STATUSES)\n .groupBy('status'),\n context,\n ).execute()\n const openByStatus: Record<string, number> = Object.fromEntries(OPEN_STATUSES.map((status) => [status, 0]))\n for (const row of openRows) {\n openByStatus[row.status] = Math.trunc(parseNumeric(row.count))\n }\n\n const overdueRow = await baseClaimsQuery(\n db\n .selectFrom('warranty_claims')\n .select(sql<NumericAggregateValue>`count(*)`.as('count'))\n .where('sla_due_at', '<', now)\n .where('sla_paused_at', 'is', null)\n .where('status', 'not in', [...SLA_EXCLUDED_STATUSES]),\n context,\n ).executeTakeFirst()\n const overdue = Math.trunc(parseNumeric(overdueRow?.count))\n\n const atRiskRow = await baseClaimsQuery(\n db\n .selectFrom('warranty_claims')\n .select(sql<NumericAggregateValue>`count(*)`.as('count'))\n .where('sla_due_at', '>', now)\n .where('sla_paused_at', 'is', null)\n .where('status', 'not in', [...SLA_EXCLUDED_STATUSES])\n .where('submitted_at', 'is not', null)\n .where(sql<boolean>`sla_due_at > submitted_at`)\n .where(sql<boolean>`extract(epoch from (${now}::timestamptz - submitted_at)) * 100 >= extract(epoch from (sla_due_at - submitted_at)) * ${effectiveSettings.slaAtRiskThresholdPct}`),\n context,\n ).executeTakeFirst()\n const slaAtRisk = Math.trunc(parseNumeric(atRiskRow?.count))\n\n let assignedToMe = 0\n if (context.userId) {\n const assignedRow = await baseClaimsQuery(\n db\n .selectFrom('warranty_claims')\n .select(sql<NumericAggregateValue>`count(*)`.as('count'))\n .where('status', 'in', OPEN_STATUSES)\n .where('assignee_user_id', '=', context.userId),\n context,\n ).executeTakeFirst()\n assignedToMe = Math.trunc(parseNumeric(assignedRow?.count))\n }\n\n const resolvedRow = await baseClaimsQuery(\n db\n .selectFrom('warranty_claims')\n .select(sql<NumericAggregateValue>`count(*)`.as('count'))\n .where('resolved_at', '>=', thirtyDaysAgo),\n context,\n ).executeTakeFirst()\n const resolvedLast30d = Math.trunc(parseNumeric(resolvedRow?.count))\n\n const avgRow = await baseClaimsQuery(\n db\n .selectFrom('warranty_claims')\n .select(sql<NumericAggregateValue>`avg(extract(epoch from (resolved_at - submitted_at)) / 86400.0)`.as('avgDays'))\n .where('resolved_at', '>=', thirtyDaysAgo)\n .where('submitted_at', 'is not', null),\n context,\n ).executeTakeFirst()\n const avgValue = avgRow?.avgDays\n const avgResolutionDays = avgValue === undefined || avgValue === null\n ? null\n : Number(parseNumeric(avgValue).toFixed(1))\n\n const approvalRow = await baseClaimsQuery(\n db\n .selectFrom('warranty_claims')\n .select([\n sql<NumericAggregateValue>`count(*)`.as('total'),\n sql<NumericAggregateValue>`count(*) filter (where total_approved_amount > 0)`.as('approved'),\n ])\n .where('status', 'in', RESOLUTION_STATUSES)\n .where('resolved_at', '>=', thirtyDaysAgo),\n context,\n ).executeTakeFirst()\n const approvalTotal = parseNumeric(approvalRow?.total)\n const approvalApproved = parseNumeric(approvalRow?.approved)\n const approvalRatePct = approvalTotal > 0 ? Math.round((approvalApproved / approvalTotal) * 100) : null\n\n const recoveredRows = await baseClaimsQuery(\n db\n .selectFrom('warranty_claims')\n .select([\n 'currency_code as currencyCode',\n sql<NumericAggregateValue>`coalesce(sum(total_recovered_amount), 0)`.as('total'),\n ])\n .where('status', 'in', RESOLUTION_STATUSES)\n .where('resolved_at', '>=', thirtyDaysAgo)\n .where('total_recovered_amount', '>', 0)\n .groupBy('currency_code'),\n context,\n ).execute()\n // `group by` returns rows in an unspecified order, so consumers that render a\n // single bucket would otherwise show an arbitrary currency. Sort largest-first\n // with the code as a tiebreak so the response is deterministic.\n const recoveredLast30dByCurrency = recoveredRows\n .map((row) => ({\n currencyCode: row.currencyCode ?? null,\n total: Number(parseNumeric(row.total).toFixed(2)),\n }))\n .sort((left, right) =>\n right.total - left.total\n || (left.currencyCode ?? '').localeCompare(right.currencyCode ?? ''))\n\n return NextResponse.json({\n ok: true,\n result: {\n openByStatus,\n overdue,\n slaAtRisk,\n assignedToMe,\n resolvedLast30d,\n avgResolutionDays,\n approvalRatePct,\n recoveredLast30dByCurrency,\n slaAtRiskThresholdPct: effectiveSettings.slaAtRiskThresholdPct,\n },\n })\n } catch (err) {\n if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status })\n const { translate } = await resolveTranslations()\n if (err instanceof z.ZodError) {\n return NextResponse.json({ error: translate('warranty_claims.errors.invalidInput', 'Invalid input') }, { status: 400 })\n }\n logger.error('warranty_claims.stats.get failed', { err })\n return NextResponse.json({ error: translate('warranty_claims.errors.load_failed', 'Failed to load warranty claim data') }, { status: 500 })\n }\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Warranty Claims',\n summary: 'Warranty claim statistics',\n methods: {\n GET: {\n summary: 'Get warranty claim queue and resolution statistics',\n query: querySchema,\n responses: [\n {\n status: 200,\n description: 'Warranty claim statistics',\n schema: responseSchema,\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 { sql, type SelectQueryBuilder } from 'kysely'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport { CrudHttpError, isCrudHttpError, translateCrudErrorBody } 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 { resolveEffectiveWarrantyClaimSettings } from '../../lib/settings'\nimport { SLA_EXCLUDED_STATUSES } from '../../lib/deskFilters'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('warranty_claims')\n\ntype NumericAggregateValue = string | number | bigint | null\n\ntype WarrantyClaimsStatsTable = {\n id: string\n organization_id: string\n tenant_id: string\n status: string\n deleted_at: Date | null\n sla_due_at: Date | null\n sla_paused_at: Date | null\n assignee_user_id: string | null\n submitted_at: Date | null\n resolved_at: Date | null\n total_approved_amount: string | number | null\n total_recovered_amount: string | number | null\n currency_code: string | null\n}\n\ntype WarrantyClaimsStatsDb = {\n warranty_claims: WarrantyClaimsStatsTable\n}\n\ntype StatsRouteContext = {\n tenantId: string\n organizationIds: string[]\n userId: string | null\n em: EntityManager\n}\n\nconst querySchema = z.object({}).strict()\n\nconst OPEN_STATUSES = [\n 'submitted',\n 'in_review',\n 'info_requested',\n 'approved',\n 'awaiting_return',\n 'received',\n 'inspecting',\n]\n\nconst RESOLUTION_STATUSES = ['resolved', 'closed']\nconst THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1000\n\nconst recoveredByCurrencySchema = z.object({\n currencyCode: z.string().nullable(),\n total: z.number(),\n})\n\nconst resultSchema = z.object({\n openByStatus: z.record(z.string(), z.number().int().nonnegative()),\n overdue: z.number().int().nonnegative(),\n slaAtRisk: z.number().int().nonnegative(),\n assignedToMe: z.number().int().nonnegative(),\n resolvedLast30d: z.number().int().nonnegative(),\n avgResolutionDays: z.number().nullable(),\n approvalRatePct: z.number().nullable(),\n recoveredLast30dByCurrency: z.array(recoveredByCurrencySchema),\n slaAtRiskThresholdPct: z.number().int().min(1).max(100),\n})\n\nconst responseSchema = z.object({\n ok: z.literal(true),\n result: resultSchema,\n})\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['warranty_claims.claim.view'] },\n}\n\nfunction parseNumeric(value: NumericAggregateValue | undefined): number {\n if (value === undefined || value === null) return 0\n const parsed = Number(value)\n return Number.isFinite(parsed) ? parsed : 0\n}\n\nfunction applyOrganizationScope<O>(\n query: SelectQueryBuilder<WarrantyClaimsStatsDb, 'warranty_claims', O>,\n organizationIds: string[],\n): SelectQueryBuilder<WarrantyClaimsStatsDb, 'warranty_claims', O> {\n if (organizationIds.length === 1) {\n return query.where('organization_id', '=', organizationIds[0])\n }\n return query.where('organization_id', 'in', organizationIds)\n}\n\nfunction baseClaimsQuery<O>(\n query: SelectQueryBuilder<WarrantyClaimsStatsDb, 'warranty_claims', O>,\n context: StatsRouteContext,\n): SelectQueryBuilder<WarrantyClaimsStatsDb, 'warranty_claims', O> {\n return applyOrganizationScope(\n query\n .where('tenant_id', '=', context.tenantId)\n .where('deleted_at', 'is', null),\n context.organizationIds,\n )\n}\n\nasync function resolveStatsContext(req: Request): Promise<StatsRouteContext> {\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\n const { translate } = await resolveTranslations()\n if (!auth || !auth.tenantId) {\n throw new CrudHttpError(401, { error: translate('warranty_claims.errors.unauthorized', 'Unauthorized') })\n }\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n const organizationIds = Array.isArray(scope?.filterIds) && scope.filterIds.length > 0\n ? scope.filterIds\n : auth.orgId\n ? [auth.orgId]\n : []\n if (organizationIds.length === 0) {\n throw new CrudHttpError(400, { error: translate('warranty_claims.errors.organization_required', 'Organization context is required') })\n }\n return {\n tenantId: auth.tenantId,\n organizationIds,\n userId: typeof auth.sub === 'string' ? auth.sub : null,\n em: (container.resolve('em') as EntityManager).fork(),\n }\n}\n\nexport async function GET(req: Request) {\n try {\n const url = new URL(req.url)\n querySchema.parse(Object.fromEntries(url.searchParams))\n const context = await resolveStatsContext(req)\n const db = context.em.getKysely<WarrantyClaimsStatsDb>()\n const now = new Date()\n const thirtyDaysAgo = new Date(now.getTime() - THIRTY_DAYS_MS)\n const effectiveSettings = await resolveEffectiveWarrantyClaimSettings(context.em, {\n tenantId: context.tenantId,\n organizationId: context.organizationIds[0] ?? null,\n })\n\n const openRows = await baseClaimsQuery(\n db\n .selectFrom('warranty_claims')\n .select(['status', sql<NumericAggregateValue>`count(*)`.as('count')])\n .where('status', 'in', OPEN_STATUSES)\n .groupBy('status'),\n context,\n ).execute()\n const openByStatus: Record<string, number> = Object.fromEntries(OPEN_STATUSES.map((status) => [status, 0]))\n for (const row of openRows) {\n openByStatus[row.status] = Math.trunc(parseNumeric(row.count))\n }\n\n const overdueRow = await baseClaimsQuery(\n db\n .selectFrom('warranty_claims')\n .select(sql<NumericAggregateValue>`count(*)`.as('count'))\n .where('sla_due_at', '<', now)\n .where('sla_paused_at', 'is', null)\n .where('status', 'not in', [...SLA_EXCLUDED_STATUSES]),\n context,\n ).executeTakeFirst()\n const overdue = Math.trunc(parseNumeric(overdueRow?.count))\n\n const atRiskRow = await baseClaimsQuery(\n db\n .selectFrom('warranty_claims')\n .select(sql<NumericAggregateValue>`count(*)`.as('count'))\n .where('sla_due_at', '>', now)\n .where('sla_paused_at', 'is', null)\n .where('status', 'not in', [...SLA_EXCLUDED_STATUSES])\n .where('submitted_at', 'is not', null)\n .where(sql<boolean>`sla_due_at > submitted_at`)\n .where(sql<boolean>`extract(epoch from (${now}::timestamptz - submitted_at)) * 100 >= extract(epoch from (sla_due_at - submitted_at)) * ${effectiveSettings.slaAtRiskThresholdPct}`),\n context,\n ).executeTakeFirst()\n const slaAtRisk = Math.trunc(parseNumeric(atRiskRow?.count))\n\n let assignedToMe = 0\n if (context.userId) {\n const assignedRow = await baseClaimsQuery(\n db\n .selectFrom('warranty_claims')\n .select(sql<NumericAggregateValue>`count(*)`.as('count'))\n .where('status', 'in', OPEN_STATUSES)\n .where('assignee_user_id', '=', context.userId),\n context,\n ).executeTakeFirst()\n assignedToMe = Math.trunc(parseNumeric(assignedRow?.count))\n }\n\n const resolvedRow = await baseClaimsQuery(\n db\n .selectFrom('warranty_claims')\n .select(sql<NumericAggregateValue>`count(*)`.as('count'))\n .where('resolved_at', '>=', thirtyDaysAgo),\n context,\n ).executeTakeFirst()\n const resolvedLast30d = Math.trunc(parseNumeric(resolvedRow?.count))\n\n const avgRow = await baseClaimsQuery(\n db\n .selectFrom('warranty_claims')\n .select(sql<NumericAggregateValue>`avg(extract(epoch from (resolved_at - submitted_at)) / 86400.0)`.as('avgDays'))\n .where('resolved_at', '>=', thirtyDaysAgo)\n .where('submitted_at', 'is not', null),\n context,\n ).executeTakeFirst()\n const avgValue = avgRow?.avgDays\n const avgResolutionDays = avgValue === undefined || avgValue === null\n ? null\n : Number(parseNumeric(avgValue).toFixed(1))\n\n const approvalRow = await baseClaimsQuery(\n db\n .selectFrom('warranty_claims')\n .select([\n sql<NumericAggregateValue>`count(*)`.as('total'),\n sql<NumericAggregateValue>`count(*) filter (where total_approved_amount > 0)`.as('approved'),\n ])\n .where('status', 'in', RESOLUTION_STATUSES)\n .where('resolved_at', '>=', thirtyDaysAgo),\n context,\n ).executeTakeFirst()\n const approvalTotal = parseNumeric(approvalRow?.total)\n const approvalApproved = parseNumeric(approvalRow?.approved)\n const approvalRatePct = approvalTotal > 0 ? Math.round((approvalApproved / approvalTotal) * 100) : null\n\n const recoveredRows = await baseClaimsQuery(\n db\n .selectFrom('warranty_claims')\n .select([\n 'currency_code as currencyCode',\n sql<NumericAggregateValue>`coalesce(sum(total_recovered_amount), 0)`.as('total'),\n ])\n .where('status', 'in', RESOLUTION_STATUSES)\n .where('resolved_at', '>=', thirtyDaysAgo)\n .where('total_recovered_amount', '>', 0)\n .groupBy('currency_code'),\n context,\n ).execute()\n // `group by` returns rows in an unspecified order, so consumers that render a\n // single bucket would otherwise show an arbitrary currency. Sort largest-first\n // with the code as a tiebreak so the response is deterministic.\n const recoveredLast30dByCurrency = recoveredRows\n .map((row) => ({\n currencyCode: row.currencyCode ?? null,\n total: Number(parseNumeric(row.total).toFixed(2)),\n }))\n .sort((left, right) =>\n right.total - left.total\n || (left.currencyCode ?? '').localeCompare(right.currencyCode ?? ''))\n\n return NextResponse.json({\n ok: true,\n result: {\n openByStatus,\n overdue,\n slaAtRisk,\n assignedToMe,\n resolvedLast30d,\n avgResolutionDays,\n approvalRatePct,\n recoveredLast30dByCurrency,\n slaAtRiskThresholdPct: effectiveSettings.slaAtRiskThresholdPct,\n },\n })\n } catch (err) {\n const { translate } = await resolveTranslations()\n if (isCrudHttpError(err)) return NextResponse.json(translateCrudErrorBody(err.body, translate), { status: err.status })\n if (err instanceof z.ZodError) {\n return NextResponse.json({ error: translate('warranty_claims.errors.invalidInput', 'Invalid input') }, { status: 400 })\n }\n logger.error('warranty_claims.stats.get failed', { err })\n return NextResponse.json({ error: translate('warranty_claims.errors.load_failed', 'Failed to load warranty claim data') }, { status: 500 })\n }\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Warranty Claims',\n summary: 'Warranty claim statistics',\n methods: {\n GET: {\n summary: 'Get warranty claim queue and resolution statistics',\n query: querySchema,\n responses: [\n {\n status: 200,\n description: 'Warranty claim statistics',\n schema: responseSchema,\n },\n ],\n },\n },\n}\n"],
5
+ "mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAElB,SAAS,WAAoC;AAC7C,SAAS,8BAA8B;AACvC,SAAS,0BAA0B;AACnC,SAAS,0CAA0C;AACnD,SAAS,eAAe,iBAAiB,8BAA8B;AACvE,SAAS,2BAA2B;AAEpC,SAAS,6CAA6C;AACtD,SAAS,6BAA6B;AACtC,SAAS,oBAAoB;AAE7B,MAAM,SAAS,aAAa,iBAAiB;AA+B7C,MAAM,cAAc,EAAE,OAAO,CAAC,CAAC,EAAE,OAAO;AAExC,MAAM,gBAAgB;AAAA,EACpB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEA,MAAM,sBAAsB,CAAC,YAAY,QAAQ;AACjD,MAAM,iBAAiB,KAAK,KAAK,KAAK,KAAK;AAE3C,MAAM,4BAA4B,EAAE,OAAO;AAAA,EACzC,cAAc,EAAE,OAAO,EAAE,SAAS;AAAA,EAClC,OAAO,EAAE,OAAO;AAClB,CAAC;AAED,MAAM,eAAe,EAAE,OAAO;AAAA,EAC5B,cAAc,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY,CAAC;AAAA,EACjE,SAAS,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY;AAAA,EACtC,WAAW,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY;AAAA,EACxC,cAAc,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY;AAAA,EAC3C,iBAAiB,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY;AAAA,EAC9C,mBAAmB,EAAE,OAAO,EAAE,SAAS;AAAA,EACvC,iBAAiB,EAAE,OAAO,EAAE,SAAS;AAAA,EACrC,4BAA4B,EAAE,MAAM,yBAAyB;AAAA,EAC7D,uBAAuB,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG;AACxD,CAAC;AAED,MAAM,iBAAiB,EAAE,OAAO;AAAA,EAC9B,IAAI,EAAE,QAAQ,IAAI;AAAA,EAClB,QAAQ;AACV,CAAC;AAEM,MAAM,WAAW;AAAA,EACtB,KAAK,EAAE,aAAa,MAAM,iBAAiB,CAAC,4BAA4B,EAAE;AAC5E;AAEA,SAAS,aAAa,OAAkD;AACtE,MAAI,UAAU,UAAa,UAAU,KAAM,QAAO;AAClD,QAAM,SAAS,OAAO,KAAK;AAC3B,SAAO,OAAO,SAAS,MAAM,IAAI,SAAS;AAC5C;AAEA,SAAS,uBACP,OACA,iBACiE;AACjE,MAAI,gBAAgB,WAAW,GAAG;AAChC,WAAO,MAAM,MAAM,mBAAmB,KAAK,gBAAgB,CAAC,CAAC;AAAA,EAC/D;AACA,SAAO,MAAM,MAAM,mBAAmB,MAAM,eAAe;AAC7D;AAEA,SAAS,gBACP,OACA,SACiE;AACjE,SAAO;AAAA,IACL,MACG,MAAM,aAAa,KAAK,QAAQ,QAAQ,EACxC,MAAM,cAAc,MAAM,IAAI;AAAA,IACjC,QAAQ;AAAA,EACV;AACF;AAEA,eAAe,oBAAoB,KAA0C;AAC3E,QAAM,YAAY,MAAM,uBAAuB;AAC/C,QAAM,OAAO,MAAM,mBAAmB,GAAG;AACzC,QAAM,EAAE,UAAU,IAAI,MAAM,oBAAoB;AAChD,MAAI,CAAC,QAAQ,CAAC,KAAK,UAAU;AAC3B,UAAM,IAAI,cAAc,KAAK,EAAE,OAAO,UAAU,uCAAuC,cAAc,EAAE,CAAC;AAAA,EAC1G;AACA,QAAM,QAAQ,MAAM,mCAAmC,EAAE,WAAW,MAAM,SAAS,IAAI,CAAC;AACxF,QAAM,kBAAkB,MAAM,QAAQ,OAAO,SAAS,KAAK,MAAM,UAAU,SAAS,IAChF,MAAM,YACN,KAAK,QACH,CAAC,KAAK,KAAK,IACX,CAAC;AACP,MAAI,gBAAgB,WAAW,GAAG;AAChC,UAAM,IAAI,cAAc,KAAK,EAAE,OAAO,UAAU,gDAAgD,kCAAkC,EAAE,CAAC;AAAA,EACvI;AACA,SAAO;AAAA,IACL,UAAU,KAAK;AAAA,IACf;AAAA,IACA,QAAQ,OAAO,KAAK,QAAQ,WAAW,KAAK,MAAM;AAAA,IAClD,IAAK,UAAU,QAAQ,IAAI,EAAoB,KAAK;AAAA,EACtD;AACF;AAEA,eAAsB,IAAI,KAAc;AACtC,MAAI;AACF,UAAM,MAAM,IAAI,IAAI,IAAI,GAAG;AAC3B,gBAAY,MAAM,OAAO,YAAY,IAAI,YAAY,CAAC;AACtD,UAAM,UAAU,MAAM,oBAAoB,GAAG;AAC7C,UAAM,KAAK,QAAQ,GAAG,UAAiC;AACvD,UAAM,MAAM,oBAAI,KAAK;AACrB,UAAM,gBAAgB,IAAI,KAAK,IAAI,QAAQ,IAAI,cAAc;AAC7D,UAAM,oBAAoB,MAAM,sCAAsC,QAAQ,IAAI;AAAA,MAChF,UAAU,QAAQ;AAAA,MAClB,gBAAgB,QAAQ,gBAAgB,CAAC,KAAK;AAAA,IAChD,CAAC;AAED,UAAM,WAAW,MAAM;AAAA,MACrB,GACG,WAAW,iBAAiB,EAC5B,OAAO,CAAC,UAAU,cAAqC,GAAG,OAAO,CAAC,CAAC,EACnE,MAAM,UAAU,MAAM,aAAa,EACnC,QAAQ,QAAQ;AAAA,MACnB;AAAA,IACF,EAAE,QAAQ;AACV,UAAM,eAAuC,OAAO,YAAY,cAAc,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC,CAAC;AAC1G,eAAW,OAAO,UAAU;AAC1B,mBAAa,IAAI,MAAM,IAAI,KAAK,MAAM,aAAa,IAAI,KAAK,CAAC;AAAA,IAC/D;AAEA,UAAM,aAAa,MAAM;AAAA,MACvB,GACG,WAAW,iBAAiB,EAC5B,OAAO,cAAqC,GAAG,OAAO,CAAC,EACvD,MAAM,cAAc,KAAK,GAAG,EAC5B,MAAM,iBAAiB,MAAM,IAAI,EACjC,MAAM,UAAU,UAAU,CAAC,GAAG,qBAAqB,CAAC;AAAA,MACvD;AAAA,IACF,EAAE,iBAAiB;AACnB,UAAM,UAAU,KAAK,MAAM,aAAa,YAAY,KAAK,CAAC;AAE1D,UAAM,YAAY,MAAM;AAAA,MACtB,GACG,WAAW,iBAAiB,EAC5B,OAAO,cAAqC,GAAG,OAAO,CAAC,EACvD,MAAM,cAAc,KAAK,GAAG,EAC5B,MAAM,iBAAiB,MAAM,IAAI,EACjC,MAAM,UAAU,UAAU,CAAC,GAAG,qBAAqB,CAAC,EACpD,MAAM,gBAAgB,UAAU,IAAI,EACpC,MAAM,8BAAuC,EAC7C,MAAM,0BAAmC,GAAG,6FAA6F,kBAAkB,qBAAqB,EAAE;AAAA,MACrL;AAAA,IACF,EAAE,iBAAiB;AACnB,UAAM,YAAY,KAAK,MAAM,aAAa,WAAW,KAAK,CAAC;AAE3D,QAAI,eAAe;AACnB,QAAI,QAAQ,QAAQ;AAClB,YAAM,cAAc,MAAM;AAAA,QACxB,GACG,WAAW,iBAAiB,EAC5B,OAAO,cAAqC,GAAG,OAAO,CAAC,EACvD,MAAM,UAAU,MAAM,aAAa,EACnC,MAAM,oBAAoB,KAAK,QAAQ,MAAM;AAAA,QAChD;AAAA,MACF,EAAE,iBAAiB;AACnB,qBAAe,KAAK,MAAM,aAAa,aAAa,KAAK,CAAC;AAAA,IAC5D;AAEA,UAAM,cAAc,MAAM;AAAA,MACxB,GACG,WAAW,iBAAiB,EAC5B,OAAO,cAAqC,GAAG,OAAO,CAAC,EACvD,MAAM,eAAe,MAAM,aAAa;AAAA,MAC3C;AAAA,IACF,EAAE,iBAAiB;AACnB,UAAM,kBAAkB,KAAK,MAAM,aAAa,aAAa,KAAK,CAAC;AAEnE,UAAM,SAAS,MAAM;AAAA,MACnB,GACG,WAAW,iBAAiB,EAC5B,OAAO,qEAA4F,GAAG,SAAS,CAAC,EAChH,MAAM,eAAe,MAAM,aAAa,EACxC,MAAM,gBAAgB,UAAU,IAAI;AAAA,MACvC;AAAA,IACF,EAAE,iBAAiB;AACnB,UAAM,WAAW,QAAQ;AACzB,UAAM,oBAAoB,aAAa,UAAa,aAAa,OAC7D,OACA,OAAO,aAAa,QAAQ,EAAE,QAAQ,CAAC,CAAC;AAE5C,UAAM,cAAc,MAAM;AAAA,MACxB,GACG,WAAW,iBAAiB,EAC5B,OAAO;AAAA,QACN,cAAqC,GAAG,OAAO;AAAA,QAC/C,uDAA8E,GAAG,UAAU;AAAA,MAC7F,CAAC,EACA,MAAM,UAAU,MAAM,mBAAmB,EACzC,MAAM,eAAe,MAAM,aAAa;AAAA,MAC3C;AAAA,IACF,EAAE,iBAAiB;AACnB,UAAM,gBAAgB,aAAa,aAAa,KAAK;AACrD,UAAM,mBAAmB,aAAa,aAAa,QAAQ;AAC3D,UAAM,kBAAkB,gBAAgB,IAAI,KAAK,MAAO,mBAAmB,gBAAiB,GAAG,IAAI;AAEnG,UAAM,gBAAgB,MAAM;AAAA,MAC1B,GACG,WAAW,iBAAiB,EAC5B,OAAO;AAAA,QACN;AAAA,QACA,8CAAqE,GAAG,OAAO;AAAA,MACjF,CAAC,EACA,MAAM,UAAU,MAAM,mBAAmB,EACzC,MAAM,eAAe,MAAM,aAAa,EACxC,MAAM,0BAA0B,KAAK,CAAC,EACtC,QAAQ,eAAe;AAAA,MAC1B;AAAA,IACF,EAAE,QAAQ;AAIV,UAAM,6BAA6B,cAChC,IAAI,CAAC,SAAS;AAAA,MACb,cAAc,IAAI,gBAAgB;AAAA,MAClC,OAAO,OAAO,aAAa,IAAI,KAAK,EAAE,QAAQ,CAAC,CAAC;AAAA,IAClD,EAAE,EACD,KAAK,CAAC,MAAM,UACX,MAAM,QAAQ,KAAK,UACf,KAAK,gBAAgB,IAAI,cAAc,MAAM,gBAAgB,EAAE,CAAC;AAExE,WAAO,aAAa,KAAK;AAAA,MACvB,IAAI;AAAA,MACJ,QAAQ;AAAA,QACN;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,uBAAuB,kBAAkB;AAAA,MAC3C;AAAA,IACF,CAAC;AAAA,EACH,SAAS,KAAK;AACZ,UAAM,EAAE,UAAU,IAAI,MAAM,oBAAoB;AAChD,QAAI,gBAAgB,GAAG,EAAG,QAAO,aAAa,KAAK,uBAAuB,IAAI,MAAM,SAAS,GAAG,EAAE,QAAQ,IAAI,OAAO,CAAC;AACtH,QAAI,eAAe,EAAE,UAAU;AAC7B,aAAO,aAAa,KAAK,EAAE,OAAO,UAAU,uCAAuC,eAAe,EAAE,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IACxH;AACA,WAAO,MAAM,oCAAoC,EAAE,IAAI,CAAC;AACxD,WAAO,aAAa,KAAK,EAAE,OAAO,UAAU,sCAAsC,oCAAoC,EAAE,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EAC5I;AACF;AAEO,MAAM,UAA2B;AAAA,EACtC,KAAK;AAAA,EACL,SAAS;AAAA,EACT,SAAS;AAAA,IACP,KAAK;AAAA,MACH,SAAS;AAAA,MACT,OAAO;AAAA,MACP,WAAW;AAAA,QACT;AAAA,UACE,QAAQ;AAAA,UACR,aAAa;AAAA,UACb,QAAQ;AAAA,QACV;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;",
6
6
  "names": []
7
7
  }
@@ -4,7 +4,7 @@ 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 { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
7
- import { CrudHttpError, isCrudHttpError } from "@open-mercato/shared/lib/crud/errors";
7
+ import { CrudHttpError, isCrudHttpError, translateCrudErrorBody } from "@open-mercato/shared/lib/crud/errors";
8
8
  import { getCommandInterceptorHttpRejection } from "@open-mercato/shared/lib/commands/errors";
9
9
  import { readJsonSafe } from "@open-mercato/shared/lib/http/readJsonSafe";
10
10
  import { runRouteMutationGuards } from "@open-mercato/shared/lib/crud/route-mutation-guard";
@@ -87,12 +87,12 @@ async function POST(req) {
87
87
  await guarded.runAfterSuccess();
88
88
  return NextResponse.json({ ok: true, claimId: result?.claimId ?? commandInput.id });
89
89
  } catch (err) {
90
- if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status });
90
+ const { translate } = await resolveTranslations();
91
+ if (isCrudHttpError(err)) return NextResponse.json(translateCrudErrorBody(err.body, translate), { status: err.status });
91
92
  const interceptorRejection = getCommandInterceptorHttpRejection(err);
92
93
  if (interceptorRejection) {
93
94
  return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status });
94
95
  }
95
- const { translate } = await resolveTranslations();
96
96
  if (err instanceof z.ZodError) {
97
97
  return NextResponse.json({ error: translate("warranty_claims.errors.invalidInput", "Invalid input") }, { status: 400 });
98
98
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/warranty_claims/api/submit/route.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport type { CommandBus, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { getCommandInterceptorHttpRejection } from '@open-mercato/shared/lib/commands/errors'\nimport { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'\nimport { runRouteMutationGuards, type RouteMutationGuardResult } from '@open-mercato/shared/lib/crud/route-mutation-guard'\nimport { withScopedPayload } from '@open-mercato/shared/lib/api/scoped'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { WARRANTY_CLAIM_RESOURCE_KIND } from '../../commands/shared'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('warranty_claims')\n\n// Scope is derived from the authenticated principal, never accepted from the body \u2014\n// mirroring the sibling transition/assign routes. `withScopedPayload` prefers a\n// caller-supplied tenantId/organizationId over the actor's context, so admitting them\n// here would let the request choose its own scope.\nconst submitSchema = z\n .object({\n id: z.string().uuid(),\n })\n .strict()\n\ntype SubmitInput = z.infer<typeof submitSchema>\n\ntype ActionRouteContext = {\n ctx: CommandRuntimeContext\n tenantId: string\n organizationId: string\n translate: (key: string, fallback?: string) => string\n}\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['warranty_claims.claim.manage'] },\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\nasync function resolveActionContext(req: Request): Promise<ActionRouteContext> {\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\n const { translate } = await resolveTranslations()\n if (!auth || !auth.tenantId) {\n throw new CrudHttpError(401, { error: translate('warranty_claims.errors.unauthorized', 'Unauthorized') })\n }\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n const organizationId = scope?.selectedId ?? auth.orgId ?? null\n if (!organizationId) {\n throw new CrudHttpError(400, { error: translate('warranty_claims.errors.organization_required', 'Organization context is required') })\n }\n return {\n ctx: {\n container,\n auth,\n organizationScope: scope,\n selectedOrganizationId: organizationId,\n organizationIds: scope?.filterIds ?? (auth.orgId ? [auth.orgId] : null),\n request: req,\n },\n tenantId: auth.tenantId,\n organizationId,\n translate,\n }\n}\n\nasync function runGuard(\n req: Request,\n context: ActionRouteContext,\n input: SubmitInput,\n): Promise<RouteMutationGuardResult> {\n const userId = context.ctx.auth?.sub\n if (!userId) {\n throw new CrudHttpError(401, { error: context.translate('warranty_claims.errors.unauthorized', 'Unauthorized') })\n }\n return runRouteMutationGuards({\n container: context.ctx.container,\n req,\n auth: {\n userId,\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n },\n input: {\n resourceKind: WARRANTY_CLAIM_RESOURCE_KIND,\n resourceId: input.id,\n operation: 'custom',\n mutationPayload: { ...input },\n },\n })\n}\n\nexport async function POST(req: Request) {\n try {\n const context = await resolveActionContext(req)\n const payload = toRecord(await readJsonSafe(req, {}))\n const scopedPayload = toRecord(withScopedPayload(payload, context.ctx, context.translate))\n const input = submitSchema.parse({ id: scopedPayload.id })\n const guarded = await runGuard(req, context, input)\n if (!guarded.ok) {\n return guarded.response\n }\n const commandInput = guarded.modifiedPayload ? submitSchema.parse(guarded.modifiedPayload) : input\n\n const commandBus = context.ctx.container.resolve('commandBus') as CommandBus\n const { result } = await commandBus.execute<SubmitInput, { claimId: string }>(\n 'warranty_claims.claim.submit',\n { input: commandInput, ctx: context.ctx },\n )\n\n await guarded.runAfterSuccess()\n\n return NextResponse.json({ ok: true, claimId: result?.claimId ?? commandInput.id })\n } catch (err) {\n if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status })\n const interceptorRejection = getCommandInterceptorHttpRejection(err)\n if (interceptorRejection) {\n return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status })\n }\n const { translate } = await resolveTranslations()\n if (err instanceof z.ZodError) {\n return NextResponse.json({ error: translate('warranty_claims.errors.invalidInput', 'Invalid input') }, { status: 400 })\n }\n logger.error('warranty_claims.submit.post failed', { err })\n return NextResponse.json({ error: translate('warranty_claims.errors.save_failed', 'Failed to save warranty claim') }, { status: 400 })\n }\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Warranty Claims',\n summary: 'Submit warranty claim',\n methods: {\n POST: {\n summary: 'Submit a draft claim',\n requestBody: { contentType: 'application/json', schema: submitSchema },\n responses: [\n {\n status: 200,\n description: 'Claim 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 { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport type { CommandBus, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { CrudHttpError, isCrudHttpError, translateCrudErrorBody } from '@open-mercato/shared/lib/crud/errors'\nimport { getCommandInterceptorHttpRejection } from '@open-mercato/shared/lib/commands/errors'\nimport { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'\nimport { runRouteMutationGuards, type RouteMutationGuardResult } from '@open-mercato/shared/lib/crud/route-mutation-guard'\nimport { withScopedPayload } from '@open-mercato/shared/lib/api/scoped'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { WARRANTY_CLAIM_RESOURCE_KIND } from '../../commands/shared'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('warranty_claims')\n\n// Scope is derived from the authenticated principal, never accepted from the body \u2014\n// mirroring the sibling transition/assign routes. `withScopedPayload` prefers a\n// caller-supplied tenantId/organizationId over the actor's context, so admitting them\n// here would let the request choose its own scope.\nconst submitSchema = z\n .object({\n id: z.string().uuid(),\n })\n .strict()\n\ntype SubmitInput = z.infer<typeof submitSchema>\n\ntype ActionRouteContext = {\n ctx: CommandRuntimeContext\n tenantId: string\n organizationId: string\n translate: (key: string, fallback?: string) => string\n}\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['warranty_claims.claim.manage'] },\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\nasync function resolveActionContext(req: Request): Promise<ActionRouteContext> {\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\n const { translate } = await resolveTranslations()\n if (!auth || !auth.tenantId) {\n throw new CrudHttpError(401, { error: translate('warranty_claims.errors.unauthorized', 'Unauthorized') })\n }\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n const organizationId = scope?.selectedId ?? auth.orgId ?? null\n if (!organizationId) {\n throw new CrudHttpError(400, { error: translate('warranty_claims.errors.organization_required', 'Organization context is required') })\n }\n return {\n ctx: {\n container,\n auth,\n organizationScope: scope,\n selectedOrganizationId: organizationId,\n organizationIds: scope?.filterIds ?? (auth.orgId ? [auth.orgId] : null),\n request: req,\n },\n tenantId: auth.tenantId,\n organizationId,\n translate,\n }\n}\n\nasync function runGuard(\n req: Request,\n context: ActionRouteContext,\n input: SubmitInput,\n): Promise<RouteMutationGuardResult> {\n const userId = context.ctx.auth?.sub\n if (!userId) {\n throw new CrudHttpError(401, { error: context.translate('warranty_claims.errors.unauthorized', 'Unauthorized') })\n }\n return runRouteMutationGuards({\n container: context.ctx.container,\n req,\n auth: {\n userId,\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n },\n input: {\n resourceKind: WARRANTY_CLAIM_RESOURCE_KIND,\n resourceId: input.id,\n operation: 'custom',\n mutationPayload: { ...input },\n },\n })\n}\n\nexport async function POST(req: Request) {\n try {\n const context = await resolveActionContext(req)\n const payload = toRecord(await readJsonSafe(req, {}))\n const scopedPayload = toRecord(withScopedPayload(payload, context.ctx, context.translate))\n const input = submitSchema.parse({ id: scopedPayload.id })\n const guarded = await runGuard(req, context, input)\n if (!guarded.ok) {\n return guarded.response\n }\n const commandInput = guarded.modifiedPayload ? submitSchema.parse(guarded.modifiedPayload) : input\n\n const commandBus = context.ctx.container.resolve('commandBus') as CommandBus\n const { result } = await commandBus.execute<SubmitInput, { claimId: string }>(\n 'warranty_claims.claim.submit',\n { input: commandInput, ctx: context.ctx },\n )\n\n await guarded.runAfterSuccess()\n\n return NextResponse.json({ ok: true, claimId: result?.claimId ?? commandInput.id })\n } catch (err) {\n const { translate } = await resolveTranslations()\n if (isCrudHttpError(err)) return NextResponse.json(translateCrudErrorBody(err.body, translate), { status: err.status })\n const interceptorRejection = getCommandInterceptorHttpRejection(err)\n if (interceptorRejection) {\n return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status })\n }\n if (err instanceof z.ZodError) {\n return NextResponse.json({ error: translate('warranty_claims.errors.invalidInput', 'Invalid input') }, { status: 400 })\n }\n logger.error('warranty_claims.submit.post failed', { err })\n return NextResponse.json({ error: translate('warranty_claims.errors.save_failed', 'Failed to save warranty claim') }, { status: 400 })\n }\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Warranty Claims',\n summary: 'Submit warranty claim',\n methods: {\n POST: {\n summary: 'Submit a draft claim',\n requestBody: { contentType: 'application/json', schema: submitSchema },\n responses: [\n {\n status: 200,\n description: 'Claim 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
  }
@@ -4,7 +4,7 @@ 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 { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
7
- import { CrudHttpError, isCrudHttpError } from "@open-mercato/shared/lib/crud/errors";
7
+ import { CrudHttpError, isCrudHttpError, translateCrudErrorBody } from "@open-mercato/shared/lib/crud/errors";
8
8
  import { getCommandInterceptorHttpRejection } from "@open-mercato/shared/lib/commands/errors";
9
9
  import { readJsonSafe } from "@open-mercato/shared/lib/http/readJsonSafe";
10
10
  import { runRouteMutationGuards } from "@open-mercato/shared/lib/crud/route-mutation-guard";
@@ -93,12 +93,12 @@ async function POST(req) {
93
93
  await guarded.runAfterSuccess();
94
94
  return NextResponse.json({ ok: true, claimId: result?.claimId ?? commandInput.id });
95
95
  } catch (err) {
96
- if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status });
96
+ const { translate } = await resolveTranslations();
97
+ if (isCrudHttpError(err)) return NextResponse.json(translateCrudErrorBody(err.body, translate), { status: err.status });
97
98
  const interceptorRejection = getCommandInterceptorHttpRejection(err);
98
99
  if (interceptorRejection) {
99
100
  return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status });
100
101
  }
101
- const { translate } = await resolveTranslations();
102
102
  if (err instanceof z.ZodError) {
103
103
  return NextResponse.json({ error: translate("warranty_claims.errors.invalidInput", "Invalid input") }, { status: 400 });
104
104
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/warranty_claims/api/transition/route.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport type { CommandBus, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { getCommandInterceptorHttpRejection } from '@open-mercato/shared/lib/commands/errors'\nimport { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'\nimport { runRouteMutationGuards, type RouteMutationGuardResult } from '@open-mercato/shared/lib/crud/route-mutation-guard'\nimport { withScopedPayload } from '@open-mercato/shared/lib/api/scoped'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { transitionClaimInputSchema, type TransitionClaimInput } from '../../data/validators'\nimport { WARRANTY_CLAIM_RESOURCE_KIND } from '../../commands/shared'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('warranty_claims')\n\ntype ActionRouteContext = {\n ctx: CommandRuntimeContext\n tenantId: string\n organizationId: string\n translate: (key: string, fallback?: string) => string\n}\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['warranty_claims.claim.manage'] },\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\nasync function resolveActionContext(req: Request): Promise<ActionRouteContext> {\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\n const { translate } = await resolveTranslations()\n if (!auth || !auth.tenantId) {\n throw new CrudHttpError(401, { error: translate('warranty_claims.errors.unauthorized', 'Unauthorized') })\n }\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n const organizationId = scope?.selectedId ?? auth.orgId ?? null\n if (!organizationId) {\n throw new CrudHttpError(400, { error: translate('warranty_claims.errors.organization_required', 'Organization context is required') })\n }\n return {\n ctx: {\n container,\n auth,\n organizationScope: scope,\n selectedOrganizationId: organizationId,\n organizationIds: scope?.filterIds ?? (auth.orgId ? [auth.orgId] : null),\n request: req,\n },\n tenantId: auth.tenantId,\n organizationId,\n translate,\n }\n}\n\nfunction toTransitionInput(scopedPayload: Record<string, unknown>): TransitionClaimInput {\n return transitionClaimInputSchema.parse({\n id: scopedPayload.id,\n toStatus: scopedPayload.toStatus,\n rejectionReasonCode: scopedPayload.rejectionReasonCode,\n resolutionSummary: scopedPayload.resolutionSummary,\n })\n}\n\nasync function runGuard(\n req: Request,\n context: ActionRouteContext,\n input: TransitionClaimInput,\n): Promise<RouteMutationGuardResult> {\n const userId = context.ctx.auth?.sub\n if (!userId) {\n throw new CrudHttpError(401, { error: context.translate('warranty_claims.errors.unauthorized', 'Unauthorized') })\n }\n return runRouteMutationGuards({\n container: context.ctx.container,\n req,\n auth: {\n userId,\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n },\n input: {\n resourceKind: WARRANTY_CLAIM_RESOURCE_KIND,\n resourceId: input.id,\n operation: 'custom',\n mutationPayload: { ...input },\n },\n })\n}\n\nexport async function POST(req: Request) {\n try {\n const context = await resolveActionContext(req)\n const payload = toRecord(await readJsonSafe(req, {}))\n const scopedPayload = withScopedPayload(payload, context.ctx, context.translate)\n const input = toTransitionInput(scopedPayload)\n const guarded = await runGuard(req, context, input)\n if (!guarded.ok) {\n return guarded.response\n }\n const commandInput = guarded.modifiedPayload ? toTransitionInput(guarded.modifiedPayload) : input\n\n const commandBus = context.ctx.container.resolve('commandBus') as CommandBus\n const { result } = await commandBus.execute<TransitionClaimInput, { claimId: string }>(\n 'warranty_claims.claim.transition',\n { input: commandInput, ctx: context.ctx },\n )\n\n await guarded.runAfterSuccess()\n\n return NextResponse.json({ ok: true, claimId: result?.claimId ?? commandInput.id })\n } catch (err) {\n if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status })\n const interceptorRejection = getCommandInterceptorHttpRejection(err)\n if (interceptorRejection) {\n return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status })\n }\n const { translate } = await resolveTranslations()\n if (err instanceof z.ZodError) {\n return NextResponse.json({ error: translate('warranty_claims.errors.invalidInput', 'Invalid input') }, { status: 400 })\n }\n logger.error('warranty_claims.transition.post failed', { err })\n return NextResponse.json({ error: translate('warranty_claims.errors.save_failed', 'Failed to save warranty claim') }, { status: 400 })\n }\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Warranty Claims',\n summary: 'Transition warranty claim',\n methods: {\n POST: {\n summary: 'Move a claim through the lifecycle',\n requestBody: { contentType: 'application/json', schema: transitionClaimInputSchema },\n responses: [\n {\n status: 200,\n description: 'Claim transitioned',\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 { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport type { CommandBus, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { CrudHttpError, isCrudHttpError, translateCrudErrorBody } from '@open-mercato/shared/lib/crud/errors'\nimport { getCommandInterceptorHttpRejection } from '@open-mercato/shared/lib/commands/errors'\nimport { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'\nimport { runRouteMutationGuards, type RouteMutationGuardResult } from '@open-mercato/shared/lib/crud/route-mutation-guard'\nimport { withScopedPayload } from '@open-mercato/shared/lib/api/scoped'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { transitionClaimInputSchema, type TransitionClaimInput } from '../../data/validators'\nimport { WARRANTY_CLAIM_RESOURCE_KIND } from '../../commands/shared'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('warranty_claims')\n\ntype ActionRouteContext = {\n ctx: CommandRuntimeContext\n tenantId: string\n organizationId: string\n translate: (key: string, fallback?: string) => string\n}\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['warranty_claims.claim.manage'] },\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\nasync function resolveActionContext(req: Request): Promise<ActionRouteContext> {\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\n const { translate } = await resolveTranslations()\n if (!auth || !auth.tenantId) {\n throw new CrudHttpError(401, { error: translate('warranty_claims.errors.unauthorized', 'Unauthorized') })\n }\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n const organizationId = scope?.selectedId ?? auth.orgId ?? null\n if (!organizationId) {\n throw new CrudHttpError(400, { error: translate('warranty_claims.errors.organization_required', 'Organization context is required') })\n }\n return {\n ctx: {\n container,\n auth,\n organizationScope: scope,\n selectedOrganizationId: organizationId,\n organizationIds: scope?.filterIds ?? (auth.orgId ? [auth.orgId] : null),\n request: req,\n },\n tenantId: auth.tenantId,\n organizationId,\n translate,\n }\n}\n\nfunction toTransitionInput(scopedPayload: Record<string, unknown>): TransitionClaimInput {\n return transitionClaimInputSchema.parse({\n id: scopedPayload.id,\n toStatus: scopedPayload.toStatus,\n rejectionReasonCode: scopedPayload.rejectionReasonCode,\n resolutionSummary: scopedPayload.resolutionSummary,\n })\n}\n\nasync function runGuard(\n req: Request,\n context: ActionRouteContext,\n input: TransitionClaimInput,\n): Promise<RouteMutationGuardResult> {\n const userId = context.ctx.auth?.sub\n if (!userId) {\n throw new CrudHttpError(401, { error: context.translate('warranty_claims.errors.unauthorized', 'Unauthorized') })\n }\n return runRouteMutationGuards({\n container: context.ctx.container,\n req,\n auth: {\n userId,\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n },\n input: {\n resourceKind: WARRANTY_CLAIM_RESOURCE_KIND,\n resourceId: input.id,\n operation: 'custom',\n mutationPayload: { ...input },\n },\n })\n}\n\nexport async function POST(req: Request) {\n try {\n const context = await resolveActionContext(req)\n const payload = toRecord(await readJsonSafe(req, {}))\n const scopedPayload = withScopedPayload(payload, context.ctx, context.translate)\n const input = toTransitionInput(scopedPayload)\n const guarded = await runGuard(req, context, input)\n if (!guarded.ok) {\n return guarded.response\n }\n const commandInput = guarded.modifiedPayload ? toTransitionInput(guarded.modifiedPayload) : input\n\n const commandBus = context.ctx.container.resolve('commandBus') as CommandBus\n const { result } = await commandBus.execute<TransitionClaimInput, { claimId: string }>(\n 'warranty_claims.claim.transition',\n { input: commandInput, ctx: context.ctx },\n )\n\n await guarded.runAfterSuccess()\n\n return NextResponse.json({ ok: true, claimId: result?.claimId ?? commandInput.id })\n } catch (err) {\n const { translate } = await resolveTranslations()\n if (isCrudHttpError(err)) return NextResponse.json(translateCrudErrorBody(err.body, translate), { status: err.status })\n const interceptorRejection = getCommandInterceptorHttpRejection(err)\n if (interceptorRejection) {\n return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status })\n }\n if (err instanceof z.ZodError) {\n return NextResponse.json({ error: translate('warranty_claims.errors.invalidInput', 'Invalid input') }, { status: 400 })\n }\n logger.error('warranty_claims.transition.post failed', { err })\n return NextResponse.json({ error: translate('warranty_claims.errors.save_failed', 'Failed to save warranty claim') }, { status: 400 })\n }\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Warranty Claims',\n summary: 'Transition warranty claim',\n methods: {\n POST: {\n summary: 'Move a claim through the lifecycle',\n requestBody: { contentType: 'application/json', schema: transitionClaimInputSchema },\n responses: [\n {\n status: 200,\n description: 'Claim transitioned',\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,7 @@ 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 { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
7
- import { CrudHttpError, isCrudHttpError } from "@open-mercato/shared/lib/crud/errors";
7
+ import { CrudHttpError, isCrudHttpError, translateCrudErrorBody } from "@open-mercato/shared/lib/crud/errors";
8
8
  import { getCommandInterceptorHttpRejection } from "@open-mercato/shared/lib/commands/errors";
9
9
  import { readJsonSafe } from "@open-mercato/shared/lib/http/readJsonSafe";
10
10
  import { runRouteMutationGuards } from "@open-mercato/shared/lib/crud/route-mutation-guard";
@@ -97,12 +97,12 @@ async function POST(req) {
97
97
  await guarded.runAfterSuccess();
98
98
  return NextResponse.json({ ok: true, claimId: createdClaimId });
99
99
  } catch (err) {
100
- if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status });
100
+ const { translate } = await resolveTranslations();
101
+ if (isCrudHttpError(err)) return NextResponse.json(translateCrudErrorBody(err.body, translate), { status: err.status });
101
102
  const interceptorRejection = getCommandInterceptorHttpRejection(err);
102
103
  if (interceptorRejection) {
103
104
  return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status });
104
105
  }
105
- const { translate } = await resolveTranslations();
106
106
  if (err instanceof z.ZodError) {
107
107
  return NextResponse.json({ error: translate("warranty_claims.errors.invalidInput", "Invalid input") }, { status: 400 });
108
108
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/warranty_claims/api/vendor-recovery/route.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport type { CommandBus, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { getCommandInterceptorHttpRejection } from '@open-mercato/shared/lib/commands/errors'\nimport { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'\nimport { runRouteMutationGuards, type RouteMutationGuardResult } from '@open-mercato/shared/lib/crud/route-mutation-guard'\nimport { withScopedPayload } from '@open-mercato/shared/lib/api/scoped'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { vendorRecoveryInputSchema, type VendorRecoveryInput } from '../../data/validators'\nimport { WARRANTY_CLAIM_RESOURCE_KIND } from '../../commands/shared'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('warranty_claims')\n\ntype ActionRouteContext = {\n ctx: CommandRuntimeContext\n tenantId: string\n organizationId: string\n translate: (key: string, fallback?: string) => string\n}\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['warranty_claims.claim.manage'] },\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\nasync function resolveActionContext(req: Request): Promise<ActionRouteContext> {\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\n const { translate } = await resolveTranslations()\n if (!auth || !auth.tenantId) {\n throw new CrudHttpError(401, { error: translate('warranty_claims.errors.unauthorized', 'Unauthorized') })\n }\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n const organizationId = scope?.selectedId ?? auth.orgId ?? null\n if (!organizationId) {\n throw new CrudHttpError(400, { error: translate('warranty_claims.errors.organization_required', 'Organization context is required') })\n }\n return {\n ctx: {\n container,\n auth,\n organizationScope: scope,\n selectedOrganizationId: organizationId,\n organizationIds: scope?.filterIds ?? (auth.orgId ? [auth.orgId] : null),\n request: req,\n },\n tenantId: auth.tenantId,\n organizationId,\n translate,\n }\n}\n\nfunction toVendorRecoveryInput(scopedPayload: Record<string, unknown>): VendorRecoveryInput {\n return vendorRecoveryInputSchema.parse({\n claimId: scopedPayload.claimId,\n lineIds: scopedPayload.lineIds,\n vendorName: scopedPayload.vendorName,\n vendorRef: scopedPayload.vendorRef,\n })\n}\n\nasync function runGuard(\n req: Request,\n context: ActionRouteContext,\n input: VendorRecoveryInput,\n): Promise<RouteMutationGuardResult> {\n const userId = context.ctx.auth?.sub\n if (!userId) {\n throw new CrudHttpError(401, { error: context.translate('warranty_claims.errors.unauthorized', 'Unauthorized') })\n }\n return runRouteMutationGuards({\n container: context.ctx.container,\n req,\n auth: {\n userId,\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n },\n input: {\n resourceKind: WARRANTY_CLAIM_RESOURCE_KIND,\n resourceId: input.claimId,\n operation: 'custom',\n mutationPayload: { ...input },\n },\n })\n}\n\nexport async function POST(req: Request) {\n try {\n const context = await resolveActionContext(req)\n const payload = toRecord(await readJsonSafe(req, {}))\n const scopedPayload = withScopedPayload(payload, context.ctx, context.translate)\n const input = toVendorRecoveryInput(scopedPayload)\n const guarded = await runGuard(req, context, input)\n if (!guarded.ok) {\n return guarded.response\n }\n const commandInput = guarded.modifiedPayload ? toVendorRecoveryInput(guarded.modifiedPayload) : input\n\n const commandBus = context.ctx.container.resolve('commandBus') as CommandBus\n const { result } = await commandBus.execute<VendorRecoveryInput, { claimId: string }>(\n 'warranty_claims.claim.create_vendor_recovery',\n { input: commandInput, ctx: context.ctx },\n )\n const createdClaimId = result?.claimId\n if (typeof createdClaimId !== 'string') {\n throw new CrudHttpError(400, { error: context.translate('warranty_claims.errors.notFound', 'Warranty claim not found.') })\n }\n\n await guarded.runAfterSuccess()\n\n return NextResponse.json({ ok: true, claimId: createdClaimId })\n } catch (err) {\n if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status })\n const interceptorRejection = getCommandInterceptorHttpRejection(err)\n if (interceptorRejection) {\n return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status })\n }\n const { translate } = await resolveTranslations()\n if (err instanceof z.ZodError) {\n return NextResponse.json({ error: translate('warranty_claims.errors.invalidInput', 'Invalid input') }, { status: 400 })\n }\n logger.error('warranty_claims.vendor-recovery.post failed', { err })\n return NextResponse.json({ error: translate('warranty_claims.errors.save_failed', 'Failed to save warranty claim') }, { status: 400 })\n }\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Warranty Claims',\n summary: 'Create vendor recovery claim',\n methods: {\n POST: {\n summary: 'Create a linked vendor-recovery claim from resolved source lines',\n requestBody: { contentType: 'application/json', schema: vendorRecoveryInputSchema },\n responses: [\n {\n status: 200,\n description: 'Vendor recovery claim created',\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 { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport type { CommandBus, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { CrudHttpError, isCrudHttpError, translateCrudErrorBody } from '@open-mercato/shared/lib/crud/errors'\nimport { getCommandInterceptorHttpRejection } from '@open-mercato/shared/lib/commands/errors'\nimport { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'\nimport { runRouteMutationGuards, type RouteMutationGuardResult } from '@open-mercato/shared/lib/crud/route-mutation-guard'\nimport { withScopedPayload } from '@open-mercato/shared/lib/api/scoped'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { vendorRecoveryInputSchema, type VendorRecoveryInput } from '../../data/validators'\nimport { WARRANTY_CLAIM_RESOURCE_KIND } from '../../commands/shared'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('warranty_claims')\n\ntype ActionRouteContext = {\n ctx: CommandRuntimeContext\n tenantId: string\n organizationId: string\n translate: (key: string, fallback?: string) => string\n}\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['warranty_claims.claim.manage'] },\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\nasync function resolveActionContext(req: Request): Promise<ActionRouteContext> {\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\n const { translate } = await resolveTranslations()\n if (!auth || !auth.tenantId) {\n throw new CrudHttpError(401, { error: translate('warranty_claims.errors.unauthorized', 'Unauthorized') })\n }\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n const organizationId = scope?.selectedId ?? auth.orgId ?? null\n if (!organizationId) {\n throw new CrudHttpError(400, { error: translate('warranty_claims.errors.organization_required', 'Organization context is required') })\n }\n return {\n ctx: {\n container,\n auth,\n organizationScope: scope,\n selectedOrganizationId: organizationId,\n organizationIds: scope?.filterIds ?? (auth.orgId ? [auth.orgId] : null),\n request: req,\n },\n tenantId: auth.tenantId,\n organizationId,\n translate,\n }\n}\n\nfunction toVendorRecoveryInput(scopedPayload: Record<string, unknown>): VendorRecoveryInput {\n return vendorRecoveryInputSchema.parse({\n claimId: scopedPayload.claimId,\n lineIds: scopedPayload.lineIds,\n vendorName: scopedPayload.vendorName,\n vendorRef: scopedPayload.vendorRef,\n })\n}\n\nasync function runGuard(\n req: Request,\n context: ActionRouteContext,\n input: VendorRecoveryInput,\n): Promise<RouteMutationGuardResult> {\n const userId = context.ctx.auth?.sub\n if (!userId) {\n throw new CrudHttpError(401, { error: context.translate('warranty_claims.errors.unauthorized', 'Unauthorized') })\n }\n return runRouteMutationGuards({\n container: context.ctx.container,\n req,\n auth: {\n userId,\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n },\n input: {\n resourceKind: WARRANTY_CLAIM_RESOURCE_KIND,\n resourceId: input.claimId,\n operation: 'custom',\n mutationPayload: { ...input },\n },\n })\n}\n\nexport async function POST(req: Request) {\n try {\n const context = await resolveActionContext(req)\n const payload = toRecord(await readJsonSafe(req, {}))\n const scopedPayload = withScopedPayload(payload, context.ctx, context.translate)\n const input = toVendorRecoveryInput(scopedPayload)\n const guarded = await runGuard(req, context, input)\n if (!guarded.ok) {\n return guarded.response\n }\n const commandInput = guarded.modifiedPayload ? toVendorRecoveryInput(guarded.modifiedPayload) : input\n\n const commandBus = context.ctx.container.resolve('commandBus') as CommandBus\n const { result } = await commandBus.execute<VendorRecoveryInput, { claimId: string }>(\n 'warranty_claims.claim.create_vendor_recovery',\n { input: commandInput, ctx: context.ctx },\n )\n const createdClaimId = result?.claimId\n if (typeof createdClaimId !== 'string') {\n throw new CrudHttpError(400, { error: context.translate('warranty_claims.errors.notFound', 'Warranty claim not found.') })\n }\n\n await guarded.runAfterSuccess()\n\n return NextResponse.json({ ok: true, claimId: createdClaimId })\n } catch (err) {\n const { translate } = await resolveTranslations()\n if (isCrudHttpError(err)) return NextResponse.json(translateCrudErrorBody(err.body, translate), { status: err.status })\n const interceptorRejection = getCommandInterceptorHttpRejection(err)\n if (interceptorRejection) {\n return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status })\n }\n if (err instanceof z.ZodError) {\n return NextResponse.json({ error: translate('warranty_claims.errors.invalidInput', 'Invalid input') }, { status: 400 })\n }\n logger.error('warranty_claims.vendor-recovery.post failed', { err })\n return NextResponse.json({ error: translate('warranty_claims.errors.save_failed', 'Failed to save warranty claim') }, { status: 400 })\n }\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Warranty Claims',\n summary: 'Create vendor recovery claim',\n methods: {\n POST: {\n summary: 'Create a linked vendor-recovery claim from resolved source lines',\n requestBody: { contentType: 'application/json', schema: vendorRecoveryInputSchema },\n responses: [\n {\n status: 200,\n description: 'Vendor recovery claim created',\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
  }
@@ -3,7 +3,7 @@ import { z } from "zod";
3
3
  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
- import { CrudHttpError, isCrudHttpError } from "@open-mercato/shared/lib/crud/errors";
6
+ import { CrudHttpError, isCrudHttpError, translateCrudErrorBody } from "@open-mercato/shared/lib/crud/errors";
7
7
  import { findWithDecryption } from "@open-mercato/shared/lib/encryption/find";
8
8
  import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
9
9
  import { WarrantyClaimLine, WarrantyVendorPolicy } from "../../data/entities.js";
@@ -120,8 +120,8 @@ async function GET(req) {
120
120
  }));
121
121
  return NextResponse.json({ ok: true, result: { claimId: claim.id, suggestions } });
122
122
  } catch (err) {
123
- if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status });
124
123
  const { translate } = await resolveTranslations();
124
+ if (isCrudHttpError(err)) return NextResponse.json(translateCrudErrorBody(err.body, translate), { status: err.status });
125
125
  if (err instanceof z.ZodError) {
126
126
  return NextResponse.json({ error: translate("warranty_claims.errors.invalidInput", "Invalid input") }, { status: 400 });
127
127
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/warranty_claims/api/vendor-recovery-suggestions/route.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { WarrantyClaimLine, WarrantyVendorPolicy } from '../../data/entities'\nimport { requireScopedClaim, type WarrantyClaimScope } from '../../commands/shared'\nimport { findVendorRecoveryMatches } from '../../lib/vendorPolicyRecovery'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('warranty_claims')\n\nconst querySchema = z.object({\n claimId: z.string().uuid(),\n}).strict()\n\nconst suggestionSchema = z.object({\n lineId: z.string().uuid(),\n lineNo: z.number().int(),\n productName: z.string().nullable(),\n sku: z.string().nullable(),\n vendorName: z.string(),\n policyId: z.string().uuid(),\n recoveryRatePct: z.string().nullable(),\n causalFault: z.string().nullable(),\n estimatedRecovery: z.string().nullable(),\n})\n\nconst responseSchema = z.object({\n ok: z.literal(true),\n result: z.object({\n claimId: z.string().uuid(),\n suggestions: z.array(suggestionSchema),\n }),\n})\n\ntype SuggestionsRouteContext = {\n tenantId: string\n organizationId: string\n scope: WarrantyClaimScope\n translate: (key: string, fallback?: string) => string\n em: EntityManager\n}\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['warranty_claims.claim.manage'] },\n}\n\nfunction toNullableString(value: number | string | null | undefined): string | null {\n if (typeof value === 'number') return Number.isFinite(value) ? String(value) : null\n if (typeof value !== 'string') return null\n const trimmed = value.trim()\n return trimmed ? trimmed : null\n}\n\nfunction toSuggestionVendorName(lineVendorName: string | null | undefined, policyVendorName: string): string {\n const normalized = lineVendorName?.trim()\n return normalized || policyVendorName\n}\n\nasync function resolveSuggestionsContext(req: Request): Promise<SuggestionsRouteContext> {\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\n const { translate } = await resolveTranslations()\n if (!auth || !auth.tenantId) {\n throw new CrudHttpError(401, { error: translate('warranty_claims.errors.unauthorized', 'Unauthorized') })\n }\n const organizationScope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n const organizationId = organizationScope?.selectedId ?? auth.orgId ?? null\n if (!organizationId) {\n throw new CrudHttpError(400, { error: translate('warranty_claims.errors.organization_required', 'Organization context is required') })\n }\n const em = container.resolve('em') as EntityManager\n return {\n tenantId: auth.tenantId,\n organizationId,\n scope: { tenantId: auth.tenantId, organizationId },\n translate,\n em,\n }\n}\n\nexport async function GET(req: Request) {\n try {\n const context = await resolveSuggestionsContext(req)\n const url = new URL(req.url)\n const query = querySchema.parse(Object.fromEntries(url.searchParams))\n const claim = await requireScopedClaim(context.em, query.claimId, context.scope, {}, context.translate('warranty_claims.errors.notFound', 'Claim not found.'))\n if (claim.claimType !== 'warranty' || (claim.status !== 'resolved' && claim.status !== 'closed')) {\n return NextResponse.json({ ok: true, result: { claimId: claim.id, suggestions: [] } })\n }\n\n const lines = await findWithDecryption(\n context.em,\n WarrantyClaimLine,\n {\n claim: claim.id,\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n lineStatus: 'resolved',\n vendorClaimLineId: null,\n deletedAt: null,\n },\n { orderBy: { lineNo: 'ASC' } },\n context.scope,\n )\n const policies = await findWithDecryption(\n context.em,\n WarrantyVendorPolicy,\n {\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n isActive: true,\n deletedAt: null,\n },\n { orderBy: { vendorName: 'ASC', updatedAt: 'DESC' } },\n context.scope,\n )\n const matches = findVendorRecoveryMatches({\n claim,\n lines,\n policies,\n requireWarrantyResolved: true,\n })\n const suggestions = matches.map((match) => ({\n lineId: match.line.id,\n lineNo: match.line.lineNo ?? 0,\n productName: match.line.productName ?? null,\n sku: match.line.sku ?? null,\n vendorName: toSuggestionVendorName(match.line.vendorName, match.policy.vendorName),\n policyId: match.policy.id,\n recoveryRatePct: toNullableString(match.policy.recoveryRatePct),\n causalFault: match.causalFault,\n estimatedRecovery: match.estimatedRecovery,\n }))\n return NextResponse.json({ ok: true, result: { claimId: claim.id, suggestions } })\n } catch (err) {\n if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status })\n const { translate } = await resolveTranslations()\n if (err instanceof z.ZodError) {\n return NextResponse.json({ error: translate('warranty_claims.errors.invalidInput', 'Invalid input') }, { status: 400 })\n }\n logger.error('[internal] warranty_claims.vendor-recovery-suggestions.get failed', { err })\n return NextResponse.json({ error: translate('warranty_claims.errors.load_failed', 'Failed to load warranty claim data') }, { status: 500 })\n }\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Warranty Claims',\n summary: 'List supplier recovery suggestions',\n methods: {\n GET: {\n summary: 'List resolved warranty claim lines eligible for supplier recovery',\n query: querySchema,\n responses: [\n {\n status: 200,\n description: 'Supplier recovery suggestions',\n schema: responseSchema,\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 { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport { CrudHttpError, isCrudHttpError, translateCrudErrorBody } from '@open-mercato/shared/lib/crud/errors'\nimport { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { WarrantyClaimLine, WarrantyVendorPolicy } from '../../data/entities'\nimport { requireScopedClaim, type WarrantyClaimScope } from '../../commands/shared'\nimport { findVendorRecoveryMatches } from '../../lib/vendorPolicyRecovery'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('warranty_claims')\n\nconst querySchema = z.object({\n claimId: z.string().uuid(),\n}).strict()\n\nconst suggestionSchema = z.object({\n lineId: z.string().uuid(),\n lineNo: z.number().int(),\n productName: z.string().nullable(),\n sku: z.string().nullable(),\n vendorName: z.string(),\n policyId: z.string().uuid(),\n recoveryRatePct: z.string().nullable(),\n causalFault: z.string().nullable(),\n estimatedRecovery: z.string().nullable(),\n})\n\nconst responseSchema = z.object({\n ok: z.literal(true),\n result: z.object({\n claimId: z.string().uuid(),\n suggestions: z.array(suggestionSchema),\n }),\n})\n\ntype SuggestionsRouteContext = {\n tenantId: string\n organizationId: string\n scope: WarrantyClaimScope\n translate: (key: string, fallback?: string) => string\n em: EntityManager\n}\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['warranty_claims.claim.manage'] },\n}\n\nfunction toNullableString(value: number | string | null | undefined): string | null {\n if (typeof value === 'number') return Number.isFinite(value) ? String(value) : null\n if (typeof value !== 'string') return null\n const trimmed = value.trim()\n return trimmed ? trimmed : null\n}\n\nfunction toSuggestionVendorName(lineVendorName: string | null | undefined, policyVendorName: string): string {\n const normalized = lineVendorName?.trim()\n return normalized || policyVendorName\n}\n\nasync function resolveSuggestionsContext(req: Request): Promise<SuggestionsRouteContext> {\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\n const { translate } = await resolveTranslations()\n if (!auth || !auth.tenantId) {\n throw new CrudHttpError(401, { error: translate('warranty_claims.errors.unauthorized', 'Unauthorized') })\n }\n const organizationScope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n const organizationId = organizationScope?.selectedId ?? auth.orgId ?? null\n if (!organizationId) {\n throw new CrudHttpError(400, { error: translate('warranty_claims.errors.organization_required', 'Organization context is required') })\n }\n const em = container.resolve('em') as EntityManager\n return {\n tenantId: auth.tenantId,\n organizationId,\n scope: { tenantId: auth.tenantId, organizationId },\n translate,\n em,\n }\n}\n\nexport async function GET(req: Request) {\n try {\n const context = await resolveSuggestionsContext(req)\n const url = new URL(req.url)\n const query = querySchema.parse(Object.fromEntries(url.searchParams))\n const claim = await requireScopedClaim(context.em, query.claimId, context.scope, {}, context.translate('warranty_claims.errors.notFound', 'Claim not found.'))\n if (claim.claimType !== 'warranty' || (claim.status !== 'resolved' && claim.status !== 'closed')) {\n return NextResponse.json({ ok: true, result: { claimId: claim.id, suggestions: [] } })\n }\n\n const lines = await findWithDecryption(\n context.em,\n WarrantyClaimLine,\n {\n claim: claim.id,\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n lineStatus: 'resolved',\n vendorClaimLineId: null,\n deletedAt: null,\n },\n { orderBy: { lineNo: 'ASC' } },\n context.scope,\n )\n const policies = await findWithDecryption(\n context.em,\n WarrantyVendorPolicy,\n {\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n isActive: true,\n deletedAt: null,\n },\n { orderBy: { vendorName: 'ASC', updatedAt: 'DESC' } },\n context.scope,\n )\n const matches = findVendorRecoveryMatches({\n claim,\n lines,\n policies,\n requireWarrantyResolved: true,\n })\n const suggestions = matches.map((match) => ({\n lineId: match.line.id,\n lineNo: match.line.lineNo ?? 0,\n productName: match.line.productName ?? null,\n sku: match.line.sku ?? null,\n vendorName: toSuggestionVendorName(match.line.vendorName, match.policy.vendorName),\n policyId: match.policy.id,\n recoveryRatePct: toNullableString(match.policy.recoveryRatePct),\n causalFault: match.causalFault,\n estimatedRecovery: match.estimatedRecovery,\n }))\n return NextResponse.json({ ok: true, result: { claimId: claim.id, suggestions } })\n } catch (err) {\n const { translate } = await resolveTranslations()\n if (isCrudHttpError(err)) return NextResponse.json(translateCrudErrorBody(err.body, translate), { status: err.status })\n if (err instanceof z.ZodError) {\n return NextResponse.json({ error: translate('warranty_claims.errors.invalidInput', 'Invalid input') }, { status: 400 })\n }\n logger.error('[internal] warranty_claims.vendor-recovery-suggestions.get failed', { err })\n return NextResponse.json({ error: translate('warranty_claims.errors.load_failed', 'Failed to load warranty claim data') }, { status: 500 })\n }\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Warranty Claims',\n summary: 'List supplier recovery suggestions',\n methods: {\n GET: {\n summary: 'List resolved warranty claim lines eligible for supplier recovery',\n query: querySchema,\n responses: [\n {\n status: 200,\n description: 'Supplier recovery suggestions',\n schema: responseSchema,\n },\n ],\n },\n },\n}\n"],
5
+ "mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAElB,SAAS,8BAA8B;AACvC,SAAS,0BAA0B;AACnC,SAAS,0CAA0C;AACnD,SAAS,eAAe,iBAAiB,8BAA8B;AACvE,SAAS,0BAA0B;AACnC,SAAS,2BAA2B;AAEpC,SAAS,mBAAmB,4BAA4B;AACxD,SAAS,0BAAmD;AAC5D,SAAS,iCAAiC;AAC1C,SAAS,oBAAoB;AAE7B,MAAM,SAAS,aAAa,iBAAiB;AAE7C,MAAM,cAAc,EAAE,OAAO;AAAA,EAC3B,SAAS,EAAE,OAAO,EAAE,KAAK;AAC3B,CAAC,EAAE,OAAO;AAEV,MAAM,mBAAmB,EAAE,OAAO;AAAA,EAChC,QAAQ,EAAE,OAAO,EAAE,KAAK;AAAA,EACxB,QAAQ,EAAE,OAAO,EAAE,IAAI;AAAA,EACvB,aAAa,EAAE,OAAO,EAAE,SAAS;AAAA,EACjC,KAAK,EAAE,OAAO,EAAE,SAAS;AAAA,EACzB,YAAY,EAAE,OAAO;AAAA,EACrB,UAAU,EAAE,OAAO,EAAE,KAAK;AAAA,EAC1B,iBAAiB,EAAE,OAAO,EAAE,SAAS;AAAA,EACrC,aAAa,EAAE,OAAO,EAAE,SAAS;AAAA,EACjC,mBAAmB,EAAE,OAAO,EAAE,SAAS;AACzC,CAAC;AAED,MAAM,iBAAiB,EAAE,OAAO;AAAA,EAC9B,IAAI,EAAE,QAAQ,IAAI;AAAA,EAClB,QAAQ,EAAE,OAAO;AAAA,IACf,SAAS,EAAE,OAAO,EAAE,KAAK;AAAA,IACzB,aAAa,EAAE,MAAM,gBAAgB;AAAA,EACvC,CAAC;AACH,CAAC;AAUM,MAAM,WAAW;AAAA,EACtB,KAAK,EAAE,aAAa,MAAM,iBAAiB,CAAC,8BAA8B,EAAE;AAC9E;AAEA,SAAS,iBAAiB,OAA0D;AAClF,MAAI,OAAO,UAAU,SAAU,QAAO,OAAO,SAAS,KAAK,IAAI,OAAO,KAAK,IAAI;AAC/E,MAAI,OAAO,UAAU,SAAU,QAAO;AACtC,QAAM,UAAU,MAAM,KAAK;AAC3B,SAAO,UAAU,UAAU;AAC7B;AAEA,SAAS,uBAAuB,gBAA2C,kBAAkC;AAC3G,QAAM,aAAa,gBAAgB,KAAK;AACxC,SAAO,cAAc;AACvB;AAEA,eAAe,0BAA0B,KAAgD;AACvF,QAAM,YAAY,MAAM,uBAAuB;AAC/C,QAAM,OAAO,MAAM,mBAAmB,GAAG;AACzC,QAAM,EAAE,UAAU,IAAI,MAAM,oBAAoB;AAChD,MAAI,CAAC,QAAQ,CAAC,KAAK,UAAU;AAC3B,UAAM,IAAI,cAAc,KAAK,EAAE,OAAO,UAAU,uCAAuC,cAAc,EAAE,CAAC;AAAA,EAC1G;AACA,QAAM,oBAAoB,MAAM,mCAAmC,EAAE,WAAW,MAAM,SAAS,IAAI,CAAC;AACpG,QAAM,iBAAiB,mBAAmB,cAAc,KAAK,SAAS;AACtE,MAAI,CAAC,gBAAgB;AACnB,UAAM,IAAI,cAAc,KAAK,EAAE,OAAO,UAAU,gDAAgD,kCAAkC,EAAE,CAAC;AAAA,EACvI;AACA,QAAM,KAAK,UAAU,QAAQ,IAAI;AACjC,SAAO;AAAA,IACL,UAAU,KAAK;AAAA,IACf;AAAA,IACA,OAAO,EAAE,UAAU,KAAK,UAAU,eAAe;AAAA,IACjD;AAAA,IACA;AAAA,EACF;AACF;AAEA,eAAsB,IAAI,KAAc;AACtC,MAAI;AACF,UAAM,UAAU,MAAM,0BAA0B,GAAG;AACnD,UAAM,MAAM,IAAI,IAAI,IAAI,GAAG;AAC3B,UAAM,QAAQ,YAAY,MAAM,OAAO,YAAY,IAAI,YAAY,CAAC;AACpE,UAAM,QAAQ,MAAM,mBAAmB,QAAQ,IAAI,MAAM,SAAS,QAAQ,OAAO,CAAC,GAAG,QAAQ,UAAU,mCAAmC,kBAAkB,CAAC;AAC7J,QAAI,MAAM,cAAc,cAAe,MAAM,WAAW,cAAc,MAAM,WAAW,UAAW;AAChG,aAAO,aAAa,KAAK,EAAE,IAAI,MAAM,QAAQ,EAAE,SAAS,MAAM,IAAI,aAAa,CAAC,EAAE,EAAE,CAAC;AAAA,IACvF;AAEA,UAAM,QAAQ,MAAM;AAAA,MAClB,QAAQ;AAAA,MACR;AAAA,MACA;AAAA,QACE,OAAO,MAAM;AAAA,QACb,UAAU,QAAQ;AAAA,QAClB,gBAAgB,QAAQ;AAAA,QACxB,YAAY;AAAA,QACZ,mBAAmB;AAAA,QACnB,WAAW;AAAA,MACb;AAAA,MACA,EAAE,SAAS,EAAE,QAAQ,MAAM,EAAE;AAAA,MAC7B,QAAQ;AAAA,IACV;AACA,UAAM,WAAW,MAAM;AAAA,MACrB,QAAQ;AAAA,MACR;AAAA,MACA;AAAA,QACE,UAAU,QAAQ;AAAA,QAClB,gBAAgB,QAAQ;AAAA,QACxB,UAAU;AAAA,QACV,WAAW;AAAA,MACb;AAAA,MACA,EAAE,SAAS,EAAE,YAAY,OAAO,WAAW,OAAO,EAAE;AAAA,MACpD,QAAQ;AAAA,IACV;AACA,UAAM,UAAU,0BAA0B;AAAA,MACxC;AAAA,MACA;AAAA,MACA;AAAA,MACA,yBAAyB;AAAA,IAC3B,CAAC;AACD,UAAM,cAAc,QAAQ,IAAI,CAAC,WAAW;AAAA,MAC1C,QAAQ,MAAM,KAAK;AAAA,MACnB,QAAQ,MAAM,KAAK,UAAU;AAAA,MAC7B,aAAa,MAAM,KAAK,eAAe;AAAA,MACvC,KAAK,MAAM,KAAK,OAAO;AAAA,MACvB,YAAY,uBAAuB,MAAM,KAAK,YAAY,MAAM,OAAO,UAAU;AAAA,MACjF,UAAU,MAAM,OAAO;AAAA,MACvB,iBAAiB,iBAAiB,MAAM,OAAO,eAAe;AAAA,MAC9D,aAAa,MAAM;AAAA,MACnB,mBAAmB,MAAM;AAAA,IAC3B,EAAE;AACF,WAAO,aAAa,KAAK,EAAE,IAAI,MAAM,QAAQ,EAAE,SAAS,MAAM,IAAI,YAAY,EAAE,CAAC;AAAA,EACnF,SAAS,KAAK;AACZ,UAAM,EAAE,UAAU,IAAI,MAAM,oBAAoB;AAChD,QAAI,gBAAgB,GAAG,EAAG,QAAO,aAAa,KAAK,uBAAuB,IAAI,MAAM,SAAS,GAAG,EAAE,QAAQ,IAAI,OAAO,CAAC;AACtH,QAAI,eAAe,EAAE,UAAU;AAC7B,aAAO,aAAa,KAAK,EAAE,OAAO,UAAU,uCAAuC,eAAe,EAAE,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IACxH;AACA,WAAO,MAAM,qEAAqE,EAAE,IAAI,CAAC;AACzF,WAAO,aAAa,KAAK,EAAE,OAAO,UAAU,sCAAsC,oCAAoC,EAAE,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EAC5I;AACF;AAEO,MAAM,UAA2B;AAAA,EACtC,KAAK;AAAA,EACL,SAAS;AAAA,EACT,SAAS;AAAA,IACP,KAAK;AAAA,MACH,SAAS;AAAA,MACT,OAAO;AAAA,MACP,WAAW;AAAA,QACT;AAAA,UACE,QAAQ;AAAA,UACR,aAAa;AAAA,UACb,QAAQ;AAAA,QACV;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;",
6
6
  "names": []
7
7
  }
@@ -20,6 +20,7 @@ import { buildCrudExportUrl } from "@open-mercato/ui/backend/utils/crud";
20
20
  import { buildOptimisticLockHeader } from "@open-mercato/ui/backend/utils/optimisticLock";
21
21
  import { surfaceRecordConflict } from "@open-mercato/ui/backend/conflicts";
22
22
  import { useCurrentUserId } from "@open-mercato/ui/backend/utils/useCurrentUserId";
23
+ import { useCurrentOrganization } from "@open-mercato/ui/backend/BackendChromeProvider";
23
24
  import { useOrganizationScopeVersion } from "@open-mercato/shared/lib/frontend/useOrganizationScope";
24
25
  import { emitProgressUpdate } from "@open-mercato/shared/lib/frontend/progressEvents";
25
26
  import { useLocale, useT } from "@open-mercato/shared/lib/i18n/context";
@@ -292,6 +293,7 @@ function WarrantyClaimsPage() {
292
293
  const pathname = usePathname();
293
294
  const searchParams = useSearchParams();
294
295
  const scopeVersion = useOrganizationScopeVersion();
296
+ const activeOrgId = useCurrentOrganization()?.id ?? null;
295
297
  const { confirm, ConfirmDialogElement } = useConfirmDialog();
296
298
  const currentUserId = useCurrentUserId();
297
299
  const initialUrlStateRef = React.useRef(null);
@@ -469,7 +471,7 @@ function WarrantyClaimsPage() {
469
471
  [t]
470
472
  );
471
473
  const loadAssignableStaffOptions = React.useCallback(async (query) => {
472
- const page2 = await fetchAssignableStaffMembersPage(query ?? "", { pageSize: 24 });
474
+ const page2 = await fetchAssignableStaffMembersPage(query ?? "", { pageSize: 24, activeOrgId });
473
475
  const options = page2.items.map((member) => ({
474
476
  value: member.userId,
475
477
  label: staffOptionLabel(member)
@@ -481,7 +483,7 @@ function WarrantyClaimsPage() {
481
483
  },
482
484
  ...options
483
485
  ];
484
- }, [t]);
486
+ }, [activeOrgId, t]);
485
487
  const formatAssigneeFilterValue = React.useCallback((value) => {
486
488
  if (value === UNASSIGNED_ASSIGNEE_VALUE) return t("warranty_claims.form.assigneeUserId.unassigned", "Unassigned");
487
489
  return assigneeDisplayNames[value] ?? t("warranty_claims.list.assignee.unknownUser", "Unknown user");