@open-mercato/core 0.6.8-develop.6998.1.d2bc46c56c → 0.6.8-develop.7001.1.03608ab252

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 (651) hide show
  1. package/.turbo/turbo-build.log +2 -2
  2. package/dist/generated/entities/warranty_claim/index.js +105 -0
  3. package/dist/generated/entities/warranty_claim/index.js.map +7 -0
  4. package/dist/generated/entities/warranty_claim_event/index.js +25 -0
  5. package/dist/generated/entities/warranty_claim_event/index.js.map +7 -0
  6. package/dist/generated/entities/warranty_claim_line/index.js +77 -0
  7. package/dist/generated/entities/warranty_claim_line/index.js.map +7 -0
  8. package/dist/generated/entities/warranty_claim_registration/index.js +43 -0
  9. package/dist/generated/entities/warranty_claim_registration/index.js.map +7 -0
  10. package/dist/generated/entities/warranty_claim_sequence/index.js +17 -0
  11. package/dist/generated/entities/warranty_claim_sequence/index.js.map +7 -0
  12. package/dist/generated/entities/warranty_claim_settings/index.js +43 -0
  13. package/dist/generated/entities/warranty_claim_settings/index.js.map +7 -0
  14. package/dist/generated/entities/warranty_claim_sla_signal/index.js +25 -0
  15. package/dist/generated/entities/warranty_claim_sla_signal/index.js.map +7 -0
  16. package/dist/generated/entities/warranty_troubleshooting_guide/index.js +25 -0
  17. package/dist/generated/entities/warranty_troubleshooting_guide/index.js.map +7 -0
  18. package/dist/generated/entities/warranty_vendor_policy/index.js +31 -0
  19. package/dist/generated/entities/warranty_vendor_policy/index.js.map +7 -0
  20. package/dist/generated/entities.ids.generated.js +12 -0
  21. package/dist/generated/entities.ids.generated.js.map +2 -2
  22. package/dist/generated/entity-fields-registry.js +200 -0
  23. package/dist/generated/entity-fields-registry.js.map +2 -2
  24. package/dist/helpers/integration/api.js +3 -2
  25. package/dist/helpers/integration/api.js.map +2 -2
  26. package/dist/helpers/integration/salesFixtures.js +13 -6
  27. package/dist/helpers/integration/salesFixtures.js.map +2 -2
  28. package/dist/modules/attachments/di.js +18 -0
  29. package/dist/modules/attachments/di.js.map +2 -2
  30. package/dist/modules/attachments/fields/attachment.js +186 -26
  31. package/dist/modules/attachments/fields/attachment.js.map +2 -2
  32. package/dist/modules/attachments/lib/scoped-upload-service.js +202 -0
  33. package/dist/modules/attachments/lib/scoped-upload-service.js.map +7 -0
  34. package/dist/modules/attachments/lib/target-access-service.js +39 -0
  35. package/dist/modules/attachments/lib/target-access-service.js.map +7 -0
  36. package/dist/modules/auth/api/logout.js +37 -10
  37. package/dist/modules/auth/api/logout.js.map +2 -2
  38. package/dist/modules/auth/api/reset/confirm.js +7 -0
  39. package/dist/modules/auth/api/reset/confirm.js.map +2 -2
  40. package/dist/modules/auth/api/reset.js +7 -0
  41. package/dist/modules/auth/api/reset.js.map +2 -2
  42. package/dist/modules/auth/commands/users.js +13 -0
  43. package/dist/modules/auth/commands/users.js.map +2 -2
  44. package/dist/modules/auth/events.js +15 -0
  45. package/dist/modules/auth/events.js.map +2 -2
  46. package/dist/modules/customers/api/companies/route.js +1 -0
  47. package/dist/modules/customers/api/companies/route.js.map +2 -2
  48. package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js +2 -2
  49. package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js.map +2 -2
  50. package/dist/modules/customers/backend/customers/people-v2/[id]/page.js +2 -2
  51. package/dist/modules/customers/backend/customers/people-v2/[id]/page.js.map +2 -2
  52. package/dist/modules/customers/components/detail/assignableStaff.js +6 -69
  53. package/dist/modules/customers/components/detail/assignableStaff.js.map +2 -2
  54. package/dist/modules/customers/lib/assignableStaff.js +76 -0
  55. package/dist/modules/customers/lib/assignableStaff.js.map +7 -0
  56. package/dist/modules/data_sync/lib/batch-stream.js +56 -0
  57. package/dist/modules/data_sync/lib/batch-stream.js.map +7 -0
  58. package/dist/modules/data_sync/lib/sync-engine.js +165 -103
  59. package/dist/modules/data_sync/lib/sync-engine.js.map +2 -2
  60. package/dist/modules/inbox_ops/api/emails/[id]/reprocess/route.js +4 -0
  61. package/dist/modules/inbox_ops/api/emails/[id]/reprocess/route.js.map +2 -2
  62. package/dist/modules/inbox_ops/api/extract/route.js +4 -0
  63. package/dist/modules/inbox_ops/api/extract/route.js.map +2 -2
  64. package/dist/modules/inbox_ops/api/webhook/inbound.js +4 -0
  65. package/dist/modules/inbox_ops/api/webhook/inbound.js.map +2 -2
  66. package/dist/modules/sales/commands/documents.js +8 -8
  67. package/dist/modules/sales/commands/documents.js.map +2 -2
  68. package/dist/modules/warranty_claims/acl.js +75 -0
  69. package/dist/modules/warranty_claims/acl.js.map +7 -0
  70. package/dist/modules/warranty_claims/ai-agents.js +161 -0
  71. package/dist/modules/warranty_claims/ai-agents.js.map +7 -0
  72. package/dist/modules/warranty_claims/ai-tools.js +591 -0
  73. package/dist/modules/warranty_claims/ai-tools.js.map +7 -0
  74. package/dist/modules/warranty_claims/api/ai/assess/route.js +304 -0
  75. package/dist/modules/warranty_claims/api/ai/assess/route.js.map +7 -0
  76. package/dist/modules/warranty_claims/api/ai/draft-reply/route.js +127 -0
  77. package/dist/modules/warranty_claims/api/ai/draft-reply/route.js.map +7 -0
  78. package/dist/modules/warranty_claims/api/ai/suggest/route.js +177 -0
  79. package/dist/modules/warranty_claims/api/ai/suggest/route.js.map +7 -0
  80. package/dist/modules/warranty_claims/api/assign/route.js +124 -0
  81. package/dist/modules/warranty_claims/api/assign/route.js.map +7 -0
  82. package/dist/modules/warranty_claims/api/assignees/route.js +97 -0
  83. package/dist/modules/warranty_claims/api/assignees/route.js.map +7 -0
  84. package/dist/modules/warranty_claims/api/credit-memo/route.js +152 -0
  85. package/dist/modules/warranty_claims/api/credit-memo/route.js.map +7 -0
  86. package/dist/modules/warranty_claims/api/entitlement/route.js +145 -0
  87. package/dist/modules/warranty_claims/api/entitlement/route.js.map +7 -0
  88. package/dist/modules/warranty_claims/api/events/route.js +199 -0
  89. package/dist/modules/warranty_claims/api/events/route.js.map +7 -0
  90. package/dist/modules/warranty_claims/api/external/claims/route.js +446 -0
  91. package/dist/modules/warranty_claims/api/external/claims/route.js.map +7 -0
  92. package/dist/modules/warranty_claims/api/lines/route.js +258 -0
  93. package/dist/modules/warranty_claims/api/lines/route.js.map +7 -0
  94. package/dist/modules/warranty_claims/api/openapi.js +27 -0
  95. package/dist/modules/warranty_claims/api/openapi.js.map +7 -0
  96. package/dist/modules/warranty_claims/api/portal/attachments/route.js +490 -0
  97. package/dist/modules/warranty_claims/api/portal/attachments/route.js.map +7 -0
  98. package/dist/modules/warranty_claims/api/portal/claims/[id]/route.js +178 -0
  99. package/dist/modules/warranty_claims/api/portal/claims/[id]/route.js.map +7 -0
  100. package/dist/modules/warranty_claims/api/portal/claims/[id]/shared.js +101 -0
  101. package/dist/modules/warranty_claims/api/portal/claims/[id]/shared.js.map +7 -0
  102. package/dist/modules/warranty_claims/api/portal/claims/[id]/submit/route.js +78 -0
  103. package/dist/modules/warranty_claims/api/portal/claims/[id]/submit/route.js.map +7 -0
  104. package/dist/modules/warranty_claims/api/portal/claims/[id]/withdraw/route.js +81 -0
  105. package/dist/modules/warranty_claims/api/portal/claims/[id]/withdraw/route.js.map +7 -0
  106. package/dist/modules/warranty_claims/api/portal/claims/route.js +462 -0
  107. package/dist/modules/warranty_claims/api/portal/claims/route.js.map +7 -0
  108. package/dist/modules/warranty_claims/api/portal/events/route.js +228 -0
  109. package/dist/modules/warranty_claims/api/portal/events/route.js.map +7 -0
  110. package/dist/modules/warranty_claims/api/portal/options/route.js +91 -0
  111. package/dist/modules/warranty_claims/api/portal/options/route.js.map +7 -0
  112. package/dist/modules/warranty_claims/api/portal/orders/lines/route.js +172 -0
  113. package/dist/modules/warranty_claims/api/portal/orders/lines/route.js.map +7 -0
  114. package/dist/modules/warranty_claims/api/portal/orders/route.js +151 -0
  115. package/dist/modules/warranty_claims/api/portal/orders/route.js.map +7 -0
  116. package/dist/modules/warranty_claims/api/portal/troubleshooting/route.js +121 -0
  117. package/dist/modules/warranty_claims/api/portal/troubleshooting/route.js.map +7 -0
  118. package/dist/modules/warranty_claims/api/receiving/route.js +176 -0
  119. package/dist/modules/warranty_claims/api/receiving/route.js.map +7 -0
  120. package/dist/modules/warranty_claims/api/registrations/route.js +382 -0
  121. package/dist/modules/warranty_claims/api/registrations/route.js.map +7 -0
  122. package/dist/modules/warranty_claims/api/replacement-order/route.js +150 -0
  123. package/dist/modules/warranty_claims/api/replacement-order/route.js.map +7 -0
  124. package/dist/modules/warranty_claims/api/return-label/route.js +235 -0
  125. package/dist/modules/warranty_claims/api/return-label/route.js.map +7 -0
  126. package/dist/modules/warranty_claims/api/risk/route.js +101 -0
  127. package/dist/modules/warranty_claims/api/risk/route.js.map +7 -0
  128. package/dist/modules/warranty_claims/api/route.js +482 -0
  129. package/dist/modules/warranty_claims/api/route.js.map +7 -0
  130. package/dist/modules/warranty_claims/api/sales-return/route.js +151 -0
  131. package/dist/modules/warranty_claims/api/sales-return/route.js.map +7 -0
  132. package/dist/modules/warranty_claims/api/settings-general/route.js +195 -0
  133. package/dist/modules/warranty_claims/api/settings-general/route.js.map +7 -0
  134. package/dist/modules/warranty_claims/api/stats/route.js +199 -0
  135. package/dist/modules/warranty_claims/api/stats/route.js.map +7 -0
  136. package/dist/modules/warranty_claims/api/submit/route.js +120 -0
  137. package/dist/modules/warranty_claims/api/submit/route.js.map +7 -0
  138. package/dist/modules/warranty_claims/api/transition/route.js +126 -0
  139. package/dist/modules/warranty_claims/api/transition/route.js.map +7 -0
  140. package/dist/modules/warranty_claims/api/troubleshooting-guides/route.js +261 -0
  141. package/dist/modules/warranty_claims/api/troubleshooting-guides/route.js.map +7 -0
  142. package/dist/modules/warranty_claims/api/vendor-policies/route.js +311 -0
  143. package/dist/modules/warranty_claims/api/vendor-policies/route.js.map +7 -0
  144. package/dist/modules/warranty_claims/api/vendor-recovery/route.js +130 -0
  145. package/dist/modules/warranty_claims/api/vendor-recovery/route.js.map +7 -0
  146. package/dist/modules/warranty_claims/api/vendor-recovery-suggestions/route.js +153 -0
  147. package/dist/modules/warranty_claims/api/vendor-recovery-suggestions/route.js.map +7 -0
  148. package/dist/modules/warranty_claims/backend/components/AiAssessButtons.js +268 -0
  149. package/dist/modules/warranty_claims/backend/components/AiAssessButtons.js.map +7 -0
  150. package/dist/modules/warranty_claims/backend/components/ClaimStageProgress.js +54 -0
  151. package/dist/modules/warranty_claims/backend/components/ClaimStageProgress.js.map +7 -0
  152. package/dist/modules/warranty_claims/backend/components/ClaimStatusBadge.js +87 -0
  153. package/dist/modules/warranty_claims/backend/components/ClaimStatusBadge.js.map +7 -0
  154. package/dist/modules/warranty_claims/backend/components/ClaimsKpiStrip.js +120 -0
  155. package/dist/modules/warranty_claims/backend/components/ClaimsKpiStrip.js.map +7 -0
  156. package/dist/modules/warranty_claims/backend/components/EntitlementLookupBadge.js +135 -0
  157. package/dist/modules/warranty_claims/backend/components/EntitlementLookupBadge.js.map +7 -0
  158. package/dist/modules/warranty_claims/backend/components/ReceivingPanel.js +218 -0
  159. package/dist/modules/warranty_claims/backend/components/ReceivingPanel.js.map +7 -0
  160. package/dist/modules/warranty_claims/backend/components/ReturnLabelPanel.js +231 -0
  161. package/dist/modules/warranty_claims/backend/components/ReturnLabelPanel.js.map +7 -0
  162. package/dist/modules/warranty_claims/backend/components/TroubleshootingWalker.js +82 -0
  163. package/dist/modules/warranty_claims/backend/components/TroubleshootingWalker.js.map +7 -0
  164. package/dist/modules/warranty_claims/backend/components/VendorRecoverySuggestionsPanel.js +136 -0
  165. package/dist/modules/warranty_claims/backend/components/VendorRecoverySuggestionsPanel.js.map +7 -0
  166. package/dist/modules/warranty_claims/backend/components/WarrantyWorkspace.js +41 -0
  167. package/dist/modules/warranty_claims/backend/components/WarrantyWorkspace.js.map +7 -0
  168. package/dist/modules/warranty_claims/backend/components/claimReasonOptions.js +45 -0
  169. package/dist/modules/warranty_claims/backend/components/claimReasonOptions.js.map +7 -0
  170. package/dist/modules/warranty_claims/backend/components/claimSla.js +92 -0
  171. package/dist/modules/warranty_claims/backend/components/claimSla.js.map +7 -0
  172. package/dist/modules/warranty_claims/backend/components/orderLookup.js +81 -0
  173. package/dist/modules/warranty_claims/backend/components/orderLookup.js.map +7 -0
  174. package/dist/modules/warranty_claims/backend/components/productLookup.js +317 -0
  175. package/dist/modules/warranty_claims/backend/components/productLookup.js.map +7 -0
  176. package/dist/modules/warranty_claims/backend/components/useUserDisplayNames.js +61 -0
  177. package/dist/modules/warranty_claims/backend/components/useUserDisplayNames.js.map +7 -0
  178. package/dist/modules/warranty_claims/backend/page.js +1088 -0
  179. package/dist/modules/warranty_claims/backend/page.js.map +7 -0
  180. package/dist/modules/warranty_claims/backend/page.meta.js +16 -0
  181. package/dist/modules/warranty_claims/backend/page.meta.js.map +7 -0
  182. package/dist/modules/warranty_claims/backend/warranty_claims/[id]/edit/page.js +427 -0
  183. package/dist/modules/warranty_claims/backend/warranty_claims/[id]/edit/page.js.map +7 -0
  184. package/dist/modules/warranty_claims/backend/warranty_claims/[id]/edit/page.meta.js +16 -0
  185. package/dist/modules/warranty_claims/backend/warranty_claims/[id]/edit/page.meta.js.map +7 -0
  186. package/dist/modules/warranty_claims/backend/warranty_claims/[id]/page.js +2101 -0
  187. package/dist/modules/warranty_claims/backend/warranty_claims/[id]/page.js.map +7 -0
  188. package/dist/modules/warranty_claims/backend/warranty_claims/[id]/page.meta.js +16 -0
  189. package/dist/modules/warranty_claims/backend/warranty_claims/[id]/page.meta.js.map +7 -0
  190. package/dist/modules/warranty_claims/backend/warranty_claims/create/page.js +1249 -0
  191. package/dist/modules/warranty_claims/backend/warranty_claims/create/page.js.map +7 -0
  192. package/dist/modules/warranty_claims/backend/warranty_claims/create/page.meta.js +16 -0
  193. package/dist/modules/warranty_claims/backend/warranty_claims/create/page.meta.js.map +7 -0
  194. package/dist/modules/warranty_claims/backend/warranty_claims/registrations/[id]/edit/page.js +110 -0
  195. package/dist/modules/warranty_claims/backend/warranty_claims/registrations/[id]/edit/page.js.map +7 -0
  196. package/dist/modules/warranty_claims/backend/warranty_claims/registrations/[id]/edit/page.meta.js +17 -0
  197. package/dist/modules/warranty_claims/backend/warranty_claims/registrations/[id]/edit/page.meta.js.map +7 -0
  198. package/dist/modules/warranty_claims/backend/warranty_claims/registrations/create/page.js +49 -0
  199. package/dist/modules/warranty_claims/backend/warranty_claims/registrations/create/page.js.map +7 -0
  200. package/dist/modules/warranty_claims/backend/warranty_claims/registrations/create/page.meta.js +17 -0
  201. package/dist/modules/warranty_claims/backend/warranty_claims/registrations/create/page.meta.js.map +7 -0
  202. package/dist/modules/warranty_claims/backend/warranty_claims/registrations/page.js +485 -0
  203. package/dist/modules/warranty_claims/backend/warranty_claims/registrations/page.js.map +7 -0
  204. package/dist/modules/warranty_claims/backend/warranty_claims/registrations/page.meta.js +19 -0
  205. package/dist/modules/warranty_claims/backend/warranty_claims/registrations/page.meta.js.map +7 -0
  206. package/dist/modules/warranty_claims/backend/warranty_claims/registrations/registrationForm.js +253 -0
  207. package/dist/modules/warranty_claims/backend/warranty_claims/registrations/registrationForm.js.map +7 -0
  208. package/dist/modules/warranty_claims/backend/warranty_claims/settings/page.js +1565 -0
  209. package/dist/modules/warranty_claims/backend/warranty_claims/settings/page.js.map +7 -0
  210. package/dist/modules/warranty_claims/backend/warranty_claims/settings/page.meta.js +19 -0
  211. package/dist/modules/warranty_claims/backend/warranty_claims/settings/page.meta.js.map +7 -0
  212. package/dist/modules/warranty_claims/backend/warranty_claims/troubleshooting-guides/TroubleshootingTreeBuilder.js +472 -0
  213. package/dist/modules/warranty_claims/backend/warranty_claims/troubleshooting-guides/TroubleshootingTreeBuilder.js.map +7 -0
  214. package/dist/modules/warranty_claims/backend/warranty_claims/troubleshooting-guides/[id]/edit/page.js +115 -0
  215. package/dist/modules/warranty_claims/backend/warranty_claims/troubleshooting-guides/[id]/edit/page.js.map +7 -0
  216. package/dist/modules/warranty_claims/backend/warranty_claims/troubleshooting-guides/[id]/edit/page.meta.js +19 -0
  217. package/dist/modules/warranty_claims/backend/warranty_claims/troubleshooting-guides/[id]/edit/page.meta.js.map +7 -0
  218. package/dist/modules/warranty_claims/backend/warranty_claims/troubleshooting-guides/create/page.js +54 -0
  219. package/dist/modules/warranty_claims/backend/warranty_claims/troubleshooting-guides/create/page.js.map +7 -0
  220. package/dist/modules/warranty_claims/backend/warranty_claims/troubleshooting-guides/create/page.meta.js +19 -0
  221. package/dist/modules/warranty_claims/backend/warranty_claims/troubleshooting-guides/create/page.meta.js.map +7 -0
  222. package/dist/modules/warranty_claims/backend/warranty_claims/troubleshooting-guides/page.js +414 -0
  223. package/dist/modules/warranty_claims/backend/warranty_claims/troubleshooting-guides/page.js.map +7 -0
  224. package/dist/modules/warranty_claims/backend/warranty_claims/troubleshooting-guides/page.meta.js +21 -0
  225. package/dist/modules/warranty_claims/backend/warranty_claims/troubleshooting-guides/page.meta.js.map +7 -0
  226. package/dist/modules/warranty_claims/backend/warranty_claims/troubleshooting-guides/troubleshootingGuideForm.js +171 -0
  227. package/dist/modules/warranty_claims/backend/warranty_claims/troubleshooting-guides/troubleshootingGuideForm.js.map +7 -0
  228. package/dist/modules/warranty_claims/backend/warranty_claims/vendor-policies/[id]/edit/page.js +111 -0
  229. package/dist/modules/warranty_claims/backend/warranty_claims/vendor-policies/[id]/edit/page.js.map +7 -0
  230. package/dist/modules/warranty_claims/backend/warranty_claims/vendor-policies/[id]/edit/page.meta.js +19 -0
  231. package/dist/modules/warranty_claims/backend/warranty_claims/vendor-policies/[id]/edit/page.meta.js.map +7 -0
  232. package/dist/modules/warranty_claims/backend/warranty_claims/vendor-policies/create/page.js +48 -0
  233. package/dist/modules/warranty_claims/backend/warranty_claims/vendor-policies/create/page.js.map +7 -0
  234. package/dist/modules/warranty_claims/backend/warranty_claims/vendor-policies/create/page.meta.js +19 -0
  235. package/dist/modules/warranty_claims/backend/warranty_claims/vendor-policies/create/page.meta.js.map +7 -0
  236. package/dist/modules/warranty_claims/backend/warranty_claims/vendor-policies/page.js +429 -0
  237. package/dist/modules/warranty_claims/backend/warranty_claims/vendor-policies/page.js.map +7 -0
  238. package/dist/modules/warranty_claims/backend/warranty_claims/vendor-policies/page.meta.js +21 -0
  239. package/dist/modules/warranty_claims/backend/warranty_claims/vendor-policies/page.meta.js.map +7 -0
  240. package/dist/modules/warranty_claims/backend/warranty_claims/vendor-policies/vendorPolicyForm.js +182 -0
  241. package/dist/modules/warranty_claims/backend/warranty_claims/vendor-policies/vendorPolicyForm.js.map +7 -0
  242. package/dist/modules/warranty_claims/ce.js +48 -0
  243. package/dist/modules/warranty_claims/ce.js.map +7 -0
  244. package/dist/modules/warranty_claims/commands/claim-lines.js +911 -0
  245. package/dist/modules/warranty_claims/commands/claim-lines.js.map +7 -0
  246. package/dist/modules/warranty_claims/commands/claims.js +2577 -0
  247. package/dist/modules/warranty_claims/commands/claims.js.map +7 -0
  248. package/dist/modules/warranty_claims/commands/index.js +4 -0
  249. package/dist/modules/warranty_claims/commands/index.js.map +7 -0
  250. package/dist/modules/warranty_claims/commands/settings.js +169 -0
  251. package/dist/modules/warranty_claims/commands/settings.js.map +7 -0
  252. package/dist/modules/warranty_claims/commands/shared.js +162 -0
  253. package/dist/modules/warranty_claims/commands/shared.js.map +7 -0
  254. package/dist/modules/warranty_claims/data/constants.js +35 -0
  255. package/dist/modules/warranty_claims/data/constants.js.map +7 -0
  256. package/dist/modules/warranty_claims/data/enrichers.js +97 -0
  257. package/dist/modules/warranty_claims/data/enrichers.js.map +7 -0
  258. package/dist/modules/warranty_claims/data/entities.js +731 -0
  259. package/dist/modules/warranty_claims/data/entities.js.map +7 -0
  260. package/dist/modules/warranty_claims/data/validators.js +488 -0
  261. package/dist/modules/warranty_claims/data/validators.js.map +7 -0
  262. package/dist/modules/warranty_claims/di.js +29 -0
  263. package/dist/modules/warranty_claims/di.js.map +7 -0
  264. package/dist/modules/warranty_claims/encryption.js +28 -0
  265. package/dist/modules/warranty_claims/encryption.js.map +7 -0
  266. package/dist/modules/warranty_claims/events.js +81 -0
  267. package/dist/modules/warranty_claims/events.js.map +7 -0
  268. package/dist/modules/warranty_claims/extension-points.js +19 -0
  269. package/dist/modules/warranty_claims/extension-points.js.map +7 -0
  270. package/dist/modules/warranty_claims/frontend/[orgSlug]/portal/claims/[id]/page.js +825 -0
  271. package/dist/modules/warranty_claims/frontend/[orgSlug]/portal/claims/[id]/page.js.map +7 -0
  272. package/dist/modules/warranty_claims/frontend/[orgSlug]/portal/claims/[id]/page.meta.js +11 -0
  273. package/dist/modules/warranty_claims/frontend/[orgSlug]/portal/claims/[id]/page.meta.js.map +7 -0
  274. package/dist/modules/warranty_claims/frontend/[orgSlug]/portal/claims/new/page.js +1139 -0
  275. package/dist/modules/warranty_claims/frontend/[orgSlug]/portal/claims/new/page.js.map +7 -0
  276. package/dist/modules/warranty_claims/frontend/[orgSlug]/portal/claims/new/page.meta.js +11 -0
  277. package/dist/modules/warranty_claims/frontend/[orgSlug]/portal/claims/new/page.meta.js.map +7 -0
  278. package/dist/modules/warranty_claims/frontend/[orgSlug]/portal/claims/page.js +253 -0
  279. package/dist/modules/warranty_claims/frontend/[orgSlug]/portal/claims/page.js.map +7 -0
  280. package/dist/modules/warranty_claims/frontend/[orgSlug]/portal/claims/page.meta.js +18 -0
  281. package/dist/modules/warranty_claims/frontend/[orgSlug]/portal/claims/page.meta.js.map +7 -0
  282. package/dist/modules/warranty_claims/index.js +15 -0
  283. package/dist/modules/warranty_claims/index.js.map +7 -0
  284. package/dist/modules/warranty_claims/lib/aiAssist.js +496 -0
  285. package/dist/modules/warranty_claims/lib/aiAssist.js.map +7 -0
  286. package/dist/modules/warranty_claims/lib/apiIdsFilter.js +11 -0
  287. package/dist/modules/warranty_claims/lib/apiIdsFilter.js.map +7 -0
  288. package/dist/modules/warranty_claims/lib/assigneeNames.js +153 -0
  289. package/dist/modules/warranty_claims/lib/assigneeNames.js.map +7 -0
  290. package/dist/modules/warranty_claims/lib/attachmentVisibility.js +9 -0
  291. package/dist/modules/warranty_claims/lib/attachmentVisibility.js.map +7 -0
  292. package/dist/modules/warranty_claims/lib/bulkFeedback.js +8 -0
  293. package/dist/modules/warranty_claims/lib/bulkFeedback.js.map +7 -0
  294. package/dist/modules/warranty_claims/lib/businessHours.js +250 -0
  295. package/dist/modules/warranty_claims/lib/businessHours.js.map +7 -0
  296. package/dist/modules/warranty_claims/lib/businessHoursForm.js +146 -0
  297. package/dist/modules/warranty_claims/lib/businessHoursForm.js.map +7 -0
  298. package/dist/modules/warranty_claims/lib/claimTypeConfig.js +39 -0
  299. package/dist/modules/warranty_claims/lib/claimTypeConfig.js.map +7 -0
  300. package/dist/modules/warranty_claims/lib/decryptionSafety.js +17 -0
  301. package/dist/modules/warranty_claims/lib/decryptionSafety.js.map +7 -0
  302. package/dist/modules/warranty_claims/lib/deskFilters.js +88 -0
  303. package/dist/modules/warranty_claims/lib/deskFilters.js.map +7 -0
  304. package/dist/modules/warranty_claims/lib/dictionaries.js +157 -0
  305. package/dist/modules/warranty_claims/lib/dictionaries.js.map +7 -0
  306. package/dist/modules/warranty_claims/lib/dictionaryLabels.js +12 -0
  307. package/dist/modules/warranty_claims/lib/dictionaryLabels.js.map +7 -0
  308. package/dist/modules/warranty_claims/lib/emailIntake.js +87 -0
  309. package/dist/modules/warranty_claims/lib/emailIntake.js.map +7 -0
  310. package/dist/modules/warranty_claims/lib/escalation.js +58 -0
  311. package/dist/modules/warranty_claims/lib/escalation.js.map +7 -0
  312. package/dist/modules/warranty_claims/lib/externalIntake.js +233 -0
  313. package/dist/modules/warranty_claims/lib/externalIntake.js.map +7 -0
  314. package/dist/modules/warranty_claims/lib/grading.js +21 -0
  315. package/dist/modules/warranty_claims/lib/grading.js.map +7 -0
  316. package/dist/modules/warranty_claims/lib/listSegments.js +18 -0
  317. package/dist/modules/warranty_claims/lib/listSegments.js.map +7 -0
  318. package/dist/modules/warranty_claims/lib/orderOwnership.js +25 -0
  319. package/dist/modules/warranty_claims/lib/orderOwnership.js.map +7 -0
  320. package/dist/modules/warranty_claims/lib/portalAttachmentUpload.js +11 -0
  321. package/dist/modules/warranty_claims/lib/portalAttachmentUpload.js.map +7 -0
  322. package/dist/modules/warranty_claims/lib/portalAttachmentValidation.js +108 -0
  323. package/dist/modules/warranty_claims/lib/portalAttachmentValidation.js.map +7 -0
  324. package/dist/modules/warranty_claims/lib/portalClaimAccess.js +26 -0
  325. package/dist/modules/warranty_claims/lib/portalClaimAccess.js.map +7 -0
  326. package/dist/modules/warranty_claims/lib/quantity.js +19 -0
  327. package/dist/modules/warranty_claims/lib/quantity.js.map +7 -0
  328. package/dist/modules/warranty_claims/lib/risk.js +204 -0
  329. package/dist/modules/warranty_claims/lib/risk.js.map +7 -0
  330. package/dist/modules/warranty_claims/lib/settings.js +55 -0
  331. package/dist/modules/warranty_claims/lib/settings.js.map +7 -0
  332. package/dist/modules/warranty_claims/lib/stateMachine.js +88 -0
  333. package/dist/modules/warranty_claims/lib/stateMachine.js.map +7 -0
  334. package/dist/modules/warranty_claims/lib/timelineFilters.js +16 -0
  335. package/dist/modules/warranty_claims/lib/timelineFilters.js.map +7 -0
  336. package/dist/modules/warranty_claims/lib/triage.js +241 -0
  337. package/dist/modules/warranty_claims/lib/triage.js.map +7 -0
  338. package/dist/modules/warranty_claims/lib/troubleshooting.js +111 -0
  339. package/dist/modules/warranty_claims/lib/troubleshooting.js.map +7 -0
  340. package/dist/modules/warranty_claims/lib/tryResolve.js +11 -0
  341. package/dist/modules/warranty_claims/lib/tryResolve.js.map +7 -0
  342. package/dist/modules/warranty_claims/lib/vendorPolicyRecovery.js +101 -0
  343. package/dist/modules/warranty_claims/lib/vendorPolicyRecovery.js.map +7 -0
  344. package/dist/modules/warranty_claims/lib/warrantyPreview.js +18 -0
  345. package/dist/modules/warranty_claims/lib/warrantyPreview.js.map +7 -0
  346. package/dist/modules/warranty_claims/migrations/Migration20260703191537_warranty_claims.js +32 -0
  347. package/dist/modules/warranty_claims/migrations/Migration20260703191537_warranty_claims.js.map +7 -0
  348. package/dist/modules/warranty_claims/migrations/Migration20260704115605_warranty_claims.js +16 -0
  349. package/dist/modules/warranty_claims/migrations/Migration20260704115605_warranty_claims.js.map +7 -0
  350. package/dist/modules/warranty_claims/migrations/Migration20260704230000_warranty_claims.js +17 -0
  351. package/dist/modules/warranty_claims/migrations/Migration20260704230000_warranty_claims.js.map +7 -0
  352. package/dist/modules/warranty_claims/migrations/Migration20260705121601_warranty_claims.js +29 -0
  353. package/dist/modules/warranty_claims/migrations/Migration20260705121601_warranty_claims.js.map +7 -0
  354. package/dist/modules/warranty_claims/migrations/Migration20260709120000_warranty_claims.js +25 -0
  355. package/dist/modules/warranty_claims/migrations/Migration20260709120000_warranty_claims.js.map +7 -0
  356. package/dist/modules/warranty_claims/migrations/Migration20260710023607_warranty_claims.js +13 -0
  357. package/dist/modules/warranty_claims/migrations/Migration20260710023607_warranty_claims.js.map +7 -0
  358. package/dist/modules/warranty_claims/migrations/Migration20260716203936_warranty_claims.js +15 -0
  359. package/dist/modules/warranty_claims/migrations/Migration20260716203936_warranty_claims.js.map +7 -0
  360. package/dist/modules/warranty_claims/migrations/Migration20260717150000_warranty_claims.js +13 -0
  361. package/dist/modules/warranty_claims/migrations/Migration20260717150000_warranty_claims.js.map +7 -0
  362. package/dist/modules/warranty_claims/migrations/Migration20260719120000_warranty_claims.js +13 -0
  363. package/dist/modules/warranty_claims/migrations/Migration20260719120000_warranty_claims.js.map +7 -0
  364. package/dist/modules/warranty_claims/migrations/Migration20260813103053_warranty_claims.js +12 -0
  365. package/dist/modules/warranty_claims/migrations/Migration20260813103053_warranty_claims.js.map +7 -0
  366. package/dist/modules/warranty_claims/notifications.client.js +67 -0
  367. package/dist/modules/warranty_claims/notifications.client.js.map +7 -0
  368. package/dist/modules/warranty_claims/notifications.js +103 -0
  369. package/dist/modules/warranty_claims/notifications.js.map +7 -0
  370. package/dist/modules/warranty_claims/search.js +174 -0
  371. package/dist/modules/warranty_claims/search.js.map +7 -0
  372. package/dist/modules/warranty_claims/services/adjudicationEvaluator.js +202 -0
  373. package/dist/modules/warranty_claims/services/adjudicationEvaluator.js.map +7 -0
  374. package/dist/modules/warranty_claims/services/claimNumberGenerator.js +58 -0
  375. package/dist/modules/warranty_claims/services/claimNumberGenerator.js.map +7 -0
  376. package/dist/modules/warranty_claims/services/entitlementResolver.js +69 -0
  377. package/dist/modules/warranty_claims/services/entitlementResolver.js.map +7 -0
  378. package/dist/modules/warranty_claims/services/returnLabelProvider.js +14 -0
  379. package/dist/modules/warranty_claims/services/returnLabelProvider.js.map +7 -0
  380. package/dist/modules/warranty_claims/setup.js +121 -0
  381. package/dist/modules/warranty_claims/setup.js.map +7 -0
  382. package/dist/modules/warranty_claims/subscribers/auto-vendor-recovery.js +119 -0
  383. package/dist/modules/warranty_claims/subscribers/auto-vendor-recovery.js.map +7 -0
  384. package/dist/modules/warranty_claims/subscribers/claim-assigned-notification.js +47 -0
  385. package/dist/modules/warranty_claims/subscribers/claim-assigned-notification.js.map +7 -0
  386. package/dist/modules/warranty_claims/subscribers/claim-customer-reply-notification.js +60 -0
  387. package/dist/modules/warranty_claims/subscribers/claim-customer-reply-notification.js.map +7 -0
  388. package/dist/modules/warranty_claims/subscribers/claim-status-notification.js +83 -0
  389. package/dist/modules/warranty_claims/subscribers/claim-status-notification.js.map +7 -0
  390. package/dist/modules/warranty_claims/subscribers/claim-submitted-notification.js +46 -0
  391. package/dist/modules/warranty_claims/subscribers/claim-submitted-notification.js.map +7 -0
  392. package/dist/modules/warranty_claims/subscribers/email-to-claim.js +135 -0
  393. package/dist/modules/warranty_claims/subscribers/email-to-claim.js.map +7 -0
  394. package/dist/modules/warranty_claims/subscribers/return-shipment-tracking.js +164 -0
  395. package/dist/modules/warranty_claims/subscribers/return-shipment-tracking.js.map +7 -0
  396. package/dist/modules/warranty_claims/subscribers/vendor-recovery-reconciliation-undo.js +24 -0
  397. package/dist/modules/warranty_claims/subscribers/vendor-recovery-reconciliation-undo.js.map +7 -0
  398. package/dist/modules/warranty_claims/subscribers/vendor-recovery-reconciliation.js +26 -0
  399. package/dist/modules/warranty_claims/subscribers/vendor-recovery-reconciliation.js.map +7 -0
  400. package/dist/modules/warranty_claims/widgets/dashboard/claims-queue/config.js +15 -0
  401. package/dist/modules/warranty_claims/widgets/dashboard/claims-queue/config.js.map +7 -0
  402. package/dist/modules/warranty_claims/widgets/dashboard/claims-queue/widget.client.js +197 -0
  403. package/dist/modules/warranty_claims/widgets/dashboard/claims-queue/widget.client.js.map +7 -0
  404. package/dist/modules/warranty_claims/widgets/dashboard/claims-queue/widget.js +28 -0
  405. package/dist/modules/warranty_claims/widgets/dashboard/claims-queue/widget.js.map +7 -0
  406. package/dist/modules/warranty_claims/widgets/injection/claims-ai-trigger/widget.client.js +418 -0
  407. package/dist/modules/warranty_claims/widgets/injection/claims-ai-trigger/widget.client.js.map +7 -0
  408. package/dist/modules/warranty_claims/widgets/injection/claims-ai-trigger/widget.js +18 -0
  409. package/dist/modules/warranty_claims/widgets/injection/claims-ai-trigger/widget.js.map +7 -0
  410. package/dist/modules/warranty_claims/widgets/injection/customer-claims-tab/widget.js +17 -0
  411. package/dist/modules/warranty_claims/widgets/injection/customer-claims-tab/widget.js.map +7 -0
  412. package/dist/modules/warranty_claims/widgets/injection/customer-claims-tab.js +146 -0
  413. package/dist/modules/warranty_claims/widgets/injection/customer-claims-tab.js.map +7 -0
  414. package/dist/modules/warranty_claims/widgets/injection/order-claims-tab/widget.js +17 -0
  415. package/dist/modules/warranty_claims/widgets/injection/order-claims-tab/widget.js.map +7 -0
  416. package/dist/modules/warranty_claims/widgets/injection/order-claims-tab.js +147 -0
  417. package/dist/modules/warranty_claims/widgets/injection/order-claims-tab.js.map +7 -0
  418. package/dist/modules/warranty_claims/widgets/injection-table.js +38 -0
  419. package/dist/modules/warranty_claims/widgets/injection-table.js.map +7 -0
  420. package/dist/modules/warranty_claims/widgets/notifications/WarrantyClaimNotificationRenderer.js +223 -0
  421. package/dist/modules/warranty_claims/widgets/notifications/WarrantyClaimNotificationRenderer.js.map +7 -0
  422. package/dist/modules/warranty_claims/widgets/notifications/index.js +5 -0
  423. package/dist/modules/warranty_claims/widgets/notifications/index.js.map +7 -0
  424. package/dist/modules/warranty_claims/workers/sla-escalation-sweep.js +334 -0
  425. package/dist/modules/warranty_claims/workers/sla-escalation-sweep.js.map +7 -0
  426. package/generated/entities/warranty_claim/index.ts +51 -0
  427. package/generated/entities/warranty_claim_event/index.ts +11 -0
  428. package/generated/entities/warranty_claim_line/index.ts +37 -0
  429. package/generated/entities/warranty_claim_registration/index.ts +20 -0
  430. package/generated/entities/warranty_claim_sequence/index.ts +7 -0
  431. package/generated/entities/warranty_claim_settings/index.ts +20 -0
  432. package/generated/entities/warranty_claim_sla_signal/index.ts +11 -0
  433. package/generated/entities/warranty_troubleshooting_guide/index.ts +11 -0
  434. package/generated/entities/warranty_vendor_policy/index.ts +14 -0
  435. package/generated/entities.ids.generated.ts +12 -0
  436. package/generated/entity-fields-registry.ts +200 -0
  437. package/package.json +7 -7
  438. package/src/helpers/integration/api.ts +10 -3
  439. package/src/helpers/integration/salesFixtures.ts +17 -7
  440. package/src/modules/attachments/di.ts +30 -1
  441. package/src/modules/attachments/fields/attachment.tsx +215 -23
  442. package/src/modules/attachments/i18n/de.json +5 -0
  443. package/src/modules/attachments/i18n/en.json +5 -0
  444. package/src/modules/attachments/i18n/es.json +5 -0
  445. package/src/modules/attachments/i18n/ko.json +5 -0
  446. package/src/modules/attachments/i18n/pl.json +5 -0
  447. package/src/modules/attachments/lib/scoped-upload-service.ts +257 -0
  448. package/src/modules/attachments/lib/target-access-service.ts +57 -0
  449. package/src/modules/auth/api/logout.ts +46 -10
  450. package/src/modules/auth/api/reset/confirm.ts +7 -0
  451. package/src/modules/auth/api/reset.ts +7 -0
  452. package/src/modules/auth/commands/users.ts +22 -0
  453. package/src/modules/auth/events.ts +15 -0
  454. package/src/modules/customers/api/companies/route.ts +1 -0
  455. package/src/modules/customers/backend/customers/companies-v2/[id]/page.tsx +4 -2
  456. package/src/modules/customers/backend/customers/people-v2/[id]/page.tsx +4 -2
  457. package/src/modules/customers/components/detail/assignableStaff.ts +14 -177
  458. package/src/modules/customers/lib/assignableStaff.ts +177 -0
  459. package/src/modules/data_sync/AGENTS.md +3 -0
  460. package/src/modules/data_sync/lib/batch-stream.ts +117 -0
  461. package/src/modules/data_sync/lib/sync-engine.ts +190 -115
  462. package/src/modules/inbox_ops/api/emails/[id]/reprocess/route.ts +4 -0
  463. package/src/modules/inbox_ops/api/extract/route.ts +4 -0
  464. package/src/modules/inbox_ops/api/webhook/inbound.ts +4 -0
  465. package/src/modules/sales/commands/documents.ts +16 -8
  466. package/src/modules/warranty_claims/AGENTS.md +49 -0
  467. package/src/modules/warranty_claims/acl.ts +71 -0
  468. package/src/modules/warranty_claims/ai-agents.ts +189 -0
  469. package/src/modules/warranty_claims/ai-tools.ts +711 -0
  470. package/src/modules/warranty_claims/api/ai/assess/route.ts +371 -0
  471. package/src/modules/warranty_claims/api/ai/draft-reply/route.ts +149 -0
  472. package/src/modules/warranty_claims/api/ai/suggest/route.ts +190 -0
  473. package/src/modules/warranty_claims/api/assign/route.ts +142 -0
  474. package/src/modules/warranty_claims/api/assignees/route.ts +102 -0
  475. package/src/modules/warranty_claims/api/credit-memo/route.ts +191 -0
  476. package/src/modules/warranty_claims/api/entitlement/route.ts +224 -0
  477. package/src/modules/warranty_claims/api/events/route.ts +226 -0
  478. package/src/modules/warranty_claims/api/external/claims/route.ts +528 -0
  479. package/src/modules/warranty_claims/api/lines/route.ts +275 -0
  480. package/src/modules/warranty_claims/api/openapi.ts +28 -0
  481. package/src/modules/warranty_claims/api/portal/attachments/route.ts +547 -0
  482. package/src/modules/warranty_claims/api/portal/claims/[id]/route.ts +195 -0
  483. package/src/modules/warranty_claims/api/portal/claims/[id]/shared.ts +125 -0
  484. package/src/modules/warranty_claims/api/portal/claims/[id]/submit/route.ts +82 -0
  485. package/src/modules/warranty_claims/api/portal/claims/[id]/withdraw/route.ts +87 -0
  486. package/src/modules/warranty_claims/api/portal/claims/route.ts +579 -0
  487. package/src/modules/warranty_claims/api/portal/events/route.ts +260 -0
  488. package/src/modules/warranty_claims/api/portal/options/route.ts +113 -0
  489. package/src/modules/warranty_claims/api/portal/orders/lines/route.ts +258 -0
  490. package/src/modules/warranty_claims/api/portal/orders/route.ts +206 -0
  491. package/src/modules/warranty_claims/api/portal/troubleshooting/route.ts +151 -0
  492. package/src/modules/warranty_claims/api/receiving/route.ts +215 -0
  493. package/src/modules/warranty_claims/api/registrations/route.ts +417 -0
  494. package/src/modules/warranty_claims/api/replacement-order/route.ts +188 -0
  495. package/src/modules/warranty_claims/api/return-label/route.ts +291 -0
  496. package/src/modules/warranty_claims/api/risk/route.ts +112 -0
  497. package/src/modules/warranty_claims/api/route.ts +543 -0
  498. package/src/modules/warranty_claims/api/sales-return/route.ts +183 -0
  499. package/src/modules/warranty_claims/api/settings-general/route.ts +230 -0
  500. package/src/modules/warranty_claims/api/stats/route.ts +306 -0
  501. package/src/modules/warranty_claims/api/submit/route.ts +146 -0
  502. package/src/modules/warranty_claims/api/transition/route.ts +144 -0
  503. package/src/modules/warranty_claims/api/troubleshooting-guides/route.ts +295 -0
  504. package/src/modules/warranty_claims/api/vendor-policies/route.ts +351 -0
  505. package/src/modules/warranty_claims/api/vendor-recovery/route.ts +148 -0
  506. package/src/modules/warranty_claims/api/vendor-recovery-suggestions/route.ts +167 -0
  507. package/src/modules/warranty_claims/backend/components/AiAssessButtons.tsx +389 -0
  508. package/src/modules/warranty_claims/backend/components/ClaimStageProgress.tsx +65 -0
  509. package/src/modules/warranty_claims/backend/components/ClaimStatusBadge.tsx +137 -0
  510. package/src/modules/warranty_claims/backend/components/ClaimsKpiStrip.tsx +169 -0
  511. package/src/modules/warranty_claims/backend/components/EntitlementLookupBadge.tsx +203 -0
  512. package/src/modules/warranty_claims/backend/components/ReceivingPanel.tsx +300 -0
  513. package/src/modules/warranty_claims/backend/components/ReturnLabelPanel.tsx +292 -0
  514. package/src/modules/warranty_claims/backend/components/TroubleshootingWalker.tsx +125 -0
  515. package/src/modules/warranty_claims/backend/components/VendorRecoverySuggestionsPanel.tsx +217 -0
  516. package/src/modules/warranty_claims/backend/components/WarrantyWorkspace.tsx +66 -0
  517. package/src/modules/warranty_claims/backend/components/claimReasonOptions.ts +57 -0
  518. package/src/modules/warranty_claims/backend/components/claimSla.tsx +127 -0
  519. package/src/modules/warranty_claims/backend/components/orderLookup.ts +109 -0
  520. package/src/modules/warranty_claims/backend/components/productLookup.tsx +433 -0
  521. package/src/modules/warranty_claims/backend/components/useUserDisplayNames.ts +66 -0
  522. package/src/modules/warranty_claims/backend/page.meta.ts +12 -0
  523. package/src/modules/warranty_claims/backend/page.tsx +1302 -0
  524. package/src/modules/warranty_claims/backend/warranty_claims/[id]/edit/page.meta.ts +12 -0
  525. package/src/modules/warranty_claims/backend/warranty_claims/[id]/edit/page.tsx +531 -0
  526. package/src/modules/warranty_claims/backend/warranty_claims/[id]/page.meta.ts +12 -0
  527. package/src/modules/warranty_claims/backend/warranty_claims/[id]/page.tsx +2599 -0
  528. package/src/modules/warranty_claims/backend/warranty_claims/create/page.meta.ts +12 -0
  529. package/src/modules/warranty_claims/backend/warranty_claims/create/page.tsx +1533 -0
  530. package/src/modules/warranty_claims/backend/warranty_claims/registrations/[id]/edit/page.meta.ts +13 -0
  531. package/src/modules/warranty_claims/backend/warranty_claims/registrations/[id]/edit/page.tsx +136 -0
  532. package/src/modules/warranty_claims/backend/warranty_claims/registrations/create/page.meta.ts +13 -0
  533. package/src/modules/warranty_claims/backend/warranty_claims/registrations/create/page.tsx +52 -0
  534. package/src/modules/warranty_claims/backend/warranty_claims/registrations/page.meta.ts +15 -0
  535. package/src/modules/warranty_claims/backend/warranty_claims/registrations/page.tsx +555 -0
  536. package/src/modules/warranty_claims/backend/warranty_claims/registrations/registrationForm.tsx +295 -0
  537. package/src/modules/warranty_claims/backend/warranty_claims/settings/page.meta.ts +15 -0
  538. package/src/modules/warranty_claims/backend/warranty_claims/settings/page.tsx +1903 -0
  539. package/src/modules/warranty_claims/backend/warranty_claims/troubleshooting-guides/TroubleshootingTreeBuilder.tsx +550 -0
  540. package/src/modules/warranty_claims/backend/warranty_claims/troubleshooting-guides/[id]/edit/page.meta.ts +15 -0
  541. package/src/modules/warranty_claims/backend/warranty_claims/troubleshooting-guides/[id]/edit/page.tsx +141 -0
  542. package/src/modules/warranty_claims/backend/warranty_claims/troubleshooting-guides/create/page.meta.ts +15 -0
  543. package/src/modules/warranty_claims/backend/warranty_claims/troubleshooting-guides/create/page.tsx +57 -0
  544. package/src/modules/warranty_claims/backend/warranty_claims/troubleshooting-guides/page.meta.ts +17 -0
  545. package/src/modules/warranty_claims/backend/warranty_claims/troubleshooting-guides/page.tsx +477 -0
  546. package/src/modules/warranty_claims/backend/warranty_claims/troubleshooting-guides/troubleshootingGuideForm.tsx +214 -0
  547. package/src/modules/warranty_claims/backend/warranty_claims/vendor-policies/[id]/edit/page.meta.ts +15 -0
  548. package/src/modules/warranty_claims/backend/warranty_claims/vendor-policies/[id]/edit/page.tsx +137 -0
  549. package/src/modules/warranty_claims/backend/warranty_claims/vendor-policies/create/page.meta.ts +15 -0
  550. package/src/modules/warranty_claims/backend/warranty_claims/vendor-policies/create/page.tsx +51 -0
  551. package/src/modules/warranty_claims/backend/warranty_claims/vendor-policies/page.meta.ts +17 -0
  552. package/src/modules/warranty_claims/backend/warranty_claims/vendor-policies/page.tsx +506 -0
  553. package/src/modules/warranty_claims/backend/warranty_claims/vendor-policies/vendorPolicyForm.tsx +220 -0
  554. package/src/modules/warranty_claims/ce.ts +44 -0
  555. package/src/modules/warranty_claims/commands/claim-lines.ts +1082 -0
  556. package/src/modules/warranty_claims/commands/claims.ts +3364 -0
  557. package/src/modules/warranty_claims/commands/index.ts +3 -0
  558. package/src/modules/warranty_claims/commands/settings.ts +198 -0
  559. package/src/modules/warranty_claims/commands/shared.ts +212 -0
  560. package/src/modules/warranty_claims/data/constants.ts +37 -0
  561. package/src/modules/warranty_claims/data/enrichers.ts +165 -0
  562. package/src/modules/warranty_claims/data/entities.ts +663 -0
  563. package/src/modules/warranty_claims/data/validators.ts +634 -0
  564. package/src/modules/warranty_claims/di.ts +34 -0
  565. package/src/modules/warranty_claims/encryption.ts +26 -0
  566. package/src/modules/warranty_claims/events.ts +81 -0
  567. package/src/modules/warranty_claims/extension-points.ts +16 -0
  568. package/src/modules/warranty_claims/frontend/[orgSlug]/portal/claims/[id]/page.meta.ts +9 -0
  569. package/src/modules/warranty_claims/frontend/[orgSlug]/portal/claims/[id]/page.tsx +1037 -0
  570. package/src/modules/warranty_claims/frontend/[orgSlug]/portal/claims/new/page.meta.ts +9 -0
  571. package/src/modules/warranty_claims/frontend/[orgSlug]/portal/claims/new/page.tsx +1409 -0
  572. package/src/modules/warranty_claims/frontend/[orgSlug]/portal/claims/page.meta.ts +16 -0
  573. package/src/modules/warranty_claims/frontend/[orgSlug]/portal/claims/page.tsx +304 -0
  574. package/src/modules/warranty_claims/i18n/de.json +1270 -0
  575. package/src/modules/warranty_claims/i18n/en.json +1270 -0
  576. package/src/modules/warranty_claims/i18n/es.json +1270 -0
  577. package/src/modules/warranty_claims/i18n/ko.json +1270 -0
  578. package/src/modules/warranty_claims/i18n/pl.json +1270 -0
  579. package/src/modules/warranty_claims/index.ts +13 -0
  580. package/src/modules/warranty_claims/lib/aiAssist.ts +677 -0
  581. package/src/modules/warranty_claims/lib/apiIdsFilter.ts +12 -0
  582. package/src/modules/warranty_claims/lib/assigneeNames.ts +184 -0
  583. package/src/modules/warranty_claims/lib/attachmentVisibility.ts +5 -0
  584. package/src/modules/warranty_claims/lib/bulkFeedback.ts +6 -0
  585. package/src/modules/warranty_claims/lib/businessHours.ts +333 -0
  586. package/src/modules/warranty_claims/lib/businessHoursForm.ts +206 -0
  587. package/src/modules/warranty_claims/lib/claimTypeConfig.ts +49 -0
  588. package/src/modules/warranty_claims/lib/decryptionSafety.ts +19 -0
  589. package/src/modules/warranty_claims/lib/deskFilters.ts +131 -0
  590. package/src/modules/warranty_claims/lib/dictionaries.ts +212 -0
  591. package/src/modules/warranty_claims/lib/dictionaryLabels.ts +16 -0
  592. package/src/modules/warranty_claims/lib/emailIntake.ts +123 -0
  593. package/src/modules/warranty_claims/lib/escalation.ts +83 -0
  594. package/src/modules/warranty_claims/lib/externalIntake.ts +362 -0
  595. package/src/modules/warranty_claims/lib/grading.ts +23 -0
  596. package/src/modules/warranty_claims/lib/listSegments.ts +19 -0
  597. package/src/modules/warranty_claims/lib/orderOwnership.ts +58 -0
  598. package/src/modules/warranty_claims/lib/portalAttachmentUpload.ts +11 -0
  599. package/src/modules/warranty_claims/lib/portalAttachmentValidation.ts +115 -0
  600. package/src/modules/warranty_claims/lib/portalClaimAccess.ts +37 -0
  601. package/src/modules/warranty_claims/lib/quantity.ts +15 -0
  602. package/src/modules/warranty_claims/lib/risk.ts +371 -0
  603. package/src/modules/warranty_claims/lib/settings.ts +76 -0
  604. package/src/modules/warranty_claims/lib/stateMachine.ts +130 -0
  605. package/src/modules/warranty_claims/lib/timelineFilters.ts +22 -0
  606. package/src/modules/warranty_claims/lib/triage.ts +366 -0
  607. package/src/modules/warranty_claims/lib/troubleshooting.ts +157 -0
  608. package/src/modules/warranty_claims/lib/tryResolve.ts +7 -0
  609. package/src/modules/warranty_claims/lib/vendorPolicyRecovery.ts +182 -0
  610. package/src/modules/warranty_claims/lib/warrantyPreview.ts +20 -0
  611. package/src/modules/warranty_claims/migrations/.snapshot-open-mercato.json +3525 -0
  612. package/src/modules/warranty_claims/migrations/Migration20260703191537_warranty_claims.ts +38 -0
  613. package/src/modules/warranty_claims/migrations/Migration20260704115605_warranty_claims.ts +17 -0
  614. package/src/modules/warranty_claims/migrations/Migration20260704230000_warranty_claims.ts +17 -0
  615. package/src/modules/warranty_claims/migrations/Migration20260705121601_warranty_claims.ts +37 -0
  616. package/src/modules/warranty_claims/migrations/Migration20260709120000_warranty_claims.ts +25 -0
  617. package/src/modules/warranty_claims/migrations/Migration20260710023607_warranty_claims.ts +13 -0
  618. package/src/modules/warranty_claims/migrations/Migration20260716203936_warranty_claims.ts +15 -0
  619. package/src/modules/warranty_claims/migrations/Migration20260717150000_warranty_claims.ts +13 -0
  620. package/src/modules/warranty_claims/migrations/Migration20260719120000_warranty_claims.ts +13 -0
  621. package/src/modules/warranty_claims/migrations/Migration20260813103053_warranty_claims.ts +11 -0
  622. package/src/modules/warranty_claims/notifications.client.ts +78 -0
  623. package/src/modules/warranty_claims/notifications.ts +101 -0
  624. package/src/modules/warranty_claims/search.ts +199 -0
  625. package/src/modules/warranty_claims/services/adjudicationEvaluator.ts +344 -0
  626. package/src/modules/warranty_claims/services/claimNumberGenerator.ts +69 -0
  627. package/src/modules/warranty_claims/services/entitlementResolver.ts +102 -0
  628. package/src/modules/warranty_claims/services/returnLabelProvider.ts +30 -0
  629. package/src/modules/warranty_claims/setup.ts +154 -0
  630. package/src/modules/warranty_claims/subscribers/auto-vendor-recovery.ts +140 -0
  631. package/src/modules/warranty_claims/subscribers/claim-assigned-notification.ts +54 -0
  632. package/src/modules/warranty_claims/subscribers/claim-customer-reply-notification.ts +68 -0
  633. package/src/modules/warranty_claims/subscribers/claim-status-notification.ts +100 -0
  634. package/src/modules/warranty_claims/subscribers/claim-submitted-notification.ts +53 -0
  635. package/src/modules/warranty_claims/subscribers/email-to-claim.ts +175 -0
  636. package/src/modules/warranty_claims/subscribers/return-shipment-tracking.ts +226 -0
  637. package/src/modules/warranty_claims/subscribers/vendor-recovery-reconciliation-undo.ts +28 -0
  638. package/src/modules/warranty_claims/subscribers/vendor-recovery-reconciliation.ts +31 -0
  639. package/src/modules/warranty_claims/widgets/dashboard/claims-queue/config.ts +15 -0
  640. package/src/modules/warranty_claims/widgets/dashboard/claims-queue/widget.client.tsx +237 -0
  641. package/src/modules/warranty_claims/widgets/dashboard/claims-queue/widget.ts +27 -0
  642. package/src/modules/warranty_claims/widgets/injection/claims-ai-trigger/widget.client.tsx +527 -0
  643. package/src/modules/warranty_claims/widgets/injection/claims-ai-trigger/widget.ts +37 -0
  644. package/src/modules/warranty_claims/widgets/injection/customer-claims-tab/widget.ts +21 -0
  645. package/src/modules/warranty_claims/widgets/injection/customer-claims-tab.tsx +210 -0
  646. package/src/modules/warranty_claims/widgets/injection/order-claims-tab/widget.ts +21 -0
  647. package/src/modules/warranty_claims/widgets/injection/order-claims-tab.tsx +210 -0
  648. package/src/modules/warranty_claims/widgets/injection-table.ts +36 -0
  649. package/src/modules/warranty_claims/widgets/notifications/WarrantyClaimNotificationRenderer.tsx +253 -0
  650. package/src/modules/warranty_claims/widgets/notifications/index.ts +1 -0
  651. package/src/modules/warranty_claims/workers/sla-escalation-sweep.ts +450 -0
@@ -0,0 +1,1302 @@
1
+ "use client"
2
+
3
+ import * as React from 'react'
4
+ import Link from 'next/link'
5
+ import { usePathname, useRouter, useSearchParams } from 'next/navigation'
6
+ import type { SortingState } from '@tanstack/react-table'
7
+ import type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy'
8
+ import { Page, PageBody } from '@open-mercato/ui/backend/Page'
9
+ import { DataTable, type BulkAction, type DataTableExportFormat } from '@open-mercato/ui/backend/DataTable'
10
+ import { RowActions } from '@open-mercato/ui/backend/RowActions'
11
+ import type { FilterDef, FilterValues } from '@open-mercato/ui/backend/FilterBar'
12
+ import { Button } from '@open-mercato/ui/primitives/button'
13
+ import { StatusBadge } from '@open-mercato/ui/primitives/status-badge'
14
+ import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@open-mercato/ui/primitives/dialog'
15
+ import { CrudForm, type CrudField, type CrudFieldOption } from '@open-mercato/ui/backend/CrudForm'
16
+ import { ListEmptyState } from '@open-mercato/ui/backend/filters/ListEmptyState'
17
+ import { flash } from '@open-mercato/ui/backend/FlashMessages'
18
+ import { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'
19
+ import { useAppEvent } from '@open-mercato/ui/backend/injection/useAppEvent'
20
+ import { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'
21
+ import { apiCall, readApiResultOrThrow, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'
22
+ import { buildCrudExportUrl } from '@open-mercato/ui/backend/utils/crud'
23
+ import { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'
24
+ import { surfaceRecordConflict } from '@open-mercato/ui/backend/conflicts'
25
+ import { useCurrentUserId } from '@open-mercato/ui/backend/utils/useCurrentUserId'
26
+ import { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'
27
+ import { emitProgressUpdate } from '@open-mercato/shared/lib/frontend/progressEvents'
28
+ import { useLocale, useT } from '@open-mercato/shared/lib/i18n/context'
29
+ import { formatDateTime, formatRelativeTime } from '@open-mercato/shared/lib/time'
30
+ import { Plus } from 'lucide-react'
31
+ import {
32
+ fetchAssignableStaffMembersPage,
33
+ type AssignableStaffMember,
34
+ } from '@open-mercato/core/modules/customers/lib/assignableStaff'
35
+ import {
36
+ ClaimPriorityBadge,
37
+ ClaimStatusBadge,
38
+ CLAIM_STATUS_BADGE_VARIANTS,
39
+ type ClaimPriority,
40
+ type ClaimStatus,
41
+ } from './components/ClaimStatusBadge'
42
+ import { ClaimSlaIndicator } from './components/claimSla'
43
+ import { ClaimsKpiStrip, type WarrantyClaimsStats } from './components/ClaimsKpiStrip'
44
+ import { WarrantyWorkspace } from './components/WarrantyWorkspace'
45
+ import { useUserDisplayNames } from './components/useUserDisplayNames'
46
+ import { extensionPoints } from '../extension-points'
47
+ import { appendSkippedBulkCount } from '../lib/bulkFeedback'
48
+
49
+ type ClaimType = 'warranty' | 'return' | 'core_return' | 'vendor_recovery'
50
+ type ClaimChannel = 'staff' | 'portal' | 'api'
51
+
52
+ type ClaimRow = {
53
+ id: string
54
+ claimNumber: string | null
55
+ claimType: ClaimType | string | null
56
+ channel: ClaimChannel | string | null
57
+ status: ClaimStatus | string | null
58
+ priority: ClaimPriority | string | null
59
+ customerName: string | null
60
+ orderId: string | null
61
+ orderNumber: string | null
62
+ awaitingStaffReply: boolean
63
+ slaDueAt: string | null
64
+ slaPausedAt: string | null
65
+ submittedAt: string | null
66
+ assigneeUserId: string | null
67
+ updatedAt: string | null
68
+ }
69
+
70
+ type ClaimsResponse = {
71
+ items?: ClaimRow[]
72
+ total?: number
73
+ totalPages?: number
74
+ page?: number
75
+ pageSize?: number
76
+ error?: string
77
+ }
78
+
79
+ type AssignFormValues = {
80
+ assigneeUserId?: string | null
81
+ }
82
+
83
+ type ClaimsStats = WarrantyClaimsStats & { slaAtRisk?: number }
84
+
85
+ type ClaimsStatsResponse = {
86
+ ok?: boolean
87
+ result?: ClaimsStats
88
+ error?: string
89
+ }
90
+
91
+ type ClaimsTabCounts = Partial<Record<'all' | 'mine' | 'attention' | 'review' | 'goods' | 'resolved', number>>
92
+
93
+ type SearchParamsLike = {
94
+ toString: () => string
95
+ }
96
+
97
+ type RestoredClaimListState = {
98
+ page: number
99
+ search: string
100
+ filterValues: FilterValues
101
+ sorting: SortingState
102
+ }
103
+
104
+ type AssignDialogState = {
105
+ mode: 'single' | 'bulk'
106
+ rows: ClaimRow[]
107
+ resolve?: (result: false | { ok: true; affectedCount?: number }) => void
108
+ } | null
109
+
110
+ type BulkFailure = {
111
+ message: string
112
+ }
113
+
114
+ const CLAIM_STATUSES = Object.keys(CLAIM_STATUS_BADGE_VARIANTS) as ClaimStatus[]
115
+ const CLAIM_TYPES: ClaimType[] = ['warranty', 'return', 'core_return', 'vendor_recovery']
116
+ const CLAIM_CHANNELS = ['staff', 'portal', 'api'] as const
117
+ const CLAIM_PRIORITIES: ClaimPriority[] = ['low', 'normal', 'high', 'urgent']
118
+ const CANCEL_BLOCKED_STATUSES = new Set<string>(['received', 'inspecting', 'resolved', 'closed', 'cancelled'])
119
+ const PAGE_SIZE = 20
120
+ const UNASSIGNED_ASSIGNEE_VALUE = '__unassigned__'
121
+ const DEFAULT_SORTING: SortingState = [{ id: 'slaDueAt', desc: false }]
122
+ const SORTABLE_FIELDS = new Set(['slaDueAt', 'createdAt', 'updatedAt'])
123
+ const DATE_ONLY_PATTERN = /^\d{4}-\d{2}-\d{2}$/
124
+ const GOODS_FLOW_STATUSES: ClaimStatus[] = ['approved', 'awaiting_return', 'received', 'inspecting']
125
+ const RESOLVED_STATUSES: ClaimStatus[] = ['resolved', 'closed']
126
+
127
+ function isRecord(value: unknown): value is Record<string, unknown> {
128
+ return value !== null && typeof value === 'object' && !Array.isArray(value)
129
+ }
130
+
131
+ function toStringOrNull(value: unknown): string | null {
132
+ return typeof value === 'string' && value.trim().length ? value : null
133
+ }
134
+
135
+ function normalizeClaimRow(value: unknown): ClaimRow | null {
136
+ if (!isRecord(value)) return null
137
+ const id = toStringOrNull(value.id)
138
+ if (!id) return null
139
+ return {
140
+ id,
141
+ claimNumber: toStringOrNull(value.claimNumber),
142
+ claimType: toStringOrNull(value.claimType),
143
+ channel: toStringOrNull(value.channel),
144
+ status: toStringOrNull(value.status),
145
+ priority: toStringOrNull(value.priority),
146
+ customerName: toStringOrNull(value.customerName),
147
+ orderId: toStringOrNull(value.orderId),
148
+ orderNumber: toStringOrNull(value.orderNumber),
149
+ awaitingStaffReply: value.awaitingStaffReply === true,
150
+ slaDueAt: toStringOrNull(value.slaDueAt),
151
+ slaPausedAt: toStringOrNull(value.slaPausedAt),
152
+ submittedAt: toStringOrNull(value.submittedAt),
153
+ assigneeUserId: toStringOrNull(value.assigneeUserId),
154
+ updatedAt: toStringOrNull(value.updatedAt),
155
+ }
156
+ }
157
+
158
+ function normalizeClaimChannel(value: string | null | undefined): ClaimChannel | null {
159
+ return value === 'staff' || value === 'portal' || value === 'api' ? value : null
160
+ }
161
+
162
+ function valueAsStringArray(value: unknown): string[] {
163
+ if (!Array.isArray(value)) return []
164
+ return value.filter((entry): entry is string => typeof entry === 'string' && entry.trim().length > 0)
165
+ }
166
+
167
+ function toDateOnlyOrNull(value: unknown): string | null {
168
+ const normalized = toStringOrNull(value)
169
+ return normalized && DATE_ONLY_PATTERN.test(normalized) ? normalized : null
170
+ }
171
+
172
+ function valueAsDateRange(value: unknown): { from: string | null; to: string | null } {
173
+ if (!isRecord(value)) return { from: null, to: null }
174
+ return { from: toDateOnlyOrNull(value.from), to: toDateOnlyOrNull(value.to) }
175
+ }
176
+
177
+ function defaultSortingState(): SortingState {
178
+ return DEFAULT_SORTING.map((entry) => ({ ...entry }))
179
+ }
180
+
181
+ function isOneOf<T extends string>(value: string | null, values: readonly T[]): value is T {
182
+ return value !== null && (values as readonly string[]).includes(value)
183
+ }
184
+
185
+ function parseClaimListUrlState(searchParams: SearchParamsLike): RestoredClaimListState {
186
+ const params = new URLSearchParams(searchParams.toString())
187
+ const parsedPage = Number(params.get('page') ?? '1')
188
+ const filterValues: FilterValues = {}
189
+ const statuses = (params.get('status') ?? '')
190
+ .split(',')
191
+ .map((status) => status.trim())
192
+ .filter((status): status is ClaimStatus => isOneOf(status, CLAIM_STATUSES))
193
+ if (statuses.length) filterValues.status = statuses
194
+ const claimType = params.get('claimType')
195
+ if (isOneOf(claimType, CLAIM_TYPES)) filterValues.claimType = claimType
196
+ const priority = params.get('priority')
197
+ if (isOneOf(priority, CLAIM_PRIORITIES)) filterValues.priority = priority
198
+ const channel = params.get('channel')
199
+ if (isOneOf(channel, CLAIM_CHANNELS)) filterValues.channel = channel
200
+ if (params.get('unassignedOnly') === 'true') {
201
+ filterValues.assigneeUserId = UNASSIGNED_ASSIGNEE_VALUE
202
+ } else {
203
+ const assigneeUserId = toStringOrNull(params.get('assigneeUserId'))
204
+ if (assigneeUserId) filterValues.assigneeUserId = assigneeUserId
205
+ }
206
+ const submittedFrom = toDateOnlyOrNull(params.get('submittedFrom'))
207
+ const submittedTo = toDateOnlyOrNull(params.get('submittedTo'))
208
+ if (submittedFrom || submittedTo) {
209
+ filterValues.submittedRange = { from: submittedFrom ?? undefined, to: submittedTo ?? undefined }
210
+ }
211
+ const createdFrom = toDateOnlyOrNull(params.get('createdFrom'))
212
+ const createdTo = toDateOnlyOrNull(params.get('createdTo'))
213
+ if (createdFrom || createdTo) {
214
+ filterValues.createdRange = { from: createdFrom ?? undefined, to: createdTo ?? undefined }
215
+ }
216
+ if (params.get('overdueOnly') === 'true') filterValues.overdueOnly = true
217
+ if (params.get('slaAtRiskOnly') === 'true') filterValues.slaAtRiskOnly = true
218
+ if (params.get('needsAttention') === 'true') filterValues.needsAttention = true
219
+ if (params.get('attentionOnly') === 'true') filterValues.attentionOnly = true
220
+ const sortField = params.get('sortField')
221
+ const sorting = sortField && SORTABLE_FIELDS.has(sortField)
222
+ ? [{ id: sortField, desc: params.get('sortDir') === 'desc' }]
223
+ : defaultSortingState()
224
+ return {
225
+ page: Number.isFinite(parsedPage) && parsedPage > 0 ? Math.trunc(parsedPage) : 1,
226
+ search: params.get('search') ?? '',
227
+ filterValues,
228
+ sorting,
229
+ }
230
+ }
231
+
232
+ function appendClaimListFilterParams(params: URLSearchParams, filterValues: FilterValues): void {
233
+ const statuses = valueAsStringArray(filterValues.status)
234
+ if (statuses.length) params.set('status', statuses.join(','))
235
+ const claimType = toStringOrNull(filterValues.claimType)
236
+ if (claimType) params.set('claimType', claimType)
237
+ const priority = toStringOrNull(filterValues.priority)
238
+ if (priority) params.set('priority', priority)
239
+ const channel = toStringOrNull(filterValues.channel)
240
+ if (channel) params.set('channel', channel)
241
+ const assigneeUserId = toStringOrNull(filterValues.assigneeUserId)
242
+ if (assigneeUserId === UNASSIGNED_ASSIGNEE_VALUE) params.set('unassignedOnly', 'true')
243
+ else if (assigneeUserId) params.set('assigneeUserId', assigneeUserId)
244
+ const submittedRange = valueAsDateRange(filterValues.submittedRange)
245
+ if (submittedRange.from) params.set('submittedFrom', submittedRange.from)
246
+ if (submittedRange.to) params.set('submittedTo', submittedRange.to)
247
+ const createdRange = valueAsDateRange(filterValues.createdRange)
248
+ if (createdRange.from) params.set('createdFrom', createdRange.from)
249
+ if (createdRange.to) params.set('createdTo', createdRange.to)
250
+ if (filterValues.overdueOnly === true) params.set('overdueOnly', 'true')
251
+ if (filterValues.slaAtRiskOnly === true) params.set('slaAtRiskOnly', 'true')
252
+ if (filterValues.needsAttention === true) params.set('needsAttention', 'true')
253
+ if (filterValues.attentionOnly === true) params.set('attentionOnly', 'true')
254
+ }
255
+
256
+ function appendClaimListSortParams(params: URLSearchParams, sorting: SortingState): void {
257
+ const primarySort = sorting[0]
258
+ if (!primarySort || !SORTABLE_FIELDS.has(primarySort.id)) return
259
+ params.set('sortField', primarySort.id)
260
+ params.set('sortDir', primarySort.desc ? 'desc' : 'asc')
261
+ }
262
+
263
+ function buildClaimListUrlQuery(page: number, search: string, filterValues: FilterValues, sorting: SortingState): string {
264
+ const params = new URLSearchParams()
265
+ if (search.trim()) params.set('search', search.trim())
266
+ if (page > 1) params.set('page', String(page))
267
+ appendClaimListFilterParams(params, filterValues)
268
+ appendClaimListSortParams(params, sorting)
269
+ return params.toString()
270
+ }
271
+
272
+ function buildClaimListApiQuery(page: number, search: string, filterValues: FilterValues, sorting: SortingState): string {
273
+ const params = new URLSearchParams()
274
+ params.set('page', String(page))
275
+ params.set('pageSize', String(PAGE_SIZE))
276
+ if (search.trim()) params.set('search', search.trim())
277
+ appendClaimListFilterParams(params, filterValues)
278
+ appendClaimListSortParams(params, sorting)
279
+ return params.toString()
280
+ }
281
+
282
+ function sameStringSet(left: string[], right: string[]): boolean {
283
+ if (left.length !== right.length) return false
284
+ const leftSet = new Set(left)
285
+ return right.every((value) => leftSet.has(value))
286
+ }
287
+
288
+ function staffOptionLabel(member: AssignableStaffMember): string {
289
+ return member.email && member.email !== member.displayName
290
+ ? `${member.displayName} (${member.email})`
291
+ : member.displayName
292
+ }
293
+
294
+ function normalizeAssigneeValue(value: string | null | undefined): string | null {
295
+ const normalized = value?.trim()
296
+ if (!normalized || normalized === UNASSIGNED_ASSIGNEE_VALUE) return null
297
+ return normalized
298
+ }
299
+
300
+ function buildConflictError(call: { status: number; result: unknown }, fallbackMessage: string): Error & Record<string, unknown> {
301
+ const payload = isRecord(call.result) ? call.result : {}
302
+ const message = typeof payload.error === 'string' ? payload.error : fallbackMessage
303
+ return Object.assign(new Error(message), { status: call.status }, payload)
304
+ }
305
+
306
+ type BulkProgressLabels = {
307
+ jobType: string
308
+ name: string
309
+ }
310
+
311
+ function createClientProgressJobId(jobType: string): string {
312
+ const cryptoRef =
313
+ typeof globalThis !== 'undefined'
314
+ ? (globalThis as { crypto?: { randomUUID?: () => string } }).crypto
315
+ : undefined
316
+ if (cryptoRef && typeof cryptoRef.randomUUID === 'function') {
317
+ return `client:${cryptoRef.randomUUID()}`
318
+ }
319
+ return `client:${jobType}:${Date.now()}:${Math.random().toString(36).slice(2)}`
320
+ }
321
+
322
+ function calculateBulkProgressPercent(processed: number, total: number): number {
323
+ if (total <= 0) return 100
324
+ return Math.max(0, Math.min(100, Math.round((processed / total) * 100)))
325
+ }
326
+
327
+ function calculateBulkEtaSeconds(startedAtMs: number, processed: number, total: number): number | null {
328
+ if (processed <= 0 || processed >= total) return null
329
+ const elapsedSeconds = Math.max(1, Math.round((Date.now() - startedAtMs) / 1000))
330
+ return Math.ceil((elapsedSeconds / processed) * (total - processed))
331
+ }
332
+
333
+ async function runBulkClaimActionWithProgress(
334
+ rows: ClaimRow[],
335
+ progress: BulkProgressLabels,
336
+ execute: (claim: ClaimRow) => Promise<unknown>,
337
+ fallbackErrorMessage: string,
338
+ ): Promise<{ succeeded: number; failures: BulkFailure[] }> {
339
+ let succeeded = 0
340
+ const failures: BulkFailure[] = []
341
+ const progressJobId = rows.length > 0 ? createClientProgressJobId(progress.jobType) : null
342
+ const startedAtMs = Date.now()
343
+ const startedAt = new Date(startedAtMs).toISOString()
344
+ if (progressJobId) {
345
+ emitProgressUpdate({
346
+ jobId: progressJobId,
347
+ jobType: progress.jobType,
348
+ name: progress.name,
349
+ description: null,
350
+ meta: null,
351
+ status: 'running',
352
+ progressPercent: 0,
353
+ processedCount: 0,
354
+ totalCount: rows.length,
355
+ etaSeconds: null,
356
+ cancellable: false,
357
+ startedAt,
358
+ })
359
+ }
360
+ for (const claim of rows) {
361
+ const error = await execute(claim)
362
+ if (error) failures.push({ message: error instanceof Error ? error.message : fallbackErrorMessage })
363
+ else succeeded += 1
364
+ if (progressJobId) {
365
+ const processed = succeeded + failures.length
366
+ emitProgressUpdate({
367
+ jobId: progressJobId,
368
+ jobType: progress.jobType,
369
+ name: progress.name,
370
+ description: null,
371
+ meta: null,
372
+ status: 'running',
373
+ progressPercent: calculateBulkProgressPercent(processed, rows.length),
374
+ processedCount: processed,
375
+ totalCount: rows.length,
376
+ etaSeconds: calculateBulkEtaSeconds(startedAtMs, processed, rows.length),
377
+ cancellable: false,
378
+ startedAt,
379
+ })
380
+ }
381
+ }
382
+ if (progressJobId) {
383
+ emitProgressUpdate({
384
+ jobId: progressJobId,
385
+ jobType: progress.jobType,
386
+ name: progress.name,
387
+ description: null,
388
+ meta: { succeededCount: succeeded, failedCount: failures.length },
389
+ status: failures.length === rows.length && rows.length > 0 ? 'failed' : 'completed',
390
+ progressPercent: 100,
391
+ processedCount: rows.length,
392
+ totalCount: rows.length,
393
+ etaSeconds: 0,
394
+ cancellable: false,
395
+ startedAt,
396
+ finishedAt: new Date().toISOString(),
397
+ errorMessage:
398
+ failures.length === rows.length && failures[0]?.message
399
+ ? failures[0].message
400
+ : null,
401
+ })
402
+ }
403
+ return { succeeded, failures }
404
+ }
405
+
406
+ export default function WarrantyClaimsPage() {
407
+ const t = useT()
408
+ const locale = useLocale()
409
+ const router = useRouter()
410
+ const pathname = usePathname()
411
+ const searchParams = useSearchParams()
412
+ const scopeVersion = useOrganizationScopeVersion()
413
+ const { confirm, ConfirmDialogElement } = useConfirmDialog()
414
+ const currentUserId = useCurrentUserId()
415
+ const initialUrlStateRef = React.useRef<RestoredClaimListState | null>(null)
416
+ if (initialUrlStateRef.current === null) {
417
+ initialUrlStateRef.current = parseClaimListUrlState(searchParams)
418
+ }
419
+ const initialUrlState = initialUrlStateRef.current
420
+ const [rows, setRows] = React.useState<ClaimRow[]>([])
421
+ const [page, setPage] = React.useState(initialUrlState.page)
422
+ const [total, setTotal] = React.useState(0)
423
+ const [totalPages, setTotalPages] = React.useState(1)
424
+ const [tabCounts, setTabCounts] = React.useState<ClaimsTabCounts>({})
425
+ const [search, setSearch] = React.useState(initialUrlState.search)
426
+ const [filterValues, setFilterValues] = React.useState<FilterValues>(initialUrlState.filterValues)
427
+ const [sorting, setSorting] = React.useState<SortingState>(initialUrlState.sorting)
428
+ const [loading, setLoading] = React.useState(true)
429
+ const [stats, setStats] = React.useState<ClaimsStats | null>(null)
430
+ const [statsLoading, setStatsLoading] = React.useState(true)
431
+ const [statsError, setStatsError] = React.useState(false)
432
+ const [reloadToken, setReloadToken] = React.useState(0)
433
+ const [assignDialog, setAssignDialog] = React.useState<AssignDialogState>(null)
434
+ const urlQueryRef = React.useRef(searchParams.toString())
435
+
436
+ const mutationContextId = 'warranty-claims-list'
437
+ const { runMutation, retryLastMutation } = useGuardedMutation<{
438
+ formId: string
439
+ resourceKind: string
440
+ resourceId?: string
441
+ retryLastMutation: () => Promise<boolean>
442
+ }>({
443
+ contextId: mutationContextId,
444
+ blockedMessage: t('ui.forms.flash.saveBlocked', 'Save blocked by validation'),
445
+ })
446
+
447
+ React.useEffect(() => {
448
+ const current = searchParams.toString()
449
+ if (urlQueryRef.current === current) return
450
+ urlQueryRef.current = current
451
+ const restored = parseClaimListUrlState(searchParams)
452
+ setPage(restored.page)
453
+ setSearch(restored.search)
454
+ setFilterValues(restored.filterValues)
455
+ setSorting(restored.sorting)
456
+ }, [searchParams])
457
+
458
+ React.useEffect(() => {
459
+ if (!pathname) return
460
+ const next = buildClaimListUrlQuery(page, search, filterValues, sorting)
461
+ if (urlQueryRef.current === next) return
462
+ urlQueryRef.current = next
463
+ router.replace(next ? `${pathname}?${next}` : pathname, { scroll: false })
464
+ }, [filterValues, page, pathname, router, search, sorting])
465
+
466
+ const queryString = React.useMemo(() => {
467
+ return buildClaimListApiQuery(page, search, filterValues, sorting)
468
+ }, [filterValues, page, search, sorting])
469
+
470
+ const currentParams = React.useMemo(
471
+ () => Object.fromEntries(new URLSearchParams(queryString)),
472
+ [queryString],
473
+ )
474
+
475
+ const exportConfig = React.useMemo(() => ({
476
+ view: {
477
+ getUrl: (format: DataTableExportFormat) =>
478
+ buildCrudExportUrl('warranty_claims', { ...currentParams, exportScope: 'view' }, format),
479
+ },
480
+ full: {
481
+ getUrl: (format: DataTableExportFormat) =>
482
+ buildCrudExportUrl('warranty_claims', { ...currentParams, exportScope: 'full', all: 'true' }, format),
483
+ },
484
+ }), [currentParams])
485
+
486
+ const reload = React.useCallback(() => {
487
+ setReloadToken((current) => current + 1)
488
+ }, [])
489
+
490
+ useAppEvent('warranty_claims.claim.*', () => {
491
+ reload()
492
+ }, [reload])
493
+
494
+ React.useEffect(() => {
495
+ let cancelled = false
496
+ async function loadClaims() {
497
+ setLoading(true)
498
+ try {
499
+ const fallback: ClaimsResponse = { items: [], total: 0, totalPages: 1 }
500
+ const call = await apiCall<ClaimsResponse>(`/api/warranty_claims?${queryString}`, undefined, { fallback })
501
+ if (!call.ok) {
502
+ const message = call.result?.error ?? t('warranty_claims.list.error.load')
503
+ flash(message, 'error')
504
+ return
505
+ }
506
+ if (cancelled) return
507
+ const items = Array.isArray(call.result?.items) ? call.result.items : []
508
+ setRows(items.map(normalizeClaimRow).filter((row): row is ClaimRow => row !== null))
509
+ setTotal(typeof call.result?.total === 'number' ? call.result.total : items.length)
510
+ setTotalPages(typeof call.result?.totalPages === 'number' ? call.result.totalPages : 1)
511
+ } catch (error) {
512
+ if (!cancelled) {
513
+ const message = error instanceof Error ? error.message : t('warranty_claims.list.error.load')
514
+ flash(message, 'error')
515
+ }
516
+ } finally {
517
+ if (!cancelled) setLoading(false)
518
+ }
519
+ }
520
+ void loadClaims()
521
+ return () => {
522
+ cancelled = true
523
+ }
524
+ }, [queryString, reloadToken, scopeVersion, t])
525
+
526
+ const filterAssigneeUserId = toStringOrNull(filterValues.assigneeUserId)
527
+ const assigneeUserIds = React.useMemo(() => {
528
+ const ids: Array<string | null> = rows.map((row) => row.assigneeUserId)
529
+ if (filterAssigneeUserId && filterAssigneeUserId !== UNASSIGNED_ASSIGNEE_VALUE) ids.push(filterAssigneeUserId)
530
+ return ids
531
+ }, [filterAssigneeUserId, rows])
532
+ const assigneeDisplayNames = useUserDisplayNames(assigneeUserIds)
533
+
534
+ React.useEffect(() => {
535
+ const controller = new AbortController()
536
+ setStatsLoading(true)
537
+ setStatsError(false)
538
+ readApiResultOrThrow<ClaimsStatsResponse>(
539
+ '/api/warranty_claims/stats',
540
+ { signal: controller.signal },
541
+ { errorMessage: t('warranty_claims.list.error.load') },
542
+ )
543
+ .then((payload) => {
544
+ if (controller.signal.aborted) return
545
+ if (payload?.ok === true && payload.result) {
546
+ setStats(payload.result)
547
+ setStatsError(false)
548
+ } else {
549
+ setStats(null)
550
+ setStatsError(true)
551
+ }
552
+ })
553
+ .catch(() => {
554
+ if (!controller.signal.aborted) {
555
+ setStats(null)
556
+ setStatsError(true)
557
+ }
558
+ })
559
+ .finally(() => {
560
+ if (!controller.signal.aborted) setStatsLoading(false)
561
+ })
562
+ return () => controller.abort()
563
+ }, [reloadToken, scopeVersion, t])
564
+
565
+ React.useEffect(() => {
566
+ const controller = new AbortController()
567
+
568
+ async function loadCount(query: Record<string, string>): Promise<number> {
569
+ const params = new URLSearchParams({ page: '1', pageSize: '1', ...query })
570
+ const fallback: ClaimsResponse = { items: [], total: 0, totalPages: 1 }
571
+ const call = await apiCall<ClaimsResponse>(
572
+ `/api/warranty_claims?${params.toString()}`,
573
+ { signal: controller.signal },
574
+ { fallback },
575
+ )
576
+ if (!call.ok) throw new Error('[internal] Failed to load claim counts')
577
+ return typeof call.result?.total === 'number' ? call.result.total : 0
578
+ }
579
+
580
+ const mineQuery = currentUserId ? { assigneeUserId: currentUserId } : null
581
+ Promise.all([
582
+ loadCount({}),
583
+ mineQuery ? loadCount(mineQuery) : Promise.resolve(0),
584
+ loadCount({ attentionOnly: 'true' }),
585
+ loadCount({ status: 'in_review' }),
586
+ loadCount({ status: GOODS_FLOW_STATUSES.join(',') }),
587
+ loadCount({ status: RESOLVED_STATUSES.join(',') }),
588
+ ])
589
+ .then(([all, mine, attention, review, goods, resolved]) => {
590
+ if (!controller.signal.aborted) setTabCounts({ all, mine, attention, review, goods, resolved })
591
+ })
592
+ .catch(() => {
593
+ if (!controller.signal.aborted) setTabCounts({})
594
+ })
595
+
596
+ return () => controller.abort()
597
+ }, [currentUserId, reloadToken, scopeVersion])
598
+
599
+ const statusOptions = React.useMemo(
600
+ () => CLAIM_STATUSES.map((status) => ({ value: status, label: t(`warranty_claims.status.${status}`) })),
601
+ [t],
602
+ )
603
+ const channelOptions = React.useMemo(
604
+ () => CLAIM_CHANNELS.map((channel) => ({ value: channel, label: t(`warranty_claims.channel.${channel}`) })),
605
+ [t],
606
+ )
607
+ const claimTypeOptions = React.useMemo(
608
+ () => CLAIM_TYPES.map((claimType) => ({ value: claimType, label: t(`warranty_claims.claimType.${claimType}`) })),
609
+ [t],
610
+ )
611
+ const priorityOptions = React.useMemo(
612
+ () => CLAIM_PRIORITIES.map((priority) => ({ value: priority, label: t(`warranty_claims.priority.${priority}`) })),
613
+ [t],
614
+ )
615
+
616
+ const loadAssignableStaffOptions = React.useCallback(async (query?: string): Promise<CrudFieldOption[]> => {
617
+ const page = await fetchAssignableStaffMembersPage(query ?? '', { pageSize: 24 })
618
+ const options = page.items.map((member) => ({
619
+ value: member.userId,
620
+ label: staffOptionLabel(member),
621
+ }))
622
+ return [
623
+ {
624
+ value: UNASSIGNED_ASSIGNEE_VALUE,
625
+ label: t('warranty_claims.form.assigneeUserId.unassigned', 'Unassigned'),
626
+ },
627
+ ...options,
628
+ ]
629
+ }, [t])
630
+
631
+ const formatAssigneeFilterValue = React.useCallback((value: string) => {
632
+ if (value === UNASSIGNED_ASSIGNEE_VALUE) return t('warranty_claims.form.assigneeUserId.unassigned', 'Unassigned')
633
+ return assigneeDisplayNames[value] ?? t('warranty_claims.list.assignee.unknownUser', 'Unknown user')
634
+ }, [assigneeDisplayNames, t])
635
+
636
+ const filters = React.useMemo<FilterDef[]>(() => [
637
+ {
638
+ id: 'status',
639
+ label: t('warranty_claims.list.filter.status'),
640
+ type: 'select',
641
+ multiple: true,
642
+ options: statusOptions,
643
+ },
644
+ {
645
+ id: 'claimType',
646
+ label: t('warranty_claims.list.filter.claimType'),
647
+ type: 'select',
648
+ options: claimTypeOptions,
649
+ },
650
+ {
651
+ id: 'priority',
652
+ label: t('warranty_claims.list.filter.priority'),
653
+ type: 'select',
654
+ options: priorityOptions,
655
+ },
656
+ {
657
+ id: 'channel',
658
+ label: t('warranty_claims.list.filter.channel'),
659
+ type: 'select',
660
+ options: channelOptions,
661
+ },
662
+ {
663
+ id: 'assigneeUserId',
664
+ label: t('warranty_claims.list.filter.assignee'),
665
+ type: 'select',
666
+ loadOptions: loadAssignableStaffOptions,
667
+ formatValue: formatAssigneeFilterValue,
668
+ },
669
+ {
670
+ id: 'submittedRange',
671
+ label: t('warranty_claims.list.filter.submittedBetween', 'Submitted between'),
672
+ type: 'dateRange',
673
+ },
674
+ {
675
+ id: 'createdRange',
676
+ label: t('warranty_claims.list.filter.createdBetween', 'Created between'),
677
+ type: 'dateRange',
678
+ },
679
+ {
680
+ id: 'overdueOnly',
681
+ label: t('warranty_claims.list.filter.overdueOnly'),
682
+ type: 'checkbox',
683
+ },
684
+ {
685
+ id: 'slaAtRiskOnly',
686
+ label: t('warranty_claims.list.filter.slaAtRiskOnly', 'SLA at risk only'),
687
+ type: 'checkbox',
688
+ },
689
+ ], [
690
+ channelOptions,
691
+ claimTypeOptions,
692
+ formatAssigneeFilterValue,
693
+ loadAssignableStaffOptions,
694
+ priorityOptions,
695
+ statusOptions,
696
+ t,
697
+ ])
698
+
699
+ const assignSeedOptions = React.useMemo<CrudFieldOption[]>(() => {
700
+ const options = new Map<string, CrudFieldOption>()
701
+ options.set(UNASSIGNED_ASSIGNEE_VALUE, {
702
+ value: UNASSIGNED_ASSIGNEE_VALUE,
703
+ label: t('warranty_claims.form.assigneeUserId.unassigned', 'Unassigned'),
704
+ })
705
+ for (const row of assignDialog?.rows ?? []) {
706
+ if (!row.assigneeUserId || options.has(row.assigneeUserId)) continue
707
+ options.set(row.assigneeUserId, {
708
+ value: row.assigneeUserId,
709
+ label: assigneeDisplayNames[row.assigneeUserId]
710
+ ?? t('warranty_claims.list.assignee.unknownUser', 'Unknown user'),
711
+ })
712
+ }
713
+ return Array.from(options.values())
714
+ }, [assignDialog?.rows, assigneeDisplayNames, t])
715
+
716
+ const executeClaimAction = React.useCallback(async (
717
+ claim: ClaimRow,
718
+ actionId: string,
719
+ endpoint: string,
720
+ body: Record<string, unknown>,
721
+ ): Promise<unknown> => {
722
+ try {
723
+ await runMutation({
724
+ operation: async () => {
725
+ const call = await withScopedApiRequestHeaders(
726
+ buildOptimisticLockHeader(claim.updatedAt),
727
+ () => apiCall(endpoint, {
728
+ method: 'POST',
729
+ headers: { 'content-type': 'application/json' },
730
+ body: JSON.stringify(body),
731
+ }),
732
+ )
733
+ if (!call.ok) {
734
+ const error = buildConflictError(call, t('warranty_claims.detail.error.action'))
735
+ throw error
736
+ }
737
+ return call
738
+ },
739
+ mutationPayload: { action: actionId, ...body },
740
+ context: {
741
+ formId: mutationContextId,
742
+ resourceKind: 'warranty_claims.claim',
743
+ resourceId: claim.id,
744
+ retryLastMutation,
745
+ },
746
+ })
747
+ return null
748
+ } catch (error) {
749
+ return error
750
+ }
751
+ }, [mutationContextId, retryLastMutation, runMutation, t])
752
+
753
+ const runClaimAction = React.useCallback(async (
754
+ claim: ClaimRow,
755
+ actionId: string,
756
+ endpoint: string,
757
+ body: Record<string, unknown>,
758
+ successKey: string,
759
+ successFallback?: string,
760
+ ) => {
761
+ const error = await executeClaimAction(claim, actionId, endpoint, body)
762
+ if (error) {
763
+ if (surfaceRecordConflict(error, t, { onRefresh: reload })) return
764
+ flash(error instanceof Error ? error.message : t('warranty_claims.list.error.action'), 'error')
765
+ return
766
+ }
767
+ flash(t(successKey, successFallback), 'success')
768
+ reload()
769
+ }, [executeClaimAction, reload, t])
770
+
771
+ const handleCancel = React.useCallback(async (claim: ClaimRow) => {
772
+ const confirmed = await confirm({
773
+ title: t('warranty_claims.detail.confirm.cancelTitle'),
774
+ variant: 'destructive',
775
+ })
776
+ if (!confirmed) return
777
+ await runClaimAction(
778
+ claim,
779
+ 'cancel',
780
+ '/api/warranty_claims/transition',
781
+ { id: claim.id, toStatus: 'cancelled' },
782
+ 'warranty_claims.list.flash.cancelled',
783
+ )
784
+ }, [confirm, runClaimAction, t])
785
+
786
+ const flashBulkSummary = React.useCallback((succeeded: number, failures: BulkFailure[], skipped = 0) => {
787
+ const summary = t(
788
+ 'warranty_claims.bulk.summary',
789
+ 'Bulk action finished: {succeeded} succeeded, {failed} failed.',
790
+ { succeeded, failed: failures.length },
791
+ )
792
+ const summaryWithSkipped = appendSkippedBulkCount(summary, skipped, t)
793
+ if (failures.length) {
794
+ flash(
795
+ `${summaryWithSkipped} ${t('warranty_claims.bulk.firstError', 'First error: {message}', { message: failures[0].message })}`,
796
+ 'warning',
797
+ )
798
+ return
799
+ }
800
+ flash(summaryWithSkipped, skipped > 0 ? 'warning' : 'success')
801
+ }, [t])
802
+
803
+ const runBulkAssign = React.useCallback(async (selectedRows: ClaimRow[], assigneeUserId: string | null) => {
804
+ const { succeeded, failures } = await runBulkClaimActionWithProgress(
805
+ selectedRows,
806
+ {
807
+ jobType: 'warranty_claims.claims.bulk_assign',
808
+ name: t('warranty_claims.bulk.assign', 'Assign selected'),
809
+ },
810
+ (claim) => executeClaimAction(
811
+ claim,
812
+ 'bulk-assign',
813
+ '/api/warranty_claims/assign',
814
+ { id: claim.id, assigneeUserId },
815
+ ),
816
+ t('warranty_claims.list.error.action'),
817
+ )
818
+ flashBulkSummary(succeeded, failures)
819
+ reload()
820
+ return { ok: true as const, affectedCount: succeeded }
821
+ }, [executeClaimAction, flashBulkSummary, reload, t])
822
+
823
+ const runBulkCancel = React.useCallback(async (selectedRows: ClaimRow[]) => {
824
+ const { succeeded, failures } = await runBulkClaimActionWithProgress(
825
+ selectedRows,
826
+ {
827
+ jobType: 'warranty_claims.claims.bulk_cancel',
828
+ name: t('warranty_claims.bulk.cancel', 'Cancel selected'),
829
+ },
830
+ (claim) => executeClaimAction(
831
+ claim,
832
+ 'bulk-cancel',
833
+ '/api/warranty_claims/transition',
834
+ { id: claim.id, toStatus: 'cancelled' },
835
+ ),
836
+ t('warranty_claims.list.error.action'),
837
+ )
838
+ flashBulkSummary(succeeded, failures)
839
+ reload()
840
+ return { ok: true as const, affectedCount: succeeded }
841
+ }, [executeClaimAction, flashBulkSummary, reload, t])
842
+
843
+ const runBulkStartReview = React.useCallback(async (selectedRows: ClaimRow[]) => {
844
+ const eligibleRows = selectedRows.filter((claim) => claim.status === 'submitted')
845
+ const skipped = selectedRows.length - eligibleRows.length
846
+ const { succeeded, failures } = await runBulkClaimActionWithProgress(
847
+ eligibleRows,
848
+ {
849
+ jobType: 'warranty_claims.claims.bulk_start_review',
850
+ name: t('warranty_claims.bulk.startReview', 'Start review for selected'),
851
+ },
852
+ (claim) => executeClaimAction(
853
+ claim,
854
+ 'bulk-start-review',
855
+ '/api/warranty_claims/transition',
856
+ { id: claim.id, toStatus: 'in_review' },
857
+ ),
858
+ t('warranty_claims.list.error.action'),
859
+ )
860
+ flashBulkSummary(succeeded, failures, skipped)
861
+ reload()
862
+ return { ok: true as const, affectedCount: succeeded }
863
+ }, [executeClaimAction, flashBulkSummary, reload, t])
864
+
865
+ const closeAssignDialog = React.useCallback((result: false | { ok: true; affectedCount?: number } = false) => {
866
+ setAssignDialog((current) => {
867
+ current?.resolve?.(result)
868
+ return null
869
+ })
870
+ }, [])
871
+
872
+ const assignFields = React.useMemo<CrudField[]>(() => [
873
+ {
874
+ id: 'assigneeUserId',
875
+ label: t('warranty_claims.form.assigneeUserId'),
876
+ type: 'combobox',
877
+ placeholder: t('warranty_claims.form.assigneeUserId.searchPlaceholder', 'Search staff'),
878
+ loadOptions: loadAssignableStaffOptions,
879
+ seedOptions: assignSeedOptions,
880
+ allowCustomValues: false,
881
+ },
882
+ ], [assignSeedOptions, loadAssignableStaffOptions, t])
883
+
884
+ const bulkActions = React.useMemo<BulkAction<ClaimRow>[]>(() => [
885
+ {
886
+ id: 'bulk-assign',
887
+ label: t('warranty_claims.bulk.assign', 'Assign selected'),
888
+ onExecute: (selectedRows) => new Promise<false | { ok: true; affectedCount?: number }>((resolve) => {
889
+ if (!selectedRows.length) {
890
+ resolve(false)
891
+ return
892
+ }
893
+ setAssignDialog({ mode: 'bulk', rows: selectedRows, resolve })
894
+ }),
895
+ },
896
+ {
897
+ id: 'bulk-start-review',
898
+ label: t('warranty_claims.bulk.startReview', 'Start review for selected'),
899
+ onExecute: async (selectedRows) => {
900
+ if (!selectedRows.length) return false
901
+ return runBulkStartReview(selectedRows)
902
+ },
903
+ },
904
+ {
905
+ id: 'bulk-cancel',
906
+ label: t('warranty_claims.bulk.cancel', 'Cancel selected'),
907
+ destructive: true,
908
+ onExecute: async (selectedRows) => {
909
+ if (!selectedRows.length) return false
910
+ const confirmed = await confirm({
911
+ title: t('warranty_claims.bulk.cancelTitle', 'Cancel selected claims?'),
912
+ variant: 'destructive',
913
+ })
914
+ if (!confirmed) return false
915
+ return runBulkCancel(selectedRows)
916
+ },
917
+ },
918
+ ], [confirm, runBulkCancel, runBulkStartReview, t])
919
+
920
+ const applyOverdueFilter = React.useCallback(() => {
921
+ setFilterValues((current) => {
922
+ const next: FilterValues = { ...current }
923
+ if (current.overdueOnly === true) delete next.overdueOnly
924
+ else next.overdueOnly = true
925
+ return next
926
+ })
927
+ setPage(1)
928
+ }, [])
929
+
930
+ const clearAllFilters = React.useCallback(() => {
931
+ setFilterValues({})
932
+ setPage(1)
933
+ }, [])
934
+
935
+ const handleSortingChange = React.useCallback((nextSorting: SortingState) => {
936
+ setSorting(nextSorting.length ? nextSorting : defaultSortingState())
937
+ setPage(1)
938
+ }, [])
939
+
940
+ const currentStatusFilter = valueAsStringArray(filterValues.status)
941
+ const myClaimsActive = Boolean(currentUserId) && toStringOrNull(filterValues.assigneeUserId) === currentUserId
942
+ const overdueActive = filterValues.overdueOnly === true
943
+ const slaAtRiskActive = filterValues.slaAtRiskOnly === true
944
+ const needsAttentionActive = filterValues.needsAttention === true
945
+ const attentionOnlyActive = filterValues.attentionOnly === true
946
+ const activeWorkspaceTab = myClaimsActive
947
+ ? 'mine'
948
+ : overdueActive || slaAtRiskActive || needsAttentionActive || attentionOnlyActive
949
+ ? 'attention'
950
+ : sameStringSet(currentStatusFilter, ['in_review'])
951
+ ? 'review'
952
+ : sameStringSet(currentStatusFilter, GOODS_FLOW_STATUSES)
953
+ ? 'goods'
954
+ : sameStringSet(currentStatusFilter, RESOLVED_STATUSES)
955
+ ? 'resolved'
956
+ : 'all'
957
+
958
+ const handleWorkspaceTabChange = React.useCallback((value: string) => {
959
+ if (value === 'mine' && currentUserId) {
960
+ setFilterValues({ assigneeUserId: currentUserId })
961
+ } else if (value === 'attention') {
962
+ setFilterValues({ attentionOnly: true })
963
+ } else if (value === 'review') {
964
+ setFilterValues({ status: ['in_review'] })
965
+ } else if (value === 'goods') {
966
+ setFilterValues({ status: GOODS_FLOW_STATUSES })
967
+ } else if (value === 'resolved') {
968
+ setFilterValues({ status: RESOLVED_STATUSES })
969
+ } else {
970
+ setFilterValues({})
971
+ }
972
+ setPage(1)
973
+ }, [currentUserId])
974
+
975
+ const columns = React.useMemo<ColumnDef<ClaimRow>[]>(() => {
976
+ const noValue = <span className="text-sm text-muted-foreground">{t('warranty_claims.common.noValue')}</span>
977
+ return [
978
+ {
979
+ accessorKey: 'claimNumber',
980
+ header: t('warranty_claims.list.column.claimNumber'),
981
+ meta: { alwaysVisible: true, maxWidth: '140px' },
982
+ cell: ({ row }) => (
983
+ <div className="flex items-center gap-2">
984
+ <Link href={`/backend/warranty_claims/${row.original.id}`} className="font-medium hover:underline">
985
+ {row.original.claimNumber ?? t('warranty_claims.list.unnumbered', 'Unnumbered claim')}
986
+ </Link>
987
+ {row.original.awaitingStaffReply ? (
988
+ <StatusBadge variant="warning" dot>
989
+ {t('warranty_claims.list.badge.customerReplied', 'Customer replied')}
990
+ </StatusBadge>
991
+ ) : null}
992
+ </div>
993
+ ),
994
+ },
995
+ {
996
+ accessorKey: 'claimType',
997
+ header: t('warranty_claims.list.column.claimType'),
998
+ cell: ({ row }) => {
999
+ const value = row.original.claimType
1000
+ return value ? (
1001
+ <StatusBadge variant="neutral">{t(`warranty_claims.claimType.${value}`)}</StatusBadge>
1002
+ ) : noValue
1003
+ },
1004
+ },
1005
+ {
1006
+ accessorKey: 'channel',
1007
+ header: t('warranty_claims.list.column.channel'),
1008
+ cell: ({ row }) => {
1009
+ const value = normalizeClaimChannel(row.original.channel)
1010
+ return value ? <StatusBadge variant="neutral">{t(`warranty_claims.channel.${value}`)}</StatusBadge> : noValue
1011
+ },
1012
+ },
1013
+ {
1014
+ accessorKey: 'status',
1015
+ header: t('warranty_claims.list.column.status'),
1016
+ meta: { maxWidth: '116px' },
1017
+ cell: ({ row }) => <ClaimStatusBadge status={row.original.status} />,
1018
+ },
1019
+ {
1020
+ accessorKey: 'priority',
1021
+ header: t('warranty_claims.list.column.priority'),
1022
+ meta: { maxWidth: '92px' },
1023
+ cell: ({ row }) => <ClaimPriorityBadge priority={row.original.priority} />,
1024
+ },
1025
+ {
1026
+ accessorKey: 'customerName',
1027
+ header: t('warranty_claims.list.column.customer'),
1028
+ meta: { truncate: true, maxWidth: '160px' },
1029
+ cell: ({ row }) => row.original.customerName ? <span>{row.original.customerName}</span> : noValue,
1030
+ },
1031
+ {
1032
+ accessorKey: 'orderId',
1033
+ header: t('warranty_claims.list.column.order'),
1034
+ meta: { truncate: true, maxWidth: '110px' },
1035
+ cell: ({ row }) => {
1036
+ const orderId = row.original.orderId
1037
+ if (!orderId) return noValue
1038
+ const label = row.original.orderNumber ?? t('warranty_claims.list.viewOrder', 'View order')
1039
+ return (
1040
+ <Link href={`/backend/sales/documents/${orderId}`} className="hover:underline">
1041
+ {label}
1042
+ </Link>
1043
+ )
1044
+ },
1045
+ },
1046
+ {
1047
+ accessorKey: 'slaDueAt',
1048
+ header: t('warranty_claims.list.column.slaDueAt'),
1049
+ meta: { maxWidth: '136px' },
1050
+ cell: ({ row }) => (
1051
+ <ClaimSlaIndicator
1052
+ slaDueAt={row.original.slaDueAt}
1053
+ slaPausedAt={row.original.slaPausedAt}
1054
+ submittedAt={row.original.submittedAt}
1055
+ status={row.original.status}
1056
+ atRiskThresholdPct={stats?.slaAtRiskThresholdPct}
1057
+ />
1058
+ ),
1059
+ },
1060
+ {
1061
+ accessorKey: 'assigneeUserId',
1062
+ header: t('warranty_claims.list.column.assignee'),
1063
+ meta: { truncate: true, maxWidth: '120px' },
1064
+ cell: ({ row }) => {
1065
+ const assigneeUserId = row.original.assigneeUserId
1066
+ if (!assigneeUserId) return noValue
1067
+ const displayName = assigneeDisplayNames[assigneeUserId]
1068
+ return displayName ? <span>{displayName}</span> : noValue
1069
+ },
1070
+ },
1071
+ {
1072
+ accessorKey: 'updatedAt',
1073
+ header: t('warranty_claims.list.column.updatedAt'),
1074
+ meta: { maxWidth: '96px' },
1075
+ cell: ({ row }) => (
1076
+ <span className="text-sm text-muted-foreground" title={formatDateTime(row.original.updatedAt) ?? undefined}>
1077
+ {formatRelativeTime(row.original.updatedAt, { locale }) ?? t('warranty_claims.common.noValue')}
1078
+ </span>
1079
+ ),
1080
+ },
1081
+ ]
1082
+ }, [assigneeDisplayNames, locale, stats?.slaAtRiskThresholdPct, t])
1083
+
1084
+ const assignInitialAssignee =
1085
+ assignDialog?.rows.length === 1
1086
+ ? assignDialog.rows[0].assigneeUserId ?? UNASSIGNED_ASSIGNEE_VALUE
1087
+ : UNASSIGNED_ASSIGNEE_VALUE
1088
+ const assignDialogTitle = assignDialog?.mode === 'bulk'
1089
+ ? t('warranty_claims.bulk.assignTitle', 'Assign selected claims')
1090
+ : t('warranty_claims.detail.actions.assign')
1091
+
1092
+ return (
1093
+ <Page>
1094
+ <PageBody>
1095
+ <WarrantyWorkspace
1096
+ title={t('warranty_claims.list.title')}
1097
+ contentClassName="[&>[data-component-handle]>div:first-child]:px-8 [&>[data-component-handle]>div:first-child]:py-4 [&_:has(>[data-slot=search-input-wrapper])]:lg:w-56"
1098
+ summary={(
1099
+ <ClaimsKpiStrip
1100
+ stats={stats}
1101
+ isLoading={statsLoading}
1102
+ hasError={statsError}
1103
+ attentionCount={tabCounts.attention}
1104
+ onOverdueClick={applyOverdueFilter}
1105
+ onOpenClaimsClick={clearAllFilters}
1106
+ />
1107
+ )}
1108
+ activeTab={activeWorkspaceTab}
1109
+ onTabChange={handleWorkspaceTabChange}
1110
+ tabs={[
1111
+ { id: 'all', label: t('warranty_claims.list.tabs.all', 'All'), count: tabCounts.all },
1112
+ { id: 'mine', label: t('warranty_claims.list.quickFilters.myClaims', 'My claims'), count: tabCounts.mine, disabled: !currentUserId },
1113
+ { id: 'attention', label: t('warranty_claims.list.tabs.needsAttention', 'Needs attention'), count: tabCounts.attention },
1114
+ { id: 'review', label: t('warranty_claims.list.quickFilters.inReview', 'In review'), count: tabCounts.review },
1115
+ { id: 'goods', label: t('warranty_claims.list.tabs.goodsFlow', 'Goods flow'), count: tabCounts.goods },
1116
+ { id: 'resolved', label: t('warranty_claims.list.tabs.resolved', 'Resolved'), count: tabCounts.resolved },
1117
+ ]}
1118
+ >
1119
+ <DataTable<ClaimRow>
1120
+ embedded
1121
+ stickyFirstColumn
1122
+ stickyActionsColumn
1123
+ refreshButton={{
1124
+ label: t('warranty_claims.list.actions.refresh'),
1125
+ onRefresh: reload,
1126
+ isRefreshing: loading,
1127
+ }}
1128
+ actions={(
1129
+ <Button asChild>
1130
+ <Link href="/backend/warranty_claims/create">
1131
+ <Plus className="size-4" aria-hidden />
1132
+ {t('warranty_claims.list.actions.new')}
1133
+ </Link>
1134
+ </Button>
1135
+ )}
1136
+ columns={columns}
1137
+ columnChooser={{ auto: true }}
1138
+ data={rows}
1139
+ exporter={exportConfig}
1140
+ searchValue={search}
1141
+ onSearchChange={(value) => {
1142
+ setSearch(value)
1143
+ setPage(1)
1144
+ }}
1145
+ searchPlaceholder={t('warranty_claims.list.searchPlaceholder', 'Search claim no., customer, order, serial, or SKU')}
1146
+ filters={filters}
1147
+ filterValues={filterValues}
1148
+ onFiltersApply={(values) => {
1149
+ setFilterValues((current) => {
1150
+ const next: FilterValues = { ...values }
1151
+ if (current.needsAttention === true) next.needsAttention = true
1152
+ if (current.attentionOnly === true) next.attentionOnly = true
1153
+ return next
1154
+ })
1155
+ setPage(1)
1156
+ }}
1157
+ onFiltersClear={clearAllFilters}
1158
+ perspective={{
1159
+ tableId: extensionPoints.hosts.claimsTable.tableId,
1160
+ initialState: {
1161
+ initialSettings: {
1162
+ columnVisibility: { claimType: false, channel: false },
1163
+ columnSizing: {
1164
+ claimNumber: 112,
1165
+ status: 112,
1166
+ priority: 88,
1167
+ customerName: 144,
1168
+ orderId: 96,
1169
+ slaDueAt: 136,
1170
+ assigneeUserId: 104,
1171
+ updatedAt: 88,
1172
+ },
1173
+ },
1174
+ },
1175
+ }}
1176
+ onRowClick={(row) => router.push(`/backend/warranty_claims/${row.id}`)}
1177
+ sortable
1178
+ manualSorting
1179
+ sorting={sorting}
1180
+ onSortingChange={handleSortingChange}
1181
+ isLoading={loading}
1182
+ bulkActions={bulkActions}
1183
+ rowActions={(row) => (
1184
+ <RowActions
1185
+ items={[
1186
+ {
1187
+ id: 'open',
1188
+ label: t('warranty_claims.list.actions.open'),
1189
+ onSelect: () => router.push(`/backend/warranty_claims/${row.id}`),
1190
+ },
1191
+ {
1192
+ id: 'open-new-tab',
1193
+ label: t('warranty_claims.list.actions.openInNewTab', 'Open in new tab'),
1194
+ onSelect: () => window.open(`/backend/warranty_claims/${row.id}`, '_blank', 'noopener'),
1195
+ },
1196
+ {
1197
+ id: 'assign',
1198
+ label: t('warranty_claims.list.actions.assign'),
1199
+ onSelect: () => setAssignDialog({ mode: 'single', rows: [row] }),
1200
+ },
1201
+ ...(Boolean(currentUserId) && row.assigneeUserId !== currentUserId
1202
+ ? [{
1203
+ id: 'assign-to-me',
1204
+ label: t('warranty_claims.list.actions.assignToMe', 'Assign to me'),
1205
+ onSelect: () => {
1206
+ void runClaimAction(
1207
+ row,
1208
+ 'assign-to-me',
1209
+ '/api/warranty_claims/assign',
1210
+ { id: row.id, assigneeUserId: currentUserId },
1211
+ 'warranty_claims.list.flash.assigned',
1212
+ )
1213
+ },
1214
+ }]
1215
+ : []),
1216
+ ...row.status === 'submitted'
1217
+ ? [{
1218
+ id: 'start-review',
1219
+ label: t('warranty_claims.list.actions.startReview', 'Start review'),
1220
+ onSelect: () => {
1221
+ void runClaimAction(
1222
+ row,
1223
+ 'start-review',
1224
+ '/api/warranty_claims/transition',
1225
+ { id: row.id, toStatus: 'in_review' },
1226
+ 'warranty_claims.list.flash.reviewStarted',
1227
+ 'Review started.',
1228
+ )
1229
+ },
1230
+ }]
1231
+ : [],
1232
+ ...CANCEL_BLOCKED_STATUSES.has(String(row.status ?? ''))
1233
+ ? []
1234
+ : [{
1235
+ id: 'cancel',
1236
+ label: t('warranty_claims.list.actions.cancel'),
1237
+ destructive: true,
1238
+ onSelect: () => {
1239
+ void handleCancel(row)
1240
+ },
1241
+ }],
1242
+ ]}
1243
+ />
1244
+ )}
1245
+ emptyState={(
1246
+ <ListEmptyState
1247
+ title={t('warranty_claims.list.empty.title')}
1248
+ description={t('warranty_claims.list.empty.description')}
1249
+ createHref="/backend/warranty_claims/create"
1250
+ createLabel={t('warranty_claims.list.actions.new')}
1251
+ />
1252
+ )}
1253
+ pagination={{
1254
+ page,
1255
+ pageSize: PAGE_SIZE,
1256
+ total,
1257
+ totalPages,
1258
+ onPageChange: setPage,
1259
+ }}
1260
+ />
1261
+ </WarrantyWorkspace>
1262
+ </PageBody>
1263
+ <Dialog open={assignDialog !== null} onOpenChange={(open) => { if (!open) closeAssignDialog(false) }}>
1264
+ <DialogContent className="max-w-lg">
1265
+ <DialogHeader>
1266
+ <DialogTitle>{assignDialogTitle}</DialogTitle>
1267
+ </DialogHeader>
1268
+ <CrudForm<AssignFormValues>
1269
+ embedded
1270
+ title={assignDialogTitle}
1271
+ fields={assignFields}
1272
+ initialValues={{ assigneeUserId: assignInitialAssignee }}
1273
+ submitLabel={t('warranty_claims.form.submit')}
1274
+ onSubmit={async (values) => {
1275
+ if (!assignDialog) return
1276
+ const assigneeUserId = normalizeAssigneeValue(values.assigneeUserId)
1277
+ if (assignDialog.mode === 'bulk') {
1278
+ const result = await runBulkAssign(assignDialog.rows, assigneeUserId)
1279
+ closeAssignDialog(result)
1280
+ return
1281
+ }
1282
+ const target = assignDialog.rows[0]
1283
+ if (!target) {
1284
+ closeAssignDialog(false)
1285
+ return
1286
+ }
1287
+ await runClaimAction(
1288
+ target,
1289
+ 'assign',
1290
+ '/api/warranty_claims/assign',
1291
+ { id: target.id, assigneeUserId },
1292
+ 'warranty_claims.list.flash.assigned',
1293
+ )
1294
+ closeAssignDialog({ ok: true, affectedCount: 1 })
1295
+ }}
1296
+ />
1297
+ </DialogContent>
1298
+ </Dialog>
1299
+ {ConfirmDialogElement}
1300
+ </Page>
1301
+ )
1302
+ }