@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,2577 @@
1
+ import { randomUUID } from "crypto";
2
+ import { LockMode } from "@mikro-orm/core";
3
+ import { z } from "zod";
4
+ import { registerCommand } from "@open-mercato/shared/lib/commands";
5
+ import { withAtomicFlush } from "@open-mercato/shared/lib/commands/flush";
6
+ import { emitCrudSideEffects, emitCrudUndoSideEffects } from "@open-mercato/shared/lib/commands/helpers";
7
+ import { CrudHttpError } from "@open-mercato/shared/lib/crud/errors";
8
+ import { invalidateCrudCache } from "@open-mercato/shared/lib/crud/cache";
9
+ import { findOneWithDecryption, findWithDecryption } from "@open-mercato/shared/lib/encryption/find";
10
+ import { readSafeDecryptedString } from "../lib/decryptionSafety.js";
11
+ import { E } from "../../../generated/entities.ids.generated.js";
12
+ import {
13
+ WarrantyClaim,
14
+ WarrantyClaimLine
15
+ } from "../data/entities.js";
16
+ import {
17
+ CLAIM_FULFILLMENT_UPDATE_FIELDS,
18
+ CLAIM_INTAKE_UPDATE_FIELDS,
19
+ claimCreateSchema,
20
+ claimUpdateSchema,
21
+ assignClaimInputSchema,
22
+ claimCreateCreditMemoSchema,
23
+ claimCreateReplacementOrderSchema,
24
+ claimCreateSalesReturnSchema,
25
+ claimSetReturnLabelSchema,
26
+ commentClaimInputSchema,
27
+ transitionClaimInputSchema,
28
+ vendorRecoveryInputSchema
29
+ } from "../data/validators.js";
30
+ import { createLogger } from "@open-mercato/shared/lib/logger";
31
+ import { isAssignableStaffUser } from "../lib/assigneeNames.js";
32
+ import { WarrantyClaimNumberGenerator } from "../services/claimNumberGenerator.js";
33
+ import { emitWarrantyClaimsEvent } from "../events.js";
34
+ import { assertTransition, canResolveWithLineStatuses, computeHeaderRollups } from "../lib/stateMachine.js";
35
+ import { assertOrderBelongsToCustomer } from "../lib/orderOwnership.js";
36
+ import { addBusinessMillis, businessMillisBetween } from "../lib/businessHours.js";
37
+ import { resolveEffectiveWarrantyClaimSettings } from "../lib/settings.js";
38
+ import { evaluateClaimRisk } from "../lib/risk.js";
39
+ import {
40
+ WARRANTY_CLAIM_RESOURCE_KIND,
41
+ appendClaimEvent,
42
+ enforceWarrantyClaimOptimisticLock,
43
+ ensureOrganizationScope,
44
+ ensureTenantScope,
45
+ extractUndoPayload,
46
+ requireScopedClaim
47
+ } from "./shared.js";
48
+ const claimCrudEvents = {
49
+ module: "warranty_claims",
50
+ entity: "claim",
51
+ persistent: true,
52
+ buildPayload: (ctx) => {
53
+ const claim = ctx.entity;
54
+ return {
55
+ id: ctx.identifiers.id,
56
+ organizationId: ctx.identifiers.organizationId,
57
+ tenantId: ctx.identifiers.tenantId,
58
+ claimType: claim?.claimType ?? null,
59
+ status: claim?.status ?? null
60
+ };
61
+ }
62
+ };
63
+ const claimDeleteSchema = z.object({
64
+ id: z.string().uuid(),
65
+ organizationId: z.string().uuid().optional(),
66
+ tenantId: z.string().uuid().optional()
67
+ }).strict();
68
+ const submitClaimSchema = claimDeleteSchema.extend({
69
+ actorCustomerId: z.string().uuid().optional()
70
+ });
71
+ const escalateClaimInputSchema = z.object({
72
+ id: z.string().uuid(),
73
+ organizationId: z.string().uuid(),
74
+ tenantId: z.string().uuid(),
75
+ toLevel: z.coerce.number().int().min(1).max(1e3),
76
+ reassignToUserId: z.string().uuid().optional()
77
+ }).strict();
78
+ const intakeStatuses = /* @__PURE__ */ new Set(["draft", "submitted", "in_review", "info_requested"]);
79
+ const fulfillmentStatuses = /* @__PURE__ */ new Set(["approved", "awaiting_return", "received", "inspecting"]);
80
+ const deletableStatuses = /* @__PURE__ */ new Set(["draft", "cancelled"]);
81
+ const preReceivedStatuses = /* @__PURE__ */ new Set([
82
+ "draft",
83
+ "submitted",
84
+ "in_review",
85
+ "info_requested",
86
+ "approved",
87
+ "awaiting_return"
88
+ ]);
89
+ function parseCommandInput(schema, rawInput) {
90
+ const result = schema.safeParse(rawInput ?? {});
91
+ if (!result.success) {
92
+ throw new CrudHttpError(400, { error: "warranty_claims.errors.invalidInput" });
93
+ }
94
+ return result.data;
95
+ }
96
+ function hasOwn(input, key) {
97
+ return Object.prototype.hasOwnProperty.call(input, key);
98
+ }
99
+ function resolveScope(ctx, input) {
100
+ const tenantId = input.tenantId ?? ctx.auth?.tenantId ?? null;
101
+ const organizationId = input.organizationId ?? ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null;
102
+ if (!tenantId) throw new CrudHttpError(400, { error: "[internal] tenant scope required for warranty claim command" });
103
+ if (!organizationId) throw new CrudHttpError(400, { error: "[internal] organization scope required for warranty claim command" });
104
+ ensureTenantScope(ctx, tenantId);
105
+ ensureOrganizationScope(ctx, organizationId);
106
+ return { tenantId, organizationId };
107
+ }
108
+ function toIso(value) {
109
+ if (!value) return null;
110
+ const date = value instanceof Date ? value : new Date(value);
111
+ return Number.isNaN(date.getTime()) ? null : date.toISOString();
112
+ }
113
+ function toDate(value) {
114
+ if (!value) return null;
115
+ const date = new Date(value);
116
+ return Number.isNaN(date.getTime()) ? null : date;
117
+ }
118
+ function toDateOnlyIso(value) {
119
+ const iso = toIso(value);
120
+ return iso ? iso.slice(0, 10) : null;
121
+ }
122
+ function toDateOnly(value) {
123
+ if (!value) return null;
124
+ const date = /* @__PURE__ */ new Date(`${value}T00:00:00.000Z`);
125
+ return Number.isNaN(date.getTime()) ? null : date;
126
+ }
127
+ function amountString(value, fallback = "0") {
128
+ if (value === null) return null;
129
+ if (value === void 0) return fallback;
130
+ const parsed = typeof value === "number" ? value : Number(value);
131
+ if (!Number.isFinite(parsed)) return fallback;
132
+ return String(parsed);
133
+ }
134
+ function nullableAmountString(value) {
135
+ if (value === void 0 || value === null) return null;
136
+ return amountString(value, "0");
137
+ }
138
+ function addMonths(date, months) {
139
+ const target = new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth() + months, 1));
140
+ const lastDay = new Date(Date.UTC(target.getUTCFullYear(), target.getUTCMonth() + 1, 0)).getUTCDate();
141
+ const copy = new Date(date.getTime());
142
+ copy.setUTCDate(1);
143
+ copy.setUTCFullYear(target.getUTCFullYear(), target.getUTCMonth(), Math.min(date.getUTCDate(), lastDay));
144
+ return copy;
145
+ }
146
+ function computeWarrantyDates(purchaseDate, warrantyMonths) {
147
+ if (!purchaseDate || warrantyMonths === null || warrantyMonths === void 0) {
148
+ return { warrantyExpiresAt: null, warrantyStatus: "unknown" };
149
+ }
150
+ const warrantyExpiresAt = addMonths(purchaseDate, warrantyMonths);
151
+ const warrantyStatus = warrantyExpiresAt.getTime() >= Date.now() ? "in_warranty" : "out_of_warranty";
152
+ return { warrantyExpiresAt, warrantyStatus };
153
+ }
154
+ function buildCreateEntitlementInput(input) {
155
+ const lines = input.lines ?? [];
156
+ const sourceLine = lines.find((line) => typeof line.serialNumber === "string" && line.serialNumber.trim().length > 0) ?? lines.find((line) => Boolean(line.productId)) ?? lines.find((line) => typeof line.sku === "string" && line.sku.trim().length > 0) ?? (input.orderId ? lines.find((line) => Boolean(line.purchaseDate)) : null) ?? null;
157
+ const serialNumber = sourceLine?.serialNumber?.trim() || null;
158
+ const orderId = input.orderId ?? null;
159
+ const productId = sourceLine?.productId ?? null;
160
+ const sku = sourceLine?.sku?.trim() || null;
161
+ if (!serialNumber && !orderId && !productId && !sku) return null;
162
+ return {
163
+ serialNumber,
164
+ orderId,
165
+ productId,
166
+ sku,
167
+ purchaseDate: toDateOnlyIso(sourceLine?.purchaseDate)
168
+ };
169
+ }
170
+ async function stampEntitlementSourceIfResolvable(ctx, em, claim, input, scope) {
171
+ const entitlementInput = buildCreateEntitlementInput(input);
172
+ if (!entitlementInput) return;
173
+ try {
174
+ const resolver = ctx.container.resolve("warrantyEntitlementResolver");
175
+ const result = await resolver.resolveEntitlement(entitlementInput, scope, em);
176
+ claim.entitlementSource = result.source ?? null;
177
+ } catch {
178
+ claim.entitlementSource = null;
179
+ }
180
+ }
181
+ function buildInitialLineData(claim, input, index) {
182
+ const purchaseDate = input.purchaseDate ?? null;
183
+ const warrantyMonths = input.warrantyMonths ?? null;
184
+ const computedWarranty = computeWarrantyDates(purchaseDate, warrantyMonths);
185
+ return {
186
+ id: randomUUID(),
187
+ claim,
188
+ organizationId: claim.organizationId,
189
+ tenantId: claim.tenantId,
190
+ lineNo: input.lineNo ?? index + 1,
191
+ productId: input.productId ?? null,
192
+ variantId: input.variantId ?? null,
193
+ sku: input.sku ?? null,
194
+ productName: input.productName ?? null,
195
+ orderLineId: input.orderLineId ?? null,
196
+ serialNumber: input.serialNumber ?? null,
197
+ lotNumber: input.lotNumber ?? null,
198
+ purchaseDate,
199
+ warrantyMonths,
200
+ warrantyExpiresAt: input.warrantyExpiresAt ?? computedWarranty.warrantyExpiresAt,
201
+ warrantyStatus: input.warrantyStatus ?? computedWarranty.warrantyStatus,
202
+ faultCode: input.faultCode ?? null,
203
+ faultDescription: input.faultDescription ?? null,
204
+ qtyClaimed: amountString(input.qtyClaimed, "1") ?? "1",
205
+ qtyApproved: nullableAmountString(input.qtyApproved),
206
+ qtyReceived: nullableAmountString(input.qtyReceived),
207
+ conditionOnReceipt: input.conditionOnReceipt ?? null,
208
+ inspectionNotes: input.inspectionNotes ?? null,
209
+ disposition: input.disposition ?? null,
210
+ vendorName: input.vendorName ?? null,
211
+ lineStatus: "pending",
212
+ creditAmount: nullableAmountString(input.creditAmount),
213
+ restockingFee: nullableAmountString(input.restockingFee),
214
+ coreChargeAmount: nullableAmountString(input.coreChargeAmount),
215
+ coreCreditAmount: nullableAmountString(input.coreCreditAmount),
216
+ createdAt: /* @__PURE__ */ new Date(),
217
+ updatedAt: /* @__PURE__ */ new Date()
218
+ };
219
+ }
220
+ function snapshotLine(line) {
221
+ return {
222
+ id: line.id,
223
+ lineNo: line.lineNo,
224
+ productId: line.productId ?? null,
225
+ variantId: line.variantId ?? null,
226
+ sku: line.sku ?? null,
227
+ productName: line.productName ?? null,
228
+ orderLineId: line.orderLineId ?? null,
229
+ serialNumber: line.serialNumber ?? null,
230
+ lotNumber: line.lotNumber ?? null,
231
+ purchaseDate: toDateOnlyIso(line.purchaseDate),
232
+ warrantyMonths: line.warrantyMonths ?? null,
233
+ warrantyExpiresAt: toDateOnlyIso(line.warrantyExpiresAt),
234
+ warrantyStatus: line.warrantyStatus,
235
+ faultCode: line.faultCode ?? null,
236
+ faultDescription: line.faultDescription ?? null,
237
+ qtyClaimed: line.qtyClaimed,
238
+ qtyApproved: line.qtyApproved ?? null,
239
+ qtyReceived: line.qtyReceived ?? null,
240
+ conditionOnReceipt: line.conditionOnReceipt ?? null,
241
+ conditionGrade: line.conditionGrade ?? null,
242
+ quarantineStatus: line.quarantineStatus,
243
+ inspectionNotes: line.inspectionNotes ?? null,
244
+ assessmentPayload: line.assessmentPayload ?? null,
245
+ disposition: line.disposition ?? null,
246
+ lineStatus: line.lineStatus,
247
+ creditAmount: line.creditAmount ?? null,
248
+ restockingFee: line.restockingFee ?? null,
249
+ coreChargeAmount: line.coreChargeAmount ?? null,
250
+ coreCreditAmount: line.coreCreditAmount ?? null,
251
+ vendorClaimLineId: line.vendorClaimLineId ?? null,
252
+ vendorName: line.vendorName ?? null,
253
+ deletedAt: toIso(line.deletedAt),
254
+ updatedAt: toIso(line.updatedAt)
255
+ };
256
+ }
257
+ function snapshotClaim(claim, lines) {
258
+ return {
259
+ id: claim.id,
260
+ organizationId: claim.organizationId,
261
+ tenantId: claim.tenantId,
262
+ claimNumber: claim.claimNumber,
263
+ claimType: claim.claimType,
264
+ status: claim.status,
265
+ channel: claim.channel,
266
+ priority: claim.priority,
267
+ customerId: claim.customerId ?? null,
268
+ customerName: claim.customerName ?? null,
269
+ externalRef: claim.externalRef ?? null,
270
+ intakeMessageRef: claim.intakeMessageRef ?? null,
271
+ entitlementSource: claim.entitlementSource ?? null,
272
+ contactEmail: claim.contactEmail ?? null,
273
+ returnLabelUrl: claim.returnLabelUrl ?? null,
274
+ returnTrackingNumber: claim.returnTrackingNumber ?? null,
275
+ returnCarrier: claim.returnCarrier ?? null,
276
+ vendorName: claim.vendorName ?? null,
277
+ vendorRef: claim.vendorRef ?? null,
278
+ orderId: claim.orderId ?? null,
279
+ orderNumber: claim.orderNumber ?? null,
280
+ awaitingStaffReply: claim.awaitingStaffReply === true,
281
+ salesReturnId: claim.salesReturnId ?? null,
282
+ replacementOrderId: claim.replacementOrderId ?? null,
283
+ creditMemoId: claim.creditMemoId ?? null,
284
+ sourceClaimId: claim.sourceClaimId ?? null,
285
+ advanceReplacement: claim.advanceReplacement,
286
+ advanceShippedAt: toIso(claim.advanceShippedAt),
287
+ reasonCode: claim.reasonCode ?? null,
288
+ rejectionReasonCode: claim.rejectionReasonCode ?? null,
289
+ resolutionSummary: claim.resolutionSummary ?? null,
290
+ notes: claim.notes ?? null,
291
+ currencyCode: claim.currencyCode ?? null,
292
+ totalClaimedAmount: claim.totalClaimedAmount ?? null,
293
+ totalApprovedAmount: claim.totalApprovedAmount ?? null,
294
+ totalRecoveredAmount: claim.totalRecoveredAmount ?? null,
295
+ escalationLevel: claim.escalationLevel ?? 0,
296
+ escalatedAt: toIso(claim.escalatedAt),
297
+ slaDueAt: toIso(claim.slaDueAt),
298
+ slaPausedAt: toIso(claim.slaPausedAt),
299
+ slaAtRiskNotifiedAt: toIso(claim.slaAtRiskNotifiedAt),
300
+ slaBreachedNotifiedAt: toIso(claim.slaBreachedNotifiedAt),
301
+ submittedAt: toIso(claim.submittedAt),
302
+ resolvedAt: toIso(claim.resolvedAt),
303
+ closedAt: toIso(claim.closedAt),
304
+ assigneeUserId: claim.assigneeUserId ?? null,
305
+ deletedAt: toIso(claim.deletedAt),
306
+ updatedAt: toIso(claim.updatedAt),
307
+ lines: lines.map(snapshotLine)
308
+ };
309
+ }
310
+ async function loadClaimSnapshot(em, claimId, scope) {
311
+ const claim = await findOneWithDecryption(em, WarrantyClaim, { id: claimId, tenantId: scope.tenantId, organizationId: scope.organizationId }, {}, scope);
312
+ if (!claim) return null;
313
+ const lines = await findWithDecryption(
314
+ em,
315
+ WarrantyClaimLine,
316
+ { claim: claim.id, tenantId: scope.tenantId, organizationId: scope.organizationId },
317
+ {},
318
+ scope
319
+ );
320
+ return snapshotClaim(claim, lines);
321
+ }
322
+ function snapshotValueChanged(before, after) {
323
+ return JSON.stringify(before) !== JSON.stringify(after);
324
+ }
325
+ function restoreClaimDelta(claim, before, after) {
326
+ if (before.claimNumber !== after.claimNumber) claim.claimNumber = before.claimNumber;
327
+ if (before.claimType !== after.claimType) claim.claimType = before.claimType;
328
+ if (before.status !== after.status) claim.status = before.status;
329
+ if (before.channel !== after.channel) claim.channel = before.channel;
330
+ if (before.priority !== after.priority) claim.priority = before.priority;
331
+ if (before.customerId !== after.customerId) claim.customerId = before.customerId;
332
+ if (before.customerName !== after.customerName) claim.customerName = before.customerName;
333
+ if (before.externalRef !== after.externalRef) claim.externalRef = before.externalRef;
334
+ if (before.intakeMessageRef !== after.intakeMessageRef) claim.intakeMessageRef = before.intakeMessageRef;
335
+ if (before.entitlementSource !== after.entitlementSource) claim.entitlementSource = before.entitlementSource;
336
+ if (before.contactEmail !== after.contactEmail) claim.contactEmail = before.contactEmail;
337
+ if (before.returnLabelUrl !== after.returnLabelUrl) claim.returnLabelUrl = before.returnLabelUrl;
338
+ if (before.returnTrackingNumber !== after.returnTrackingNumber) claim.returnTrackingNumber = before.returnTrackingNumber;
339
+ if (before.returnCarrier !== after.returnCarrier) claim.returnCarrier = before.returnCarrier;
340
+ if (before.vendorName !== after.vendorName) claim.vendorName = before.vendorName;
341
+ if (before.vendorRef !== after.vendorRef) claim.vendorRef = before.vendorRef;
342
+ if (before.orderId !== after.orderId) claim.orderId = before.orderId;
343
+ if (before.orderNumber !== after.orderNumber) claim.orderNumber = before.orderNumber;
344
+ if (before.awaitingStaffReply !== after.awaitingStaffReply) claim.awaitingStaffReply = before.awaitingStaffReply === true;
345
+ if (before.salesReturnId !== after.salesReturnId) claim.salesReturnId = before.salesReturnId;
346
+ if (before.replacementOrderId !== after.replacementOrderId) claim.replacementOrderId = before.replacementOrderId;
347
+ if (before.creditMemoId !== after.creditMemoId) claim.creditMemoId = before.creditMemoId;
348
+ if (before.sourceClaimId !== after.sourceClaimId) claim.sourceClaimId = before.sourceClaimId;
349
+ if (before.advanceReplacement !== after.advanceReplacement) claim.advanceReplacement = before.advanceReplacement;
350
+ if (before.advanceShippedAt !== after.advanceShippedAt) claim.advanceShippedAt = toDate(before.advanceShippedAt);
351
+ if (before.reasonCode !== after.reasonCode) claim.reasonCode = before.reasonCode;
352
+ if (before.rejectionReasonCode !== after.rejectionReasonCode) claim.rejectionReasonCode = before.rejectionReasonCode;
353
+ if (before.resolutionSummary !== after.resolutionSummary) claim.resolutionSummary = before.resolutionSummary;
354
+ if (before.notes !== after.notes) claim.notes = before.notes;
355
+ if (before.currencyCode !== after.currencyCode) claim.currencyCode = before.currencyCode;
356
+ if (before.totalClaimedAmount !== after.totalClaimedAmount) claim.totalClaimedAmount = before.totalClaimedAmount;
357
+ if (before.totalApprovedAmount !== after.totalApprovedAmount) claim.totalApprovedAmount = before.totalApprovedAmount;
358
+ if (before.totalRecoveredAmount !== after.totalRecoveredAmount) claim.totalRecoveredAmount = before.totalRecoveredAmount;
359
+ if (before.escalationLevel !== after.escalationLevel) claim.escalationLevel = before.escalationLevel;
360
+ if (before.escalatedAt !== after.escalatedAt) claim.escalatedAt = toDate(before.escalatedAt);
361
+ if (before.slaDueAt !== after.slaDueAt) claim.slaDueAt = toDate(before.slaDueAt);
362
+ if (before.slaPausedAt !== after.slaPausedAt) claim.slaPausedAt = toDate(before.slaPausedAt);
363
+ if (before.slaAtRiskNotifiedAt !== after.slaAtRiskNotifiedAt) claim.slaAtRiskNotifiedAt = toDate(before.slaAtRiskNotifiedAt);
364
+ if (before.slaBreachedNotifiedAt !== after.slaBreachedNotifiedAt) claim.slaBreachedNotifiedAt = toDate(before.slaBreachedNotifiedAt);
365
+ if (before.submittedAt !== after.submittedAt) claim.submittedAt = toDate(before.submittedAt);
366
+ if (before.resolvedAt !== after.resolvedAt) claim.resolvedAt = toDate(before.resolvedAt);
367
+ if (before.closedAt !== after.closedAt) claim.closedAt = toDate(before.closedAt);
368
+ if (before.assigneeUserId !== after.assigneeUserId) claim.assigneeUserId = before.assigneeUserId;
369
+ if (before.deletedAt !== after.deletedAt) claim.deletedAt = toDate(before.deletedAt);
370
+ claim.updatedAt = /* @__PURE__ */ new Date();
371
+ }
372
+ function restoreLineDelta(line, before, after) {
373
+ if (before.lineNo !== after.lineNo) line.lineNo = before.lineNo;
374
+ if (before.productId !== after.productId) line.productId = before.productId;
375
+ if (before.variantId !== after.variantId) line.variantId = before.variantId;
376
+ if (before.sku !== after.sku) line.sku = before.sku;
377
+ if (before.productName !== after.productName) line.productName = before.productName;
378
+ if (before.orderLineId !== after.orderLineId) line.orderLineId = before.orderLineId;
379
+ if (before.serialNumber !== after.serialNumber) line.serialNumber = before.serialNumber;
380
+ if (before.lotNumber !== after.lotNumber) line.lotNumber = before.lotNumber;
381
+ if (before.purchaseDate !== after.purchaseDate) line.purchaseDate = toDateOnly(before.purchaseDate);
382
+ if (before.warrantyMonths !== after.warrantyMonths) line.warrantyMonths = before.warrantyMonths;
383
+ if (before.warrantyExpiresAt !== after.warrantyExpiresAt) line.warrantyExpiresAt = toDateOnly(before.warrantyExpiresAt);
384
+ if (before.warrantyStatus !== after.warrantyStatus) line.warrantyStatus = before.warrantyStatus;
385
+ if (before.faultCode !== after.faultCode) line.faultCode = before.faultCode;
386
+ if (before.faultDescription !== after.faultDescription) line.faultDescription = before.faultDescription;
387
+ if (before.qtyClaimed !== after.qtyClaimed) line.qtyClaimed = before.qtyClaimed;
388
+ if (before.qtyApproved !== after.qtyApproved) line.qtyApproved = before.qtyApproved;
389
+ if (before.qtyReceived !== after.qtyReceived) line.qtyReceived = before.qtyReceived;
390
+ if (before.conditionOnReceipt !== after.conditionOnReceipt) line.conditionOnReceipt = before.conditionOnReceipt;
391
+ if (before.conditionGrade !== after.conditionGrade) line.conditionGrade = before.conditionGrade;
392
+ if (before.quarantineStatus !== after.quarantineStatus) line.quarantineStatus = before.quarantineStatus;
393
+ if (before.inspectionNotes !== after.inspectionNotes) line.inspectionNotes = before.inspectionNotes;
394
+ if (snapshotValueChanged(before.assessmentPayload, after.assessmentPayload)) line.assessmentPayload = before.assessmentPayload;
395
+ if (before.disposition !== after.disposition) line.disposition = before.disposition;
396
+ if (before.lineStatus !== after.lineStatus) line.lineStatus = before.lineStatus;
397
+ if (before.creditAmount !== after.creditAmount) line.creditAmount = before.creditAmount;
398
+ if (before.restockingFee !== after.restockingFee) line.restockingFee = before.restockingFee;
399
+ if (before.coreChargeAmount !== after.coreChargeAmount) line.coreChargeAmount = before.coreChargeAmount;
400
+ if (before.coreCreditAmount !== after.coreCreditAmount) line.coreCreditAmount = before.coreCreditAmount;
401
+ if (before.vendorClaimLineId !== after.vendorClaimLineId) line.vendorClaimLineId = before.vendorClaimLineId;
402
+ if (before.vendorName !== after.vendorName) line.vendorName = before.vendorName;
403
+ if (before.deletedAt !== after.deletedAt) line.deletedAt = toDate(before.deletedAt);
404
+ line.updatedAt = /* @__PURE__ */ new Date();
405
+ }
406
+ function throwClaimUndoConflict() {
407
+ throw new CrudHttpError(409, { error: "warranty_claims.errors.conflict" });
408
+ }
409
+ async function requireCurrentUndoSnapshot(em, after) {
410
+ const scope = { tenantId: after.tenantId, organizationId: after.organizationId };
411
+ const claim = await findOneWithDecryption(
412
+ em,
413
+ WarrantyClaim,
414
+ { id: after.id, tenantId: scope.tenantId, organizationId: scope.organizationId },
415
+ { lockMode: LockMode.PESSIMISTIC_WRITE },
416
+ scope
417
+ );
418
+ const expectedClaimUpdatedAt = toIso(after.updatedAt);
419
+ if (!claim || !expectedClaimUpdatedAt || toIso(claim.updatedAt) !== expectedClaimUpdatedAt) throwClaimUndoConflict();
420
+ const lines = await findWithDecryption(
421
+ em,
422
+ WarrantyClaimLine,
423
+ { claim: after.id, tenantId: scope.tenantId, organizationId: scope.organizationId },
424
+ { lockMode: LockMode.PESSIMISTIC_WRITE },
425
+ scope
426
+ );
427
+ const afterLines = Array.isArray(after.lines) ? after.lines : [];
428
+ if (lines.length !== afterLines.length) throwClaimUndoConflict();
429
+ const afterLinesById = new Map(afterLines.map((line) => [line.id, line]));
430
+ for (const line of lines) {
431
+ const expected = afterLinesById.get(line.id);
432
+ const expectedLineUpdatedAt = toIso(expected?.updatedAt);
433
+ if (!expectedLineUpdatedAt || toIso(line.updatedAt) !== expectedLineUpdatedAt) throwClaimUndoConflict();
434
+ }
435
+ return { claim, lines };
436
+ }
437
+ async function assertUndoSnapshotCurrent(ctx, after) {
438
+ const em = ctx.container.resolve("em").fork();
439
+ await em.transactional(async (tx) => {
440
+ await requireCurrentUndoSnapshot(tx, after);
441
+ });
442
+ }
443
+ async function restoreSnapshot(em, before, after) {
444
+ const { claim, lines } = await requireCurrentUndoSnapshot(em, after);
445
+ restoreClaimDelta(claim, before, after);
446
+ const beforeLinesById = new Map((Array.isArray(before.lines) ? before.lines : []).map((line) => [line.id, line]));
447
+ const afterLinesById = new Map((Array.isArray(after.lines) ? after.lines : []).map((line) => [line.id, line]));
448
+ for (const line of lines) {
449
+ const beforeLine = beforeLinesById.get(line.id);
450
+ const afterLine = afterLinesById.get(line.id);
451
+ if (!beforeLine || !afterLine) throwClaimUndoConflict();
452
+ if (snapshotValueChanged(beforeLine, afterLine)) restoreLineDelta(line, beforeLine, afterLine);
453
+ }
454
+ return claim;
455
+ }
456
+ function claimEventPayload(claim) {
457
+ return {
458
+ id: claim.id,
459
+ claimId: claim.id,
460
+ claimNumber: claim.claimNumber,
461
+ externalRef: claim.externalRef ?? null,
462
+ claimType: claim.claimType,
463
+ status: claim.status,
464
+ customerId: claim.customerId ?? null,
465
+ organizationId: claim.organizationId,
466
+ tenantId: claim.tenantId
467
+ };
468
+ }
469
+ function claimPersistentEmitOptions(claim) {
470
+ return {
471
+ persistent: true,
472
+ tenantId: claim.tenantId,
473
+ organizationId: claim.organizationId
474
+ };
475
+ }
476
+ async function emitClaimCrud(ctx, action, claim) {
477
+ const dataEngine = ctx.container.resolve("dataEngine");
478
+ await emitCrudSideEffects({
479
+ dataEngine,
480
+ action,
481
+ entity: claim,
482
+ identifiers: { id: claim.id, organizationId: claim.organizationId, tenantId: claim.tenantId },
483
+ indexer: { entityType: E.warranty_claims.warranty_claim },
484
+ events: claimCrudEvents
485
+ });
486
+ await invalidateCrudCache(
487
+ ctx.container,
488
+ "warranty_claims.claim",
489
+ { id: claim.id, organizationId: claim.organizationId, tenantId: claim.tenantId },
490
+ ctx.auth?.tenantId ?? null,
491
+ `warranty_claims.claim.${action}`
492
+ );
493
+ }
494
+ async function emitClaimUndoCrud(ctx, action, claim) {
495
+ const dataEngine = ctx.container.resolve("dataEngine");
496
+ await emitCrudUndoSideEffects({
497
+ dataEngine,
498
+ action,
499
+ entity: claim,
500
+ identifiers: { id: claim.id, organizationId: claim.organizationId, tenantId: claim.tenantId },
501
+ indexer: { entityType: E.warranty_claims.warranty_claim },
502
+ events: claimCrudEvents
503
+ });
504
+ await invalidateCrudCache(
505
+ ctx.container,
506
+ "warranty_claims.claim",
507
+ { id: claim.id, organizationId: claim.organizationId, tenantId: claim.tenantId },
508
+ ctx.auth?.tenantId ?? null,
509
+ `warranty_claims.claim.undo.${action}`
510
+ );
511
+ }
512
+ async function emitLineCrud(ctx, action, line) {
513
+ const dataEngine = ctx.container.resolve("dataEngine");
514
+ await emitCrudSideEffects({
515
+ dataEngine,
516
+ action,
517
+ entity: line,
518
+ identifiers: { id: line.id, organizationId: line.organizationId, tenantId: line.tenantId },
519
+ indexer: { entityType: E.warranty_claims.warranty_claim_line }
520
+ });
521
+ await invalidateCrudCache(
522
+ ctx.container,
523
+ "warranty_claims.claim_line",
524
+ { id: line.id, organizationId: line.organizationId, tenantId: line.tenantId },
525
+ ctx.auth?.tenantId ?? null,
526
+ `warranty_claims.claim_line.${action}`
527
+ );
528
+ }
529
+ async function emitLineUndoCrud(ctx, action, line) {
530
+ const dataEngine = ctx.container.resolve("dataEngine");
531
+ await emitCrudUndoSideEffects({
532
+ dataEngine,
533
+ action,
534
+ entity: line,
535
+ identifiers: { id: line.id, organizationId: line.organizationId, tenantId: line.tenantId },
536
+ indexer: { entityType: E.warranty_claims.warranty_claim_line }
537
+ });
538
+ await invalidateCrudCache(
539
+ ctx.container,
540
+ "warranty_claims.claim_line",
541
+ { id: line.id, organizationId: line.organizationId, tenantId: line.tenantId },
542
+ ctx.auth?.tenantId ?? null,
543
+ `warranty_claims.claim_line.undo.${action}`
544
+ );
545
+ }
546
+ function resolveClaimNumberGenerator(ctx, em) {
547
+ try {
548
+ return ctx.container.resolve("warrantyClaimNumberGenerator");
549
+ } catch {
550
+ return new WarrantyClaimNumberGenerator(em);
551
+ }
552
+ }
553
+ function readCustomerName(row) {
554
+ const candidates = [row.display_name, row.displayName, row.name, row.label];
555
+ for (const candidate of candidates) {
556
+ if (typeof candidate === "string" && candidate.trim().length > 0) return candidate.trim();
557
+ }
558
+ return null;
559
+ }
560
+ function readString(row, key) {
561
+ const value = row[key];
562
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
563
+ }
564
+ function isMissingReferenceTableError(err) {
565
+ if (typeof err !== "object" || err === null) return false;
566
+ const candidate = err;
567
+ return candidate.code === "42P01" || typeof candidate.message === "string" && candidate.message.includes("does not exist");
568
+ }
569
+ async function querySalesReferenceRow(ctx, table, scope, id, select) {
570
+ const em = ctx.container.resolve("em").fork();
571
+ const db = em.getKysely();
572
+ try {
573
+ const row = await db.selectFrom(table).select(select).where("id", "=", id).where("tenant_id", "=", scope.tenantId).where("organization_id", "=", scope.organizationId).where("deleted_at", "is", null).executeTakeFirst();
574
+ return row ?? null;
575
+ } catch (err) {
576
+ if (isMissingReferenceTableError(err)) return void 0;
577
+ throw err;
578
+ }
579
+ }
580
+ async function querySalesOrderLineReferences(ctx, scope, ids, select = ["id", "order_id"]) {
581
+ if (ids.length === 0) return [];
582
+ const em = ctx.container.resolve("em").fork();
583
+ const db = em.getKysely();
584
+ try {
585
+ const rows = await db.selectFrom("sales_order_lines").select(select).where("id", "in", ids).where("tenant_id", "=", scope.tenantId).where("organization_id", "=", scope.organizationId).where("deleted_at", "is", null).execute();
586
+ return rows;
587
+ } catch (err) {
588
+ if (isMissingReferenceTableError(err)) return void 0;
589
+ throw err;
590
+ }
591
+ }
592
+ const CLAIMED_QUANTITY_SCALE = 10000n;
593
+ const TAX_RATE_FORMULA_SCALE = 1000000n;
594
+ function claimedQuantityUnits(value) {
595
+ if (value === null || value === void 0) return null;
596
+ let normalized = String(value).trim();
597
+ if (/e/i.test(normalized)) {
598
+ const numeric = Number(normalized);
599
+ if (!Number.isFinite(numeric)) return null;
600
+ normalized = numeric.toFixed(4);
601
+ }
602
+ const match = /^([+-]?)(\d+)(?:\.(\d+))?$/.exec(normalized);
603
+ if (!match) return null;
604
+ const [, sign, whole, fraction = ""] = match;
605
+ const scaledFraction = fraction.slice(0, 4).padEnd(4, "0");
606
+ let units = BigInt(whole) * CLAIMED_QUANTITY_SCALE + BigInt(scaledFraction);
607
+ if (fraction.length > 4 && fraction[4] >= "5") units += 1n;
608
+ return sign === "-" ? -units : units;
609
+ }
610
+ function roundHalfUpDivision(numerator, denominator) {
611
+ if (denominator <= 0n) throw new Error("[internal] Positive denominator required");
612
+ if (numerator < 0n) return -roundHalfUpDivision(-numerator, denominator);
613
+ return (numerator + denominator / 2n) / denominator;
614
+ }
615
+ function formatScaledUnits(units) {
616
+ const sign = units < 0n ? "-" : "";
617
+ const absolute = units < 0n ? -units : units;
618
+ const whole = absolute / CLAIMED_QUANTITY_SCALE;
619
+ const fraction = String(absolute % CLAIMED_QUANTITY_SCALE).padStart(4, "0");
620
+ return `${sign}${whole}.${fraction}`;
621
+ }
622
+ function netUnitsFromGross(grossUnits, taxRateUnits) {
623
+ const denominator = TAX_RATE_FORMULA_SCALE + taxRateUnits;
624
+ return roundHalfUpDivision(grossUnits * TAX_RATE_FORMULA_SCALE, denominator);
625
+ }
626
+ async function assertClaimedQtyWithinSold(ctx, scope, claimIdOrPendingLines, candidateLine) {
627
+ const orderLineId = candidateLine.orderLineId ?? null;
628
+ if (!orderLineId || !resolveOptionalEntityId("sales", "sales_order_line")) return;
629
+ const salesLine = await querySalesReferenceRow(ctx, "sales_order_lines", scope, orderLineId, ["id", "quantity"]);
630
+ const soldQuantity = claimedQuantityUnits(salesLine?.quantity);
631
+ if (salesLine === void 0 || salesLine === null || soldQuantity === null) return;
632
+ let lines;
633
+ if (typeof claimIdOrPendingLines === "string") {
634
+ const em = ctx.container.resolve("em").fork();
635
+ lines = await findWithDecryption(
636
+ em,
637
+ WarrantyClaimLine,
638
+ {
639
+ claim: claimIdOrPendingLines,
640
+ orderLineId,
641
+ tenantId: scope.tenantId,
642
+ organizationId: scope.organizationId,
643
+ deletedAt: null
644
+ },
645
+ {},
646
+ scope
647
+ );
648
+ } else {
649
+ lines = claimIdOrPendingLines;
650
+ }
651
+ let claimedQuantity = claimedQuantityUnits(candidateLine.qtyClaimed) ?? 0n;
652
+ for (const line of lines) {
653
+ if (line.deletedAt || (line.orderLineId ?? null) !== orderLineId) continue;
654
+ if (candidateLine.id != null && line.id === candidateLine.id) continue;
655
+ claimedQuantity += claimedQuantityUnits(line.qtyClaimed) ?? 0n;
656
+ }
657
+ if (claimedQuantity > soldQuantity) {
658
+ throw new CrudHttpError(400, { error: "warranty_claims.errors.qtyExceedsOrdered" });
659
+ }
660
+ }
661
+ async function assertPendingClaimQuantitiesWithinSold(em, scope, pendingLinesByOrderLine, options) {
662
+ const db = em.getKysely();
663
+ const orderLineIds = Array.from(pendingLinesByOrderLine.keys()).sort((left, right) => left.localeCompare(right));
664
+ for (const orderLineId of orderLineIds) {
665
+ let soldRow;
666
+ try {
667
+ soldRow = await db.selectFrom("sales_order_lines").select(["id", "quantity"]).where("id", "=", orderLineId).where("tenant_id", "=", scope.tenantId).where("organization_id", "=", scope.organizationId).where("deleted_at", "is", null).forUpdate().executeTakeFirst();
668
+ } catch (err) {
669
+ if (isMissingReferenceTableError(err)) return;
670
+ throw err;
671
+ }
672
+ const soldQuantity = claimedQuantityUnits(soldRow?.quantity);
673
+ if (!soldRow || soldQuantity === null) continue;
674
+ const persistedRows = await db.selectFrom("warranty_claim_lines").innerJoin("warranty_claims", "warranty_claims.id", "warranty_claim_lines.claim_id").select(["warranty_claim_lines.id", "warranty_claim_lines.qty_claimed"]).where("warranty_claim_lines.order_line_id", "=", orderLineId).where("warranty_claim_lines.tenant_id", "=", scope.tenantId).where("warranty_claim_lines.organization_id", "=", scope.organizationId).where("warranty_claim_lines.deleted_at", "is", null).where("warranty_claim_lines.line_status", "!=", "rejected").where("warranty_claims.tenant_id", "=", scope.tenantId).where("warranty_claims.organization_id", "=", scope.organizationId).where("warranty_claims.id", "=", options.currentClaimId).where("warranty_claims.deleted_at", "is", null).where("warranty_claims.status", "!=", "cancelled").execute();
675
+ let claimedQuantity = 0n;
676
+ for (const row of persistedRows) {
677
+ if (options.excludedLineIds?.has(row.id)) continue;
678
+ claimedQuantity += claimedQuantityUnits(row.qty_claimed) ?? 0n;
679
+ }
680
+ for (const line of pendingLinesByOrderLine.get(orderLineId) ?? []) {
681
+ claimedQuantity += claimedQuantityUnits(line.qtyClaimed) ?? 0n;
682
+ }
683
+ if (claimedQuantity > soldQuantity) {
684
+ throw new CrudHttpError(400, { error: "warranty_claims.errors.qtyExceedsOrdered" });
685
+ }
686
+ }
687
+ }
688
+ function isMissingCustomerTableError(err) {
689
+ if (typeof err !== "object" || err === null) return false;
690
+ const candidate = err;
691
+ return candidate.code === "42P01" || typeof candidate.message === "string" && candidate.message.includes("customer_entities") && candidate.message.includes("does not exist");
692
+ }
693
+ async function assertCustomerExists(ctx, customerId, scope) {
694
+ const em = ctx.container.resolve("em").fork();
695
+ const db = em.getKysely();
696
+ let row;
697
+ try {
698
+ row = await db.selectFrom("customer_entities").select("id").where("id", "=", customerId).where("tenant_id", "=", scope.tenantId).where("organization_id", "=", scope.organizationId).where("deleted_at", "is", null).executeTakeFirst();
699
+ } catch (err) {
700
+ if (isMissingCustomerTableError(err)) return;
701
+ throw err;
702
+ }
703
+ if (!row) throw new CrudHttpError(400, { error: "warranty_claims.errors.invalidInput" });
704
+ }
705
+ async function resolveCustomerName(ctx, customerId, scope, fallback, options = { strict: false }) {
706
+ if (!customerId) return fallback ?? null;
707
+ if (options.strict) await assertCustomerExists(ctx, customerId, scope);
708
+ const em = ctx.container.resolve("em").fork();
709
+ const db = em.getKysely();
710
+ let customer;
711
+ try {
712
+ customer = await db.selectFrom("customer_entities").select(["id", "display_name"]).where("id", "=", customerId).where("tenant_id", "=", scope.tenantId).where("organization_id", "=", scope.organizationId).where("deleted_at", "is", null).executeTakeFirst();
713
+ } catch (err) {
714
+ if (!isMissingCustomerTableError(err)) throw err;
715
+ return fallback ?? null;
716
+ }
717
+ if (!customer) return fallback ?? null;
718
+ let encryption = null;
719
+ try {
720
+ encryption = ctx.container.resolve("tenantEncryptionService");
721
+ } catch {
722
+ encryption = null;
723
+ }
724
+ if (encryption) {
725
+ try {
726
+ customer = await encryption.decryptEntityPayload("customers:customer_entity", customer, scope.tenantId, scope.organizationId);
727
+ } catch {
728
+ return fallback ?? null;
729
+ }
730
+ }
731
+ return readSafeDecryptedString(readCustomerName(customer)) ?? fallback ?? null;
732
+ }
733
+ async function resolveOrderNumber(ctx, orderId, scope, fallback) {
734
+ if (!orderId) return null;
735
+ const row = await querySalesReferenceRow(ctx, "sales_orders", scope, orderId, ["id", "order_number"]);
736
+ if (!row) return fallback ?? null;
737
+ return readString(row, "order_number") ?? fallback ?? null;
738
+ }
739
+ async function resolvePortalRecipientUserIds(ctx, claim) {
740
+ if (!claim.customerId) return [];
741
+ try {
742
+ const em = ctx.container.resolve("em").fork();
743
+ const db = em.getKysely();
744
+ const rows = await db.selectFrom("customer_users").select("id").where("tenant_id", "=", claim.tenantId).where("organization_id", "=", claim.organizationId).where("customer_entity_id", "=", claim.customerId).where("is_active", "=", true).where("deleted_at", "is", null).limit(100).execute();
745
+ return rows.map((row) => row.id);
746
+ } catch {
747
+ return [];
748
+ }
749
+ }
750
+ async function emitClaimStatusChanged(ctx, claim, fromStatus, toStatus) {
751
+ const payload = {
752
+ ...claimEventPayload(claim),
753
+ fromStatus,
754
+ toStatus
755
+ };
756
+ await emitWarrantyClaimsEvent("warranty_claims.claim.status_changed", payload, claimPersistentEmitOptions(claim));
757
+ const recipientUserIds = await resolvePortalRecipientUserIds(ctx, claim);
758
+ if (recipientUserIds.length === 0) return;
759
+ await emitWarrantyClaimsEvent("warranty_claims.claim.portal_status_changed", {
760
+ ...payload,
761
+ recipientUserIds
762
+ }, claimPersistentEmitOptions(claim));
763
+ }
764
+ async function emitCustomerVisibleCommentAdded(ctx, claim, input) {
765
+ const recipientUserIds = await resolvePortalRecipientUserIds(ctx, claim);
766
+ if (recipientUserIds.length === 0) return;
767
+ await emitWarrantyClaimsEvent("warranty_claims.claim.comment_added", {
768
+ ...claimEventPayload(claim),
769
+ visibility: input.visibility,
770
+ actorCustomerId: input.actorCustomerId ?? null,
771
+ recipientUserIds
772
+ }, claimPersistentEmitOptions(claim));
773
+ }
774
+ function applySlaPause(em, claim, fromStatus, toStatus, now, effectiveSettings) {
775
+ if (fromStatus === toStatus || toStatus !== "info_requested" || !effectiveSettings.slaPauseOnInfoRequested) return;
776
+ claim.slaPausedAt = now;
777
+ appendClaimEvent(em, claim, "system", {
778
+ visibility: "customer",
779
+ payload: { action: "sla_paused" },
780
+ actorUserId: null
781
+ });
782
+ }
783
+ function applySlaResume(em, claim, fromStatus, toStatus, now, effectiveSettings) {
784
+ if (fromStatus !== "info_requested" || toStatus === "info_requested" || !claim.slaPausedAt) return;
785
+ if (claim.slaDueAt && claim.slaDueAt.getTime() > claim.slaPausedAt.getTime()) {
786
+ const remainingBusinessMillis = businessMillisBetween(claim.slaPausedAt, claim.slaDueAt, effectiveSettings.businessHours);
787
+ claim.slaDueAt = addBusinessMillis(now, remainingBusinessMillis, effectiveSettings.businessHours);
788
+ }
789
+ claim.slaPausedAt = null;
790
+ claim.slaAtRiskNotifiedAt = null;
791
+ claim.slaBreachedNotifiedAt = null;
792
+ appendClaimEvent(em, claim, "system", {
793
+ visibility: "customer",
794
+ payload: { action: "sla_resumed" },
795
+ actorUserId: null
796
+ });
797
+ }
798
+ function resolveOptionalEntityId(moduleId, entity) {
799
+ const registry = E;
800
+ const value = registry[moduleId]?.[entity];
801
+ return typeof value === "string" ? value : null;
802
+ }
803
+ async function lookupSalesReference(ctx, entityId, table, scope, id, select) {
804
+ if (!entityId) return "unknown";
805
+ const row = await querySalesReferenceRow(ctx, table, scope, id, select);
806
+ if (row === void 0) return "unknown";
807
+ return row ?? "missing";
808
+ }
809
+ function assertValidSalesReference(result) {
810
+ if (result === "missing") {
811
+ throw new CrudHttpError(400, { error: "warranty_claims.errors.invalidReference" });
812
+ }
813
+ }
814
+ async function validateClaimReferences(ctx, scope, input) {
815
+ if (input.orderId) {
816
+ const result = await lookupSalesReference(ctx, resolveOptionalEntityId("sales", "sales_order"), "sales_orders", scope, input.orderId, ["id"]);
817
+ assertValidSalesReference(result);
818
+ }
819
+ if (input.replacementOrderId) {
820
+ const result = await lookupSalesReference(ctx, resolveOptionalEntityId("sales", "sales_order"), "sales_orders", scope, input.replacementOrderId, ["id"]);
821
+ assertValidSalesReference(result);
822
+ }
823
+ if (input.salesReturnId) {
824
+ const result = await lookupSalesReference(ctx, resolveOptionalEntityId("sales", "sales_return"), "sales_returns", scope, input.salesReturnId, ["id"]);
825
+ assertValidSalesReference(result);
826
+ }
827
+ if (input.creditMemoId) {
828
+ const result = await lookupSalesReference(ctx, resolveOptionalEntityId("sales", "sales_credit_memo"), "sales_credit_memos", scope, input.creditMemoId, ["id"]);
829
+ assertValidSalesReference(result);
830
+ }
831
+ const lineRefs = input.lineOrderRefs ?? [];
832
+ if (lineRefs.length === 0 || !resolveOptionalEntityId("sales", "sales_order_line")) return;
833
+ const lineRows = await querySalesOrderLineReferences(
834
+ ctx,
835
+ scope,
836
+ Array.from(new Set(lineRefs.map((lineRef) => lineRef.orderLineId)))
837
+ );
838
+ if (lineRows === void 0) return;
839
+ const lineRowsById = new Map(lineRows.map((row) => [readString(row, "id"), row]));
840
+ for (const lineRef of lineRefs) {
841
+ const result = lineRowsById.get(lineRef.orderLineId);
842
+ if (!result) {
843
+ throw new CrudHttpError(400, { error: "warranty_claims.errors.invalidReference" });
844
+ }
845
+ if (lineRef.orderId && readString(result, "order_id") !== lineRef.orderId) {
846
+ throw new CrudHttpError(400, { error: "warranty_claims.errors.invalidReference" });
847
+ }
848
+ }
849
+ }
850
+ async function validateAssigneeUser(ctx, scope, assigneeUserId) {
851
+ if (!assigneeUserId) return;
852
+ const assignable = await isAssignableStaffUser({
853
+ container: ctx.container,
854
+ tenantId: scope.tenantId,
855
+ organizationId: scope.organizationId
856
+ }, assigneeUserId);
857
+ if (!assignable) throw new CrudHttpError(400, { error: "warranty_claims.errors.invalidAssignee" });
858
+ }
859
+ function applyClaimAssignment(em, claim, assigneeUserId, actorUserId, now) {
860
+ claim.assigneeUserId = assigneeUserId;
861
+ claim.updatedAt = now;
862
+ appendClaimEvent(em, claim, "assignment", {
863
+ visibility: "internal",
864
+ payload: { assigneeUserId: claim.assigneeUserId },
865
+ actorUserId
866
+ });
867
+ }
868
+ function allowedUpdateFields(status) {
869
+ if (intakeStatuses.has(status)) return new Set(CLAIM_INTAKE_UPDATE_FIELDS);
870
+ if (fulfillmentStatuses.has(status)) return new Set(CLAIM_FULFILLMENT_UPDATE_FIELDS);
871
+ return /* @__PURE__ */ new Set();
872
+ }
873
+ function assertClaimUpdateFields(claim, input) {
874
+ const keys = [...CLAIM_INTAKE_UPDATE_FIELDS, ...CLAIM_FULFILLMENT_UPDATE_FIELDS];
875
+ const requested = keys.filter((key) => hasOwn(input, key));
876
+ if (!requested.length) return;
877
+ const allowed = allowedUpdateFields(claim.status);
878
+ const disallowed = requested.filter((key) => !allowed.has(key));
879
+ if (disallowed.length > 0) {
880
+ throw new CrudHttpError(400, { error: "warranty_claims.errors.fieldLocked" });
881
+ }
882
+ }
883
+ function applyClaimUpdate(claim, input, customerName, orderNumber) {
884
+ if (hasOwn(input, "customerId")) claim.customerId = input.customerId ?? null;
885
+ if (hasOwn(input, "customerName")) claim.customerName = customerName;
886
+ if (hasOwn(input, "orderId")) {
887
+ claim.orderId = input.orderId ?? null;
888
+ claim.orderNumber = orderNumber;
889
+ }
890
+ if (hasOwn(input, "reasonCode")) claim.reasonCode = input.reasonCode ?? null;
891
+ if (hasOwn(input, "priority") && input.priority) claim.priority = input.priority;
892
+ if (hasOwn(input, "notes")) claim.notes = input.notes ?? null;
893
+ if (hasOwn(input, "advanceReplacement")) claim.advanceReplacement = input.advanceReplacement ?? false;
894
+ if (hasOwn(input, "replacementOrderId")) claim.replacementOrderId = input.replacementOrderId ?? null;
895
+ if (hasOwn(input, "advanceShippedAt")) claim.advanceShippedAt = input.advanceShippedAt ?? null;
896
+ if (hasOwn(input, "salesReturnId")) claim.salesReturnId = input.salesReturnId ?? null;
897
+ if (hasOwn(input, "creditMemoId")) claim.creditMemoId = input.creditMemoId ?? null;
898
+ if (hasOwn(input, "vendorName")) claim.vendorName = input.vendorName ?? null;
899
+ if (hasOwn(input, "vendorRef")) claim.vendorRef = input.vendorRef ?? null;
900
+ if (hasOwn(input, "resolutionSummary")) claim.resolutionSummary = input.resolutionSummary ?? null;
901
+ claim.updatedAt = /* @__PURE__ */ new Date();
902
+ }
903
+ async function recomputeClaimRollups(em, claim) {
904
+ const lines = await findWithDecryption(
905
+ em,
906
+ WarrantyClaimLine,
907
+ {
908
+ claim: claim.id,
909
+ tenantId: claim.tenantId,
910
+ organizationId: claim.organizationId,
911
+ deletedAt: null
912
+ },
913
+ {},
914
+ { tenantId: claim.tenantId, organizationId: claim.organizationId }
915
+ );
916
+ const totals = computeHeaderRollups(lines, { claimType: claim.claimType });
917
+ claim.totalClaimedAmount = String(totals.totalClaimedAmount);
918
+ claim.totalApprovedAmount = String(totals.totalApprovedAmount);
919
+ }
920
+ function assertCanResolve(lines) {
921
+ if (!canResolveWithLineStatuses(lines)) {
922
+ throw new CrudHttpError(400, { error: "warranty_claims.errors.invalidTransition" });
923
+ }
924
+ }
925
+ function assertVendorRecoveryLines(lines, requestedLineIds) {
926
+ if (lines.length !== requestedLineIds.length) {
927
+ throw new CrudHttpError(400, { error: "warranty_claims.errors.vendorRecoveryNeedsResolvedLines" });
928
+ }
929
+ for (const line of lines) {
930
+ if (line.lineStatus !== "resolved" || line.vendorClaimLineId) {
931
+ throw new CrudHttpError(400, { error: "warranty_claims.errors.vendorRecoveryNeedsResolvedLines" });
932
+ }
933
+ }
934
+ }
935
+ function buildClaimLog(actionLabel, resourceId, snapshots) {
936
+ const after = snapshots.after;
937
+ const before = snapshots.before;
938
+ return {
939
+ actionLabel,
940
+ resourceKind: WARRANTY_CLAIM_RESOURCE_KIND,
941
+ resourceId,
942
+ tenantId: after?.tenantId ?? before?.tenantId ?? null,
943
+ organizationId: after?.organizationId ?? before?.organizationId ?? null,
944
+ snapshotBefore: before ?? null,
945
+ snapshotAfter: after ?? null,
946
+ payload: { undo: { before: before ?? null, after: after ?? null } }
947
+ };
948
+ }
949
+ const createClaimCommand = {
950
+ id: "warranty_claims.claim.create",
951
+ isUndoable: true,
952
+ async execute(rawInput, ctx) {
953
+ const input = parseCommandInput(claimCreateSchema, rawInput);
954
+ const scope = resolveScope(ctx, input);
955
+ const em = ctx.container.resolve("em").fork();
956
+ await validateClaimReferences(ctx, scope, {
957
+ orderId: input.orderId ?? null,
958
+ salesReturnId: input.salesReturnId ?? null,
959
+ replacementOrderId: input.replacementOrderId ?? null,
960
+ creditMemoId: null,
961
+ lineOrderRefs: (input.lines ?? []).filter((line) => typeof line.orderLineId === "string").map((line) => ({ orderLineId: line.orderLineId, orderId: input.orderId ?? null }))
962
+ });
963
+ await assertOrderBelongsToCustomer(em, scope, input.orderId ?? null, input.customerId ?? null);
964
+ const pendingLines = (input.lines ?? []).map((line) => ({
965
+ orderLineId: line.orderLineId ?? null,
966
+ qtyClaimed: amountString(line.qtyClaimed, "1") ?? "1"
967
+ }));
968
+ const pendingLinesByOrderLine = /* @__PURE__ */ new Map();
969
+ for (const line of pendingLines) {
970
+ if (!line.orderLineId) continue;
971
+ const grouped = pendingLinesByOrderLine.get(line.orderLineId) ?? [];
972
+ grouped.push(line);
973
+ pendingLinesByOrderLine.set(line.orderLineId, grouped);
974
+ }
975
+ const numberGenerator = resolveClaimNumberGenerator(ctx, em);
976
+ const generated = await numberGenerator.generate({
977
+ claimType: input.claimType,
978
+ tenantId: scope.tenantId,
979
+ organizationId: scope.organizationId
980
+ });
981
+ const customerName = await resolveCustomerName(ctx, input.customerId, scope, input.customerName, { strict: true });
982
+ const orderNumber = input.orderId ? await resolveOrderNumber(ctx, input.orderId, scope, input.orderNumber ?? null) : input.orderNumber ?? null;
983
+ const claimId = randomUUID();
984
+ let claim;
985
+ let createdLines = [];
986
+ await withAtomicFlush(em, [
987
+ async () => {
988
+ await assertPendingClaimQuantitiesWithinSold(em, scope, pendingLinesByOrderLine, { currentClaimId: claimId });
989
+ claim = em.create(WarrantyClaim, {
990
+ id: claimId,
991
+ organizationId: scope.organizationId,
992
+ tenantId: scope.tenantId,
993
+ claimNumber: generated.number,
994
+ claimType: input.claimType,
995
+ status: "draft",
996
+ channel: input.channel ?? "staff",
997
+ priority: input.priority ?? "normal",
998
+ customerId: input.customerId ?? null,
999
+ customerName,
1000
+ externalRef: input.externalRef ?? null,
1001
+ intakeMessageRef: input.intakeMessageRef ?? null,
1002
+ contactEmail: input.contactEmail ?? null,
1003
+ vendorName: input.vendorName ?? null,
1004
+ vendorRef: input.vendorRef ?? null,
1005
+ orderId: input.orderId ?? null,
1006
+ orderNumber,
1007
+ salesReturnId: input.salesReturnId ?? null,
1008
+ replacementOrderId: input.replacementOrderId ?? null,
1009
+ creditMemoId: null,
1010
+ sourceClaimId: null,
1011
+ advanceReplacement: input.advanceReplacement ?? false,
1012
+ advanceShippedAt: input.advanceShippedAt ?? null,
1013
+ reasonCode: input.reasonCode ?? null,
1014
+ rejectionReasonCode: input.rejectionReasonCode ?? null,
1015
+ resolutionSummary: input.resolutionSummary ?? null,
1016
+ notes: input.notes ?? null,
1017
+ currencyCode: input.currencyCode ?? null,
1018
+ totalClaimedAmount: "0",
1019
+ totalApprovedAmount: "0",
1020
+ totalRecoveredAmount: "0",
1021
+ createdAt: /* @__PURE__ */ new Date(),
1022
+ updatedAt: /* @__PURE__ */ new Date()
1023
+ });
1024
+ em.persist(claim);
1025
+ createdLines = (input.lines ?? []).map((lineInput, index) => {
1026
+ const line = em.create(WarrantyClaimLine, buildInitialLineData(claim, lineInput, index));
1027
+ em.persist(line);
1028
+ return line;
1029
+ });
1030
+ const totals = computeHeaderRollups(createdLines, { claimType: claim.claimType });
1031
+ claim.totalClaimedAmount = String(totals.totalClaimedAmount);
1032
+ claim.totalApprovedAmount = String(totals.totalApprovedAmount);
1033
+ appendClaimEvent(em, claim, "system", {
1034
+ visibility: "internal",
1035
+ payload: { action: "created" },
1036
+ actorUserId: ctx.auth?.sub ?? null
1037
+ });
1038
+ },
1039
+ () => stampEntitlementSourceIfResolvable(ctx, em, claim, input, scope)
1040
+ ], { transaction: true, label: "warranty_claims.claim.create" });
1041
+ await emitClaimCrud(ctx, "created", claim);
1042
+ await Promise.all(createdLines.map((line) => emitLineCrud(ctx, "created", line)));
1043
+ return { claimId: claim.id };
1044
+ },
1045
+ captureAfter: async (rawInput, result, ctx) => {
1046
+ const input = parseCommandInput(claimCreateSchema, rawInput);
1047
+ const scope = resolveScope(ctx, input);
1048
+ const em = ctx.container.resolve("em").fork();
1049
+ return loadClaimSnapshot(em, result.claimId, scope);
1050
+ },
1051
+ buildLog: async ({ result, snapshots }) => buildClaimLog("warranty_claims.audit.claim.create", result.claimId, snapshots),
1052
+ undo: async ({ logEntry, ctx }) => {
1053
+ const payload = extractUndoPayload(logEntry);
1054
+ const after = payload?.after;
1055
+ if (!after) return;
1056
+ const em = ctx.container.resolve("em").fork();
1057
+ let claim;
1058
+ let lines = [];
1059
+ await withAtomicFlush(em, [
1060
+ async () => {
1061
+ const current = await requireCurrentUndoSnapshot(em, after);
1062
+ claim = current.claim;
1063
+ lines = current.lines.filter((line) => !line.deletedAt);
1064
+ const now = /* @__PURE__ */ new Date();
1065
+ claim.deletedAt = now;
1066
+ claim.updatedAt = now;
1067
+ for (const line of lines) {
1068
+ line.deletedAt = now;
1069
+ line.updatedAt = now;
1070
+ }
1071
+ }
1072
+ ], { transaction: true, label: "warranty_claims.claim.create.undo" });
1073
+ await emitClaimUndoCrud(ctx, "deleted", claim);
1074
+ await Promise.all(lines.map((line) => emitLineUndoCrud(ctx, "deleted", line)));
1075
+ }
1076
+ };
1077
+ const updateClaimCommand = {
1078
+ id: "warranty_claims.claim.update",
1079
+ isUndoable: true,
1080
+ prepare: async (rawInput, ctx) => {
1081
+ const input = parseCommandInput(claimUpdateSchema, rawInput);
1082
+ const scope = resolveScope(ctx, input);
1083
+ const em = ctx.container.resolve("em").fork();
1084
+ return { before: await loadClaimSnapshot(em, input.id, scope) };
1085
+ },
1086
+ async execute(rawInput, ctx) {
1087
+ const input = parseCommandInput(claimUpdateSchema, rawInput);
1088
+ const scope = resolveScope(ctx, input);
1089
+ const em = ctx.container.resolve("em").fork();
1090
+ const claim = await requireScopedClaim(em, input.id, scope);
1091
+ await enforceWarrantyClaimOptimisticLock(ctx, claim);
1092
+ assertClaimUpdateFields(claim, input);
1093
+ await validateClaimReferences(ctx, scope, {
1094
+ orderId: hasOwn(input, "orderId") && (input.orderId ?? null) !== (claim.orderId ?? null) ? input.orderId ?? null : null,
1095
+ salesReturnId: hasOwn(input, "salesReturnId") && (input.salesReturnId ?? null) !== (claim.salesReturnId ?? null) ? input.salesReturnId ?? null : null,
1096
+ replacementOrderId: hasOwn(input, "replacementOrderId") && (input.replacementOrderId ?? null) !== (claim.replacementOrderId ?? null) ? input.replacementOrderId ?? null : null,
1097
+ creditMemoId: hasOwn(input, "creditMemoId") && (input.creditMemoId ?? null) !== (claim.creditMemoId ?? null) ? input.creditMemoId ?? null : null
1098
+ });
1099
+ const effectiveOrderId = hasOwn(input, "orderId") ? input.orderId ?? null : claim.orderId ?? null;
1100
+ const effectiveCustomerId = hasOwn(input, "customerId") ? input.customerId ?? null : claim.customerId ?? null;
1101
+ await assertOrderBelongsToCustomer(em, scope, effectiveOrderId, effectiveCustomerId);
1102
+ const customerChanged = hasOwn(input, "customerId") && (input.customerId ?? null) !== (claim.customerId ?? null);
1103
+ const shouldRefreshCustomerName = hasOwn(input, "customerId") || hasOwn(input, "customerName");
1104
+ const customerName = shouldRefreshCustomerName ? await resolveCustomerName(ctx, input.customerId ?? claim.customerId, scope, input.customerName ?? claim.customerName, { strict: customerChanged }) : claim.customerName ?? null;
1105
+ const orderNumber = hasOwn(input, "orderId") ? await resolveOrderNumber(ctx, input.orderId, scope, (input.orderId ?? null) === (claim.orderId ?? null) ? claim.orderNumber : null) : claim.orderNumber ?? null;
1106
+ await withAtomicFlush(em, [
1107
+ () => applyClaimUpdate(claim, input, customerName, orderNumber)
1108
+ ], { transaction: true, label: "warranty_claims.claim.update" });
1109
+ await emitClaimCrud(ctx, "updated", claim);
1110
+ return { claimId: claim.id };
1111
+ },
1112
+ captureAfter: async (rawInput, result, ctx) => {
1113
+ const input = parseCommandInput(claimUpdateSchema, rawInput);
1114
+ const scope = resolveScope(ctx, input);
1115
+ const em = ctx.container.resolve("em").fork();
1116
+ return loadClaimSnapshot(em, result.claimId, scope);
1117
+ },
1118
+ buildLog: async ({ result, snapshots }) => buildClaimLog("warranty_claims.audit.claim.update", result.claimId, snapshots),
1119
+ undo: async ({ logEntry, ctx }) => {
1120
+ const payload = extractUndoPayload(logEntry);
1121
+ const before = payload?.before;
1122
+ if (!before) return;
1123
+ const after = payload?.after;
1124
+ if (!after) throwClaimUndoConflict();
1125
+ const em = ctx.container.resolve("em").fork();
1126
+ let claim;
1127
+ await withAtomicFlush(em, [
1128
+ async () => {
1129
+ claim = await restoreSnapshot(em, before, after);
1130
+ }
1131
+ ], { transaction: true, label: "warranty_claims.claim.update.undo" });
1132
+ await emitClaimUndoCrud(ctx, "updated", claim);
1133
+ }
1134
+ };
1135
+ const deleteClaimCommand = {
1136
+ id: "warranty_claims.claim.delete",
1137
+ isUndoable: true,
1138
+ prepare: async (rawInput, ctx) => {
1139
+ const input = parseCommandInput(claimDeleteSchema, rawInput);
1140
+ const scope = resolveScope(ctx, input);
1141
+ const em = ctx.container.resolve("em").fork();
1142
+ return { before: await loadClaimSnapshot(em, input.id, scope) };
1143
+ },
1144
+ async execute(rawInput, ctx) {
1145
+ const input = parseCommandInput(claimDeleteSchema, rawInput);
1146
+ const scope = resolveScope(ctx, input);
1147
+ const em = ctx.container.resolve("em").fork();
1148
+ const claim = await requireScopedClaim(em, input.id, scope);
1149
+ await enforceWarrantyClaimOptimisticLock(ctx, claim);
1150
+ if (!deletableStatuses.has(claim.status)) {
1151
+ throw new CrudHttpError(400, { error: "warranty_claims.errors.deleteNotAllowed" });
1152
+ }
1153
+ const lines = await findWithDecryption(
1154
+ em,
1155
+ WarrantyClaimLine,
1156
+ { claim: claim.id, tenantId: scope.tenantId, organizationId: scope.organizationId, deletedAt: null },
1157
+ {},
1158
+ scope
1159
+ );
1160
+ await withAtomicFlush(em, [
1161
+ () => {
1162
+ const now = /* @__PURE__ */ new Date();
1163
+ claim.deletedAt = now;
1164
+ claim.updatedAt = now;
1165
+ for (const line of lines) {
1166
+ line.deletedAt = now;
1167
+ line.updatedAt = now;
1168
+ }
1169
+ }
1170
+ ], { transaction: true, label: "warranty_claims.claim.delete" });
1171
+ await emitClaimCrud(ctx, "deleted", claim);
1172
+ await Promise.all(lines.map((line) => emitLineCrud(ctx, "deleted", line)));
1173
+ return { claimId: claim.id };
1174
+ },
1175
+ captureAfter: async (rawInput, result, ctx) => {
1176
+ const input = parseCommandInput(claimDeleteSchema, rawInput);
1177
+ const scope = resolveScope(ctx, input);
1178
+ const em = ctx.container.resolve("em").fork();
1179
+ return loadClaimSnapshot(em, result.claimId, scope);
1180
+ },
1181
+ buildLog: async ({ result, snapshots }) => buildClaimLog("warranty_claims.audit.claim.delete", result.claimId, snapshots),
1182
+ undo: async ({ logEntry, ctx }) => {
1183
+ const payload = extractUndoPayload(logEntry);
1184
+ const before = payload?.before;
1185
+ if (!before) return;
1186
+ const after = payload?.after;
1187
+ if (!after) throwClaimUndoConflict();
1188
+ const em = ctx.container.resolve("em").fork();
1189
+ let claim;
1190
+ await withAtomicFlush(em, [
1191
+ async () => {
1192
+ claim = await restoreSnapshot(em, before, after);
1193
+ }
1194
+ ], { transaction: true, label: "warranty_claims.claim.delete.undo" });
1195
+ await emitClaimUndoCrud(ctx, "created", claim);
1196
+ const restoredLines = await findWithDecryption(
1197
+ em,
1198
+ WarrantyClaimLine,
1199
+ { claim: claim.id, tenantId: before.tenantId, organizationId: before.organizationId, deletedAt: null },
1200
+ {},
1201
+ { tenantId: before.tenantId, organizationId: before.organizationId }
1202
+ );
1203
+ await Promise.all(restoredLines.map((line) => emitLineCrud(ctx, "created", line)));
1204
+ }
1205
+ };
1206
+ const submitClaimCommand = {
1207
+ id: "warranty_claims.claim.submit",
1208
+ async execute(rawInput, ctx) {
1209
+ const input = parseCommandInput(submitClaimSchema, rawInput);
1210
+ const scope = resolveScope(ctx, input);
1211
+ const em = ctx.container.resolve("em").fork();
1212
+ const claim = await requireScopedClaim(em, input.id, scope);
1213
+ await enforceWarrantyClaimOptimisticLock(ctx, claim);
1214
+ if (claim.status !== "draft") {
1215
+ throw new CrudHttpError(400, { error: "warranty_claims.errors.invalidTransition" });
1216
+ }
1217
+ const fromStatus = claim.status;
1218
+ let autoApproved = false;
1219
+ let effectiveSettings = null;
1220
+ await withAtomicFlush(em, [
1221
+ async () => {
1222
+ effectiveSettings = await resolveEffectiveWarrantyClaimSettings(em, scope);
1223
+ const submittedAt = /* @__PURE__ */ new Date();
1224
+ claim.status = "submitted";
1225
+ claim.submittedAt = submittedAt;
1226
+ claim.slaDueAt = addBusinessMillis(submittedAt, effectiveSettings.slaHours * 60 * 60 * 1e3, effectiveSettings.businessHours);
1227
+ claim.slaAtRiskNotifiedAt = null;
1228
+ claim.slaBreachedNotifiedAt = null;
1229
+ claim.updatedAt = submittedAt;
1230
+ appendClaimEvent(em, claim, "status_changed", {
1231
+ visibility: "customer",
1232
+ payload: { from: fromStatus, to: "submitted" },
1233
+ actorUserId: input.actorCustomerId ? null : ctx.auth?.sub ?? null,
1234
+ actorCustomerId: input.actorCustomerId ?? null
1235
+ });
1236
+ },
1237
+ async () => {
1238
+ const lines = await findWithDecryption(
1239
+ em,
1240
+ WarrantyClaimLine,
1241
+ {
1242
+ claim: claim.id,
1243
+ tenantId: claim.tenantId,
1244
+ organizationId: claim.organizationId,
1245
+ deletedAt: null
1246
+ },
1247
+ {},
1248
+ scope
1249
+ );
1250
+ const risk = await evaluateClaimRisk(em, claim, lines);
1251
+ const settings = effectiveSettings ?? await resolveEffectiveWarrantyClaimSettings(em, scope);
1252
+ const evaluator = ctx.container.resolve("warrantyAdjudicationEvaluator");
1253
+ const { decision } = await evaluator.evaluate({
1254
+ claim,
1255
+ lines,
1256
+ settings,
1257
+ risk,
1258
+ container: ctx.container,
1259
+ em,
1260
+ scope
1261
+ });
1262
+ if (decision === "auto_approve") {
1263
+ claim.status = "approved";
1264
+ claim.updatedAt = /* @__PURE__ */ new Date();
1265
+ autoApproved = true;
1266
+ appendClaimEvent(em, claim, "status_changed", {
1267
+ visibility: "customer",
1268
+ payload: { from: "submitted", to: "approved" },
1269
+ actorUserId: null
1270
+ });
1271
+ appendClaimEvent(em, claim, "system", {
1272
+ visibility: "internal",
1273
+ payload: {
1274
+ action: "auto_approved",
1275
+ maxAmount: settings.autoApproveMaxAmount,
1276
+ currencyCode: settings.autoApproveCurrencyCode
1277
+ },
1278
+ actorUserId: null
1279
+ });
1280
+ }
1281
+ }
1282
+ ], { transaction: true, label: "warranty_claims.claim.submit" });
1283
+ await emitClaimCrud(ctx, "updated", claim);
1284
+ const payload = claimEventPayload(claim);
1285
+ await emitWarrantyClaimsEvent("warranty_claims.claim.submitted", payload, claimPersistentEmitOptions(claim));
1286
+ await emitClaimStatusChanged(ctx, claim, autoApproved ? "submitted" : fromStatus, autoApproved ? "approved" : "submitted");
1287
+ return { claimId: claim.id };
1288
+ }
1289
+ };
1290
+ const transitionClaimCommand = {
1291
+ id: "warranty_claims.claim.transition",
1292
+ isUndoable: true,
1293
+ prepare: async (rawInput, ctx) => {
1294
+ const input = parseCommandInput(transitionClaimInputSchema, rawInput);
1295
+ const scope = resolveScope(ctx, {});
1296
+ const em = ctx.container.resolve("em").fork();
1297
+ return { before: await loadClaimSnapshot(em, input.id, scope) };
1298
+ },
1299
+ async execute(rawInput, ctx) {
1300
+ const input = parseCommandInput(transitionClaimInputSchema, rawInput);
1301
+ const scope = resolveScope(ctx, {});
1302
+ const em = ctx.container.resolve("em").fork();
1303
+ const claim = await requireScopedClaim(em, input.id, scope);
1304
+ await enforceWarrantyClaimOptimisticLock(ctx, claim);
1305
+ const fromStatus = claim.status;
1306
+ assertTransition(fromStatus, input.toStatus);
1307
+ if (input.toStatus === "cancelled" && !preReceivedStatuses.has(fromStatus)) {
1308
+ throw new CrudHttpError(400, { error: "warranty_claims.errors.invalidTransition" });
1309
+ }
1310
+ if (input.toStatus === "rejected" && !input.rejectionReasonCode) {
1311
+ throw new CrudHttpError(400, { error: "warranty_claims.errors.invalidTransition" });
1312
+ }
1313
+ const lines = input.toStatus === "resolved" ? await findWithDecryption(
1314
+ em,
1315
+ WarrantyClaimLine,
1316
+ {
1317
+ claim: claim.id,
1318
+ tenantId: claim.tenantId,
1319
+ organizationId: claim.organizationId,
1320
+ deletedAt: null
1321
+ },
1322
+ {},
1323
+ { tenantId: claim.tenantId, organizationId: claim.organizationId }
1324
+ ) : [];
1325
+ if (input.toStatus === "resolved") assertCanResolve(lines);
1326
+ await withAtomicFlush(em, [
1327
+ async () => {
1328
+ const effectiveSettings = await resolveEffectiveWarrantyClaimSettings(em, scope);
1329
+ const now = /* @__PURE__ */ new Date();
1330
+ claim.status = input.toStatus;
1331
+ if (input.toStatus === "rejected") claim.rejectionReasonCode = input.rejectionReasonCode ?? null;
1332
+ if (input.toStatus === "resolved") {
1333
+ claim.resolvedAt = now;
1334
+ if (hasOwn(input, "resolutionSummary")) claim.resolutionSummary = input.resolutionSummary ?? null;
1335
+ }
1336
+ if (input.toStatus === "closed") claim.closedAt = now;
1337
+ applySlaResume(em, claim, fromStatus, input.toStatus, now, effectiveSettings);
1338
+ applySlaPause(em, claim, fromStatus, input.toStatus, now, effectiveSettings);
1339
+ claim.awaitingStaffReply = false;
1340
+ claim.updatedAt = now;
1341
+ appendClaimEvent(em, claim, "status_changed", {
1342
+ visibility: "customer",
1343
+ payload: {
1344
+ from: fromStatus,
1345
+ to: input.toStatus,
1346
+ ...input.systemNote ? { systemNote: input.systemNote } : {}
1347
+ },
1348
+ actorUserId: input.actorCustomerId ? null : ctx.auth?.sub ?? null,
1349
+ actorCustomerId: input.actorCustomerId ?? null
1350
+ });
1351
+ }
1352
+ ], { transaction: true, label: "warranty_claims.claim.transition" });
1353
+ await emitClaimCrud(ctx, "updated", claim);
1354
+ await emitClaimStatusChanged(ctx, claim, fromStatus, input.toStatus);
1355
+ return { claimId: claim.id };
1356
+ },
1357
+ captureAfter: async (_rawInput, result, ctx) => {
1358
+ const scope = resolveScope(ctx, {});
1359
+ const em = ctx.container.resolve("em").fork();
1360
+ return loadClaimSnapshot(em, result.claimId, scope);
1361
+ },
1362
+ buildLog: async ({ result, snapshots }) => buildClaimLog("warranty_claims.audit.claim.transition", result.claimId, snapshots),
1363
+ undo: async ({ logEntry, ctx }) => {
1364
+ const payload = extractUndoPayload(logEntry);
1365
+ const before = payload?.before;
1366
+ if (!before) return;
1367
+ const after = payload?.after;
1368
+ if (!after) throwClaimUndoConflict();
1369
+ const em = ctx.container.resolve("em").fork();
1370
+ let claim;
1371
+ await withAtomicFlush(em, [
1372
+ async () => {
1373
+ claim = await restoreSnapshot(em, before, after);
1374
+ appendClaimEvent(em, claim, "system", {
1375
+ visibility: "internal",
1376
+ payload: { action: "undo_status_transition" },
1377
+ actorUserId: ctx.auth?.sub ?? null
1378
+ });
1379
+ }
1380
+ ], { transaction: true, label: "warranty_claims.claim.transition.undo" });
1381
+ await emitClaimUndoCrud(ctx, "updated", claim);
1382
+ }
1383
+ };
1384
+ const assignClaimCommand = {
1385
+ id: "warranty_claims.claim.assign",
1386
+ async execute(rawInput, ctx) {
1387
+ const input = parseCommandInput(assignClaimInputSchema, rawInput);
1388
+ const scope = resolveScope(ctx, {});
1389
+ const em = ctx.container.resolve("em").fork();
1390
+ const claim = await requireScopedClaim(em, input.id, scope);
1391
+ await enforceWarrantyClaimOptimisticLock(ctx, claim);
1392
+ await validateAssigneeUser(ctx, scope, input.assigneeUserId ?? null);
1393
+ await withAtomicFlush(em, [
1394
+ () => {
1395
+ applyClaimAssignment(em, claim, input.assigneeUserId ?? null, ctx.auth?.sub ?? null, /* @__PURE__ */ new Date());
1396
+ }
1397
+ ], { transaction: true, label: "warranty_claims.claim.assign" });
1398
+ await emitClaimCrud(ctx, "updated", claim);
1399
+ await emitWarrantyClaimsEvent("warranty_claims.claim.assigned", {
1400
+ ...claimEventPayload(claim),
1401
+ assigneeUserId: claim.assigneeUserId
1402
+ }, claimPersistentEmitOptions(claim));
1403
+ return { claimId: claim.id };
1404
+ }
1405
+ };
1406
+ const setReturnLabelCommand = {
1407
+ id: "warranty_claims.claim.set_return_label",
1408
+ isUndoable: true,
1409
+ prepare: async (rawInput, ctx) => {
1410
+ const input = parseCommandInput(claimSetReturnLabelSchema, rawInput);
1411
+ const scope = resolveScope(ctx, input);
1412
+ const em = ctx.container.resolve("em").fork();
1413
+ return { before: await loadClaimSnapshot(em, input.id, scope) };
1414
+ },
1415
+ async execute(rawInput, ctx) {
1416
+ const input = parseCommandInput(claimSetReturnLabelSchema, rawInput);
1417
+ const scope = resolveScope(ctx, input);
1418
+ const em = ctx.container.resolve("em").fork();
1419
+ const claim = await requireScopedClaim(em, input.id, scope);
1420
+ await enforceWarrantyClaimOptimisticLock(ctx, claim, WARRANTY_CLAIM_RESOURCE_KIND, input.updatedAt ?? void 0);
1421
+ await withAtomicFlush(em, [
1422
+ () => {
1423
+ if (input.labelUrl !== void 0) claim.returnLabelUrl = input.labelUrl;
1424
+ if (input.trackingNumber !== void 0) claim.returnTrackingNumber = input.trackingNumber;
1425
+ if (input.carrier !== void 0) claim.returnCarrier = input.carrier;
1426
+ claim.updatedAt = /* @__PURE__ */ new Date();
1427
+ appendClaimEvent(em, claim, "system", {
1428
+ visibility: "internal",
1429
+ payload: {
1430
+ action: "return_label_created",
1431
+ carrier: claim.returnCarrier ?? null,
1432
+ trackingNumber: claim.returnTrackingNumber ?? null
1433
+ },
1434
+ actorUserId: ctx.auth?.sub ?? null
1435
+ });
1436
+ }
1437
+ ], { transaction: true, label: "warranty_claims.claim.set_return_label" });
1438
+ await emitClaimCrud(ctx, "updated", claim);
1439
+ await emitWarrantyClaimsEvent("warranty_claims.claim.return_label_created", {
1440
+ id: claim.id,
1441
+ claimId: claim.id,
1442
+ carrier: claim.returnCarrier ?? null,
1443
+ trackingNumber: claim.returnTrackingNumber ?? null,
1444
+ scope,
1445
+ organizationId: scope.organizationId,
1446
+ tenantId: scope.tenantId
1447
+ }, claimPersistentEmitOptions(claim));
1448
+ return {
1449
+ claimId: claim.id,
1450
+ labelUrl: claim.returnLabelUrl ?? null,
1451
+ trackingNumber: claim.returnTrackingNumber ?? null,
1452
+ carrier: claim.returnCarrier ?? null
1453
+ };
1454
+ },
1455
+ captureAfter: async (rawInput, result, ctx) => {
1456
+ const input = parseCommandInput(claimSetReturnLabelSchema, rawInput);
1457
+ const scope = resolveScope(ctx, input);
1458
+ const em = ctx.container.resolve("em").fork();
1459
+ return loadClaimSnapshot(em, result.claimId, scope);
1460
+ },
1461
+ buildLog: async ({ result, snapshots }) => buildClaimLog("warranty_claims.audit.claim.set_return_label", result.claimId, snapshots),
1462
+ undo: async ({ logEntry, ctx }) => {
1463
+ const payload = extractUndoPayload(logEntry);
1464
+ const before = payload?.before;
1465
+ if (!before) return;
1466
+ const after = payload?.after;
1467
+ if (!after) throwClaimUndoConflict();
1468
+ const em = ctx.container.resolve("em").fork();
1469
+ let claim;
1470
+ await withAtomicFlush(em, [
1471
+ async () => {
1472
+ claim = await restoreSnapshot(em, before, after);
1473
+ appendClaimEvent(em, claim, "system", {
1474
+ visibility: "internal",
1475
+ payload: { action: "undo_return_label" },
1476
+ actorUserId: ctx.auth?.sub ?? null
1477
+ });
1478
+ }
1479
+ ], { transaction: true, label: "warranty_claims.claim.set_return_label.undo" });
1480
+ await emitClaimUndoCrud(ctx, "updated", claim);
1481
+ }
1482
+ };
1483
+ const escalateClaimCommand = {
1484
+ id: "warranty_claims.claim.escalate",
1485
+ async execute(rawInput, ctx) {
1486
+ const input = parseCommandInput(escalateClaimInputSchema, rawInput);
1487
+ const scope = resolveScope(ctx, input);
1488
+ const em = ctx.container.resolve("em").fork();
1489
+ if (input.reassignToUserId) {
1490
+ await validateAssigneeUser(ctx, scope, input.reassignToUserId);
1491
+ }
1492
+ let claim = null;
1493
+ let escalated = false;
1494
+ await withAtomicFlush(em, [
1495
+ async () => {
1496
+ claim = await requireScopedClaim(em, input.id, scope, { lockMode: LockMode.PESSIMISTIC_WRITE });
1497
+ if ((claim.escalationLevel ?? 0) >= input.toLevel) return;
1498
+ const now = /* @__PURE__ */ new Date();
1499
+ claim.escalationLevel = input.toLevel;
1500
+ claim.escalatedAt = now;
1501
+ claim.updatedAt = now;
1502
+ appendClaimEvent(em, claim, "system", {
1503
+ visibility: "internal",
1504
+ payload: {
1505
+ action: "sla_escalated",
1506
+ level: input.toLevel,
1507
+ reassignToUserId: input.reassignToUserId ?? null
1508
+ },
1509
+ actorUserId: ctx.auth?.sub ?? null
1510
+ });
1511
+ if (input.reassignToUserId) {
1512
+ applyClaimAssignment(em, claim, input.reassignToUserId, ctx.auth?.sub ?? null, now);
1513
+ }
1514
+ escalated = true;
1515
+ }
1516
+ ], { transaction: true, label: "warranty_claims.claim.escalate" });
1517
+ const escalatedClaim = claim;
1518
+ if (!escalatedClaim) {
1519
+ throw new CrudHttpError(404, { error: "warranty_claims.errors.notFound" });
1520
+ }
1521
+ if (!escalated) {
1522
+ return { claimId: escalatedClaim.id, escalationLevel: escalatedClaim.escalationLevel ?? 0, escalated: false };
1523
+ }
1524
+ await emitClaimCrud(ctx, "updated", escalatedClaim);
1525
+ if (input.reassignToUserId) {
1526
+ await emitWarrantyClaimsEvent("warranty_claims.claim.assigned", {
1527
+ ...claimEventPayload(escalatedClaim),
1528
+ assigneeUserId: escalatedClaim.assigneeUserId ?? null
1529
+ }, claimPersistentEmitOptions(escalatedClaim));
1530
+ }
1531
+ await emitWarrantyClaimsEvent("warranty_claims.claim.escalated", {
1532
+ ...claimEventPayload(escalatedClaim),
1533
+ level: escalatedClaim.escalationLevel,
1534
+ escalationLevel: escalatedClaim.escalationLevel,
1535
+ reassignToUserId: input.reassignToUserId ?? null
1536
+ }, claimPersistentEmitOptions(escalatedClaim));
1537
+ return { claimId: escalatedClaim.id, escalationLevel: escalatedClaim.escalationLevel, escalated: true };
1538
+ }
1539
+ };
1540
+ const commentClaimCommand = {
1541
+ id: "warranty_claims.claim.comment",
1542
+ async execute(rawInput, ctx) {
1543
+ const input = parseCommandInput(commentClaimInputSchema, rawInput);
1544
+ const scope = resolveScope(ctx, {});
1545
+ const em = ctx.container.resolve("em").fork();
1546
+ const claim = await requireScopedClaim(em, input.claimId, scope);
1547
+ await enforceWarrantyClaimOptimisticLock(ctx, claim);
1548
+ let autoResumed = false;
1549
+ await withAtomicFlush(em, [
1550
+ async () => {
1551
+ const fromStatus = claim.status;
1552
+ appendClaimEvent(em, claim, "comment", {
1553
+ visibility: input.visibility,
1554
+ body: input.body,
1555
+ actorUserId: input.actorCustomerId ? null : ctx.auth?.sub ?? null,
1556
+ actorCustomerId: input.actorCustomerId ?? null
1557
+ });
1558
+ claim.awaitingStaffReply = Boolean(input.actorCustomerId);
1559
+ claim.updatedAt = /* @__PURE__ */ new Date();
1560
+ if (input.actorCustomerId && fromStatus === "info_requested") {
1561
+ const now = /* @__PURE__ */ new Date();
1562
+ const effectiveSettings = await resolveEffectiveWarrantyClaimSettings(em, scope);
1563
+ claim.status = "in_review";
1564
+ applySlaResume(em, claim, fromStatus, "in_review", now, effectiveSettings);
1565
+ claim.updatedAt = now;
1566
+ autoResumed = true;
1567
+ appendClaimEvent(em, claim, "status_changed", {
1568
+ visibility: "customer",
1569
+ payload: { from: fromStatus, to: "in_review" },
1570
+ actorUserId: null
1571
+ });
1572
+ }
1573
+ }
1574
+ ], { transaction: true, label: "warranty_claims.claim.comment" });
1575
+ if (input.visibility === "customer") {
1576
+ await emitCustomerVisibleCommentAdded(ctx, claim, input);
1577
+ }
1578
+ await emitClaimCrud(ctx, "updated", claim);
1579
+ if (autoResumed) {
1580
+ await emitClaimStatusChanged(ctx, claim, "info_requested", "in_review");
1581
+ }
1582
+ return { claimId: claim.id };
1583
+ }
1584
+ };
1585
+ const createVendorRecoveryCommand = {
1586
+ id: "warranty_claims.claim.create_vendor_recovery",
1587
+ async execute(rawInput, ctx) {
1588
+ const input = parseCommandInput(vendorRecoveryInputSchema, rawInput);
1589
+ const scope = resolveScope(ctx, input);
1590
+ const em = ctx.container.resolve("em").fork();
1591
+ const sourceClaim = await requireScopedClaim(em, input.claimId, scope);
1592
+ await enforceWarrantyClaimOptimisticLock(ctx, sourceClaim);
1593
+ const preflightLines = await findWithDecryption(
1594
+ em,
1595
+ WarrantyClaimLine,
1596
+ {
1597
+ id: { $in: input.lineIds },
1598
+ claim: sourceClaim.id,
1599
+ tenantId: scope.tenantId,
1600
+ organizationId: scope.organizationId,
1601
+ deletedAt: null
1602
+ },
1603
+ {},
1604
+ scope
1605
+ );
1606
+ assertVendorRecoveryLines(preflightLines, input.lineIds);
1607
+ const numberGenerator = resolveClaimNumberGenerator(ctx, em);
1608
+ const generated = await numberGenerator.generate({
1609
+ claimType: "vendor_recovery",
1610
+ tenantId: scope.tenantId,
1611
+ organizationId: scope.organizationId
1612
+ });
1613
+ let recoveryClaim;
1614
+ let copiedLines = [];
1615
+ let updatedSourceLines = [];
1616
+ await em.transactional(async (tx) => {
1617
+ const lockedSource = await requireScopedClaim(tx, sourceClaim.id, scope, { lockMode: LockMode.PESSIMISTIC_WRITE });
1618
+ const lockedLines = await findWithDecryption(
1619
+ tx,
1620
+ WarrantyClaimLine,
1621
+ {
1622
+ id: { $in: input.lineIds },
1623
+ claim: lockedSource.id,
1624
+ tenantId: scope.tenantId,
1625
+ organizationId: scope.organizationId,
1626
+ deletedAt: null
1627
+ },
1628
+ { lockMode: LockMode.PESSIMISTIC_WRITE },
1629
+ scope
1630
+ );
1631
+ assertVendorRecoveryLines(lockedLines, input.lineIds);
1632
+ updatedSourceLines = lockedLines;
1633
+ recoveryClaim = tx.create(WarrantyClaim, {
1634
+ id: randomUUID(),
1635
+ organizationId: scope.organizationId,
1636
+ tenantId: scope.tenantId,
1637
+ claimNumber: generated.number,
1638
+ claimType: "vendor_recovery",
1639
+ status: "draft",
1640
+ channel: "staff",
1641
+ priority: lockedSource.priority,
1642
+ customerId: lockedSource.customerId ?? null,
1643
+ customerName: lockedSource.customerName ?? null,
1644
+ vendorName: input.vendorName,
1645
+ vendorRef: input.vendorRef ?? null,
1646
+ sourceClaimId: lockedSource.id,
1647
+ advanceReplacement: false,
1648
+ currencyCode: lockedSource.currencyCode ?? null,
1649
+ totalClaimedAmount: "0",
1650
+ totalApprovedAmount: "0",
1651
+ totalRecoveredAmount: "0",
1652
+ createdAt: /* @__PURE__ */ new Date(),
1653
+ updatedAt: /* @__PURE__ */ new Date()
1654
+ });
1655
+ tx.persist(recoveryClaim);
1656
+ const lineOrder = new Map(input.lineIds.map((id, index) => [id, index]));
1657
+ copiedLines = [...lockedLines].sort((left, right) => (lineOrder.get(left.id) ?? 0) - (lineOrder.get(right.id) ?? 0)).map((sourceLine, index) => {
1658
+ const copiedLine = tx.create(WarrantyClaimLine, {
1659
+ id: randomUUID(),
1660
+ claim: recoveryClaim,
1661
+ organizationId: scope.organizationId,
1662
+ tenantId: scope.tenantId,
1663
+ lineNo: index + 1,
1664
+ productId: sourceLine.productId ?? null,
1665
+ variantId: sourceLine.variantId ?? null,
1666
+ sku: sourceLine.sku ?? null,
1667
+ productName: sourceLine.productName ?? null,
1668
+ orderLineId: sourceLine.orderLineId ?? null,
1669
+ serialNumber: sourceLine.serialNumber ?? null,
1670
+ lotNumber: sourceLine.lotNumber ?? null,
1671
+ purchaseDate: sourceLine.purchaseDate ?? null,
1672
+ warrantyMonths: sourceLine.warrantyMonths ?? null,
1673
+ warrantyExpiresAt: sourceLine.warrantyExpiresAt ?? null,
1674
+ warrantyStatus: sourceLine.warrantyStatus,
1675
+ faultCode: sourceLine.faultCode ?? null,
1676
+ faultDescription: sourceLine.faultDescription ?? null,
1677
+ qtyClaimed: sourceLine.qtyApproved ?? sourceLine.qtyClaimed,
1678
+ qtyApproved: sourceLine.qtyApproved ?? null,
1679
+ conditionOnReceipt: sourceLine.conditionOnReceipt ?? null,
1680
+ inspectionNotes: sourceLine.inspectionNotes ?? null,
1681
+ disposition: sourceLine.disposition ?? null,
1682
+ lineStatus: "pending",
1683
+ creditAmount: sourceLine.creditAmount ?? null,
1684
+ restockingFee: sourceLine.restockingFee ?? null,
1685
+ coreChargeAmount: sourceLine.coreChargeAmount ?? null,
1686
+ coreCreditAmount: sourceLine.coreCreditAmount ?? null,
1687
+ createdAt: /* @__PURE__ */ new Date(),
1688
+ updatedAt: /* @__PURE__ */ new Date()
1689
+ });
1690
+ sourceLine.vendorClaimLineId = copiedLine.id;
1691
+ sourceLine.updatedAt = /* @__PURE__ */ new Date();
1692
+ tx.persist(copiedLine);
1693
+ return copiedLine;
1694
+ });
1695
+ const totals = computeHeaderRollups(copiedLines);
1696
+ recoveryClaim.totalClaimedAmount = String(totals.totalClaimedAmount);
1697
+ recoveryClaim.totalApprovedAmount = String(totals.totalApprovedAmount);
1698
+ appendClaimEvent(tx, lockedSource, "system", {
1699
+ visibility: "internal",
1700
+ payload: { action: "vendor_recovery_created", recoveryClaimId: recoveryClaim.id },
1701
+ actorUserId: ctx.auth?.sub ?? null
1702
+ });
1703
+ appendClaimEvent(tx, recoveryClaim, "system", {
1704
+ visibility: "internal",
1705
+ payload: { action: "created_from_source_claim", sourceClaimId: lockedSource.id },
1706
+ actorUserId: ctx.auth?.sub ?? null
1707
+ });
1708
+ await tx.flush();
1709
+ });
1710
+ await emitClaimCrud(ctx, "created", recoveryClaim);
1711
+ await Promise.all(copiedLines.map((line) => emitLineCrud(ctx, "created", line)));
1712
+ await Promise.all(updatedSourceLines.map((line) => emitLineCrud(ctx, "updated", line)));
1713
+ await Promise.all(input.lineIds.map((sourceLineId) => invalidateCrudCache(
1714
+ ctx.container,
1715
+ "warranty_claims.claim_line",
1716
+ { id: sourceLineId, organizationId: sourceClaim.organizationId, tenantId: sourceClaim.tenantId },
1717
+ ctx.auth?.tenantId ?? null,
1718
+ "warranty_claims.vendor_recovery.source_lines"
1719
+ )));
1720
+ return { claimId: recoveryClaim.id };
1721
+ }
1722
+ };
1723
+ const salesReturnBridgeLogger = createLogger("warranty_claims");
1724
+ const SALES_RETURN_ELIGIBLE_CLAIM_STATUSES = /* @__PURE__ */ new Set(["approved", "awaiting_return", "received", "inspecting"]);
1725
+ const SALES_RETURN_ELIGIBLE_LINE_STATUSES = /* @__PURE__ */ new Set(["approved", "received", "inspected", "resolved"]);
1726
+ function scrubbedDispatchContext(ctx) {
1727
+ return { ...ctx, request: void 0 };
1728
+ }
1729
+ function isSalesQuantityRejection(err) {
1730
+ if (!(err instanceof CrudHttpError) || err.status !== 400) return false;
1731
+ const body = err.body;
1732
+ const message = typeof body?.error === "string" ? body.error : "";
1733
+ return message.includes("quantityExceedsShipped") || message.includes("Cannot return more than the shipped quantity");
1734
+ }
1735
+ async function dispatchSalesReturnDelete(ctx, scope, salesReturnId, orderId) {
1736
+ const commandBus = ctx.container.resolve("commandBus");
1737
+ await commandBus.execute(
1738
+ "sales.returns.delete",
1739
+ {
1740
+ input: { id: salesReturnId, orderId, tenantId: scope.tenantId, organizationId: scope.organizationId },
1741
+ ctx: scrubbedDispatchContext(ctx)
1742
+ }
1743
+ );
1744
+ }
1745
+ const createSalesReturnCommand = {
1746
+ id: "warranty_claims.claim.create_sales_return",
1747
+ isUndoable: true,
1748
+ prepare: async (rawInput, ctx) => {
1749
+ const input = parseCommandInput(claimCreateSalesReturnSchema, rawInput);
1750
+ const scope = resolveScope(ctx, input);
1751
+ const em = ctx.container.resolve("em").fork();
1752
+ return { before: await loadClaimSnapshot(em, input.id, scope) };
1753
+ },
1754
+ async execute(rawInput, ctx) {
1755
+ const input = parseCommandInput(claimCreateSalesReturnSchema, rawInput);
1756
+ const scope = resolveScope(ctx, input);
1757
+ const em = ctx.container.resolve("em").fork();
1758
+ const claim = await requireScopedClaim(em, input.id, scope);
1759
+ await enforceWarrantyClaimOptimisticLock(ctx, claim, WARRANTY_CLAIM_RESOURCE_KIND, input.updatedAt ?? void 0);
1760
+ if (!claim.orderId) {
1761
+ throw new CrudHttpError(400, { error: "warranty_claims.errors.salesReturnRequiresOrder" });
1762
+ }
1763
+ if (!SALES_RETURN_ELIGIBLE_CLAIM_STATUSES.has(claim.status)) {
1764
+ throw new CrudHttpError(400, { error: "warranty_claims.errors.salesReturnStatusNotEligible" });
1765
+ }
1766
+ if (claim.salesReturnId) {
1767
+ throw new CrudHttpError(400, { error: "warranty_claims.errors.salesReturnAlreadyLinked" });
1768
+ }
1769
+ if (!resolveOptionalEntityId("sales", "sales_return")) {
1770
+ throw new CrudHttpError(400, { error: "warranty_claims.errors.salesUnavailable" });
1771
+ }
1772
+ const lines = await findWithDecryption(
1773
+ em,
1774
+ WarrantyClaimLine,
1775
+ { claim: claim.id, tenantId: scope.tenantId, organizationId: scope.organizationId, deletedAt: null },
1776
+ {},
1777
+ scope
1778
+ );
1779
+ const eligibleLines = [];
1780
+ const skippedLineIds = [];
1781
+ for (const line of lines) {
1782
+ const effectiveQty = line.qtyApproved ?? line.qtyClaimed;
1783
+ const quantityUnits = claimedQuantityUnits(effectiveQty) ?? 0n;
1784
+ const eligible = Boolean(line.orderLineId) && SALES_RETURN_ELIGIBLE_LINE_STATUSES.has(line.lineStatus) && quantityUnits > 0n && quantityUnits % CLAIMED_QUANTITY_SCALE === 0n;
1785
+ if (eligible && line.orderLineId) {
1786
+ eligibleLines.push({ orderLineId: line.orderLineId, quantity: String(effectiveQty) });
1787
+ } else {
1788
+ skippedLineIds.push(line.id);
1789
+ }
1790
+ }
1791
+ if (eligibleLines.length === 0) {
1792
+ throw new CrudHttpError(400, { error: "warranty_claims.errors.salesReturnNoEligibleLines" });
1793
+ }
1794
+ const commandBus = ctx.container.resolve("commandBus");
1795
+ let salesReturnId;
1796
+ try {
1797
+ const { result: dispatchedResult } = await commandBus.execute(
1798
+ "sales.returns.create",
1799
+ {
1800
+ input: {
1801
+ orderId: claim.orderId,
1802
+ reason: claim.claimNumber,
1803
+ tenantId: scope.tenantId,
1804
+ organizationId: scope.organizationId,
1805
+ lines: eligibleLines
1806
+ },
1807
+ ctx: scrubbedDispatchContext(ctx)
1808
+ }
1809
+ );
1810
+ if (!dispatchedResult?.returnId) {
1811
+ throw new CrudHttpError(400, { error: "warranty_claims.errors.save_failed" });
1812
+ }
1813
+ salesReturnId = dispatchedResult.returnId;
1814
+ } catch (err) {
1815
+ if (isSalesQuantityRejection(err)) {
1816
+ throw new CrudHttpError(400, { error: "warranty_claims.errors.salesReturnQuantityRejected" });
1817
+ }
1818
+ throw err;
1819
+ }
1820
+ let salesReturnUpdatedAt = null;
1821
+ try {
1822
+ const createdReturnRow = await querySalesReferenceRow(ctx, "sales_returns", scope, salesReturnId, ["id", "updated_at"]);
1823
+ salesReturnUpdatedAt = createdReturnRow?.updated_at ? new Date(createdReturnRow.updated_at).toISOString() : null;
1824
+ await em.transactional(async (tx) => {
1825
+ const lockedClaim = await requireScopedClaim(tx, claim.id, scope, { lockMode: LockMode.PESSIMISTIC_WRITE });
1826
+ if (lockedClaim.salesReturnId) {
1827
+ throw new CrudHttpError(400, { error: "warranty_claims.errors.salesReturnAlreadyLinked" });
1828
+ }
1829
+ lockedClaim.salesReturnId = salesReturnId;
1830
+ lockedClaim.updatedAt = /* @__PURE__ */ new Date();
1831
+ appendClaimEvent(tx, lockedClaim, "system", {
1832
+ visibility: "internal",
1833
+ payload: { action: "sales_return_created", salesReturnId },
1834
+ actorUserId: ctx.auth?.sub ?? null
1835
+ });
1836
+ claim.salesReturnId = salesReturnId;
1837
+ claim.updatedAt = lockedClaim.updatedAt;
1838
+ await tx.flush();
1839
+ });
1840
+ } catch (err) {
1841
+ try {
1842
+ await dispatchSalesReturnDelete(ctx, scope, salesReturnId, claim.orderId);
1843
+ } catch (compensationErr) {
1844
+ salesReturnBridgeLogger.error("warranty_claims.claim.create_sales_return compensation failed \u2014 orphaned sales return", {
1845
+ err: compensationErr,
1846
+ claimId: claim.id,
1847
+ salesReturnId
1848
+ });
1849
+ const orphanEm = ctx.container.resolve("em").fork();
1850
+ const orphanClaim = await findOneWithDecryption(
1851
+ orphanEm,
1852
+ WarrantyClaim,
1853
+ { id: claim.id, tenantId: scope.tenantId, organizationId: scope.organizationId, deletedAt: null },
1854
+ {},
1855
+ scope
1856
+ );
1857
+ if (orphanClaim) {
1858
+ try {
1859
+ appendClaimEvent(orphanEm, orphanClaim, "system", {
1860
+ visibility: "internal",
1861
+ payload: { action: "sales_return_orphaned", salesReturnId },
1862
+ actorUserId: ctx.auth?.sub ?? null
1863
+ });
1864
+ await orphanEm.flush();
1865
+ } catch (orphanEventErr) {
1866
+ salesReturnBridgeLogger.error("warranty_claims.claim.create_sales_return orphan timeline write failed", {
1867
+ err: orphanEventErr,
1868
+ claimId: claim.id,
1869
+ salesReturnId
1870
+ });
1871
+ }
1872
+ }
1873
+ throw new CrudHttpError(500, { error: "warranty_claims.errors.save_failed" });
1874
+ }
1875
+ throw err;
1876
+ }
1877
+ await emitClaimCrud(ctx, "updated", claim);
1878
+ return { claimId: claim.id, salesReturnId, salesReturnUpdatedAt, skippedLineIds };
1879
+ },
1880
+ captureAfter: async (rawInput, result, ctx) => {
1881
+ const input = parseCommandInput(claimCreateSalesReturnSchema, rawInput);
1882
+ const scope = resolveScope(ctx, input);
1883
+ const em = ctx.container.resolve("em").fork();
1884
+ return loadClaimSnapshot(em, result.claimId, scope);
1885
+ },
1886
+ buildLog: async ({ result, snapshots }) => {
1887
+ const base = buildClaimLog("warranty_claims.audit.claim.create_sales_return", result.claimId, snapshots);
1888
+ const after = snapshots.after;
1889
+ const undoPayload = {
1890
+ ...base.payload.undo,
1891
+ salesReturn: {
1892
+ id: result.salesReturnId,
1893
+ orderId: after?.orderId ?? "",
1894
+ updatedAt: result.salesReturnUpdatedAt
1895
+ }
1896
+ };
1897
+ return { ...base, payload: { undo: undoPayload } };
1898
+ },
1899
+ undo: async ({ logEntry, ctx }) => {
1900
+ const payload = extractUndoPayload(logEntry);
1901
+ const before = payload?.before;
1902
+ if (!before) return;
1903
+ const after = payload?.after;
1904
+ if (!after) throwClaimUndoConflict();
1905
+ await assertUndoSnapshotCurrent(ctx, after);
1906
+ const scope = { tenantId: before.tenantId, organizationId: before.organizationId };
1907
+ const salesReturn = payload?.salesReturn ?? null;
1908
+ if (salesReturn?.id && salesReturn.orderId) {
1909
+ const currentRow = await querySalesReferenceRow(ctx, "sales_returns", scope, salesReturn.id, ["id", "updated_at"]);
1910
+ if (currentRow) {
1911
+ const currentUpdatedAt = currentRow.updated_at ? new Date(currentRow.updated_at).toISOString() : null;
1912
+ if (!salesReturn.updatedAt || currentUpdatedAt && currentUpdatedAt !== salesReturn.updatedAt) {
1913
+ throw new CrudHttpError(409, { error: "warranty_claims.errors.salesReturnChangedUndoAborted" });
1914
+ }
1915
+ await dispatchSalesReturnDelete(ctx, scope, salesReturn.id, salesReturn.orderId);
1916
+ }
1917
+ }
1918
+ const em = ctx.container.resolve("em").fork();
1919
+ let claim;
1920
+ await withAtomicFlush(em, [
1921
+ async () => {
1922
+ claim = await restoreSnapshot(em, before, after);
1923
+ appendClaimEvent(em, claim, "system", {
1924
+ visibility: "internal",
1925
+ payload: { action: "undo_sales_return_created" },
1926
+ actorUserId: ctx.auth?.sub ?? null
1927
+ });
1928
+ }
1929
+ ], { transaction: true, label: "warranty_claims.claim.create_sales_return.undo" });
1930
+ await emitClaimUndoCrud(ctx, "updated", claim);
1931
+ }
1932
+ };
1933
+ const creditMemoBridgeLogger = createLogger("warranty_claims");
1934
+ const CREDIT_MEMO_ELIGIBLE_CLAIM_STATUSES = /* @__PURE__ */ new Set(["received", "inspecting", "resolved"]);
1935
+ const CREDIT_MEMO_ELIGIBLE_LINE_STATUSES = /* @__PURE__ */ new Set(["received", "inspected", "resolved"]);
1936
+ const CREDIT_MEMO_ELIGIBLE_DISPOSITIONS = /* @__PURE__ */ new Set(["credit", "refund"]);
1937
+ function minimumUnits(left, right) {
1938
+ return left < right ? left : right;
1939
+ }
1940
+ function prepareCreditMemoLine(claimLine, sourceLine, creditedQuantityUnits, currencyCode) {
1941
+ const orderQuantityUnits = claimedQuantityUnits(sourceLine.quantity);
1942
+ const sourceGrossUnits = claimedQuantityUnits(sourceLine.total_gross_amount);
1943
+ const sourceNetUnits = claimedQuantityUnits(sourceLine.total_net_amount);
1944
+ const taxRateUnits = claimedQuantityUnits(sourceLine.tax_rate);
1945
+ if (orderQuantityUnits === null || orderQuantityUnits <= 0n || creditedQuantityUnits > orderQuantityUnits || sourceGrossUnits === null || sourceNetUnits === null || taxRateUnits === null) {
1946
+ return null;
1947
+ }
1948
+ let netBasisUnits = sourceNetUnits;
1949
+ if (sourceGrossUnits > 0n && netBasisUnits <= 0n) {
1950
+ netBasisUnits = netUnitsFromGross(sourceGrossUnits, taxRateUnits);
1951
+ }
1952
+ const proratedGrossUnits = roundHalfUpDivision(sourceGrossUnits * creditedQuantityUnits, orderQuantityUnits);
1953
+ const proratedNetUnits = roundHalfUpDivision(netBasisUnits * creditedQuantityUnits, orderQuantityUnits);
1954
+ const creditAmountUnits = claimedQuantityUnits(claimLine.creditAmount);
1955
+ const restockingFeeUnits = claimedQuantityUnits(claimLine.restockingFee) ?? 0n;
1956
+ const coreCreditUnits = claimedQuantityUnits(claimLine.coreCreditAmount) ?? 0n;
1957
+ const baseGrossUnits = creditAmountUnits ?? proratedGrossUnits;
1958
+ const adjustedGrossUnits = baseGrossUnits - restockingFeeUnits + coreCreditUnits;
1959
+ const grossUnits = adjustedGrossUnits > 0n ? adjustedGrossUnits : 0n;
1960
+ let netUnits = grossUnits === proratedGrossUnits ? proratedNetUnits : netUnitsFromGross(grossUnits, taxRateUnits);
1961
+ if (grossUnits > 0n && netUnits <= 0n) {
1962
+ netUnits = netUnitsFromGross(grossUnits, taxRateUnits);
1963
+ }
1964
+ const taxUnits = grossUnits - netUnits;
1965
+ const input = {
1966
+ orderLineId: claimLine.orderLineId,
1967
+ quantity: formatScaledUnits(creditedQuantityUnits),
1968
+ currencyCode,
1969
+ unitPriceNet: formatScaledUnits(roundHalfUpDivision(netUnits * CLAIMED_QUANTITY_SCALE, creditedQuantityUnits)),
1970
+ unitPriceGross: formatScaledUnits(roundHalfUpDivision(grossUnits * CLAIMED_QUANTITY_SCALE, creditedQuantityUnits)),
1971
+ taxRate: formatScaledUnits(taxRateUnits),
1972
+ taxAmount: formatScaledUnits(taxUnits),
1973
+ totalNetAmount: formatScaledUnits(netUnits),
1974
+ totalGrossAmount: formatScaledUnits(grossUnits),
1975
+ metadata: { warrantyClaimLineId: claimLine.id }
1976
+ };
1977
+ const name = readString(sourceLine, "name");
1978
+ if (name) input.name = name;
1979
+ return { input, netUnits, grossUnits, taxUnits };
1980
+ }
1981
+ async function dispatchCreditMemoDelete(ctx, scope, creditMemoId) {
1982
+ const commandBus = ctx.container.resolve("commandBus");
1983
+ await commandBus.execute(
1984
+ "sales.credit_memos.delete",
1985
+ {
1986
+ input: { id: creditMemoId, organizationId: scope.organizationId, tenantId: scope.tenantId },
1987
+ ctx: scrubbedDispatchContext(ctx)
1988
+ }
1989
+ );
1990
+ }
1991
+ const createCreditMemoCommand = {
1992
+ id: "warranty_claims.claim.create_credit_memo",
1993
+ isUndoable: true,
1994
+ prepare: async (rawInput, ctx) => {
1995
+ const input = parseCommandInput(claimCreateCreditMemoSchema, rawInput);
1996
+ const scope = resolveScope(ctx, input);
1997
+ const em = ctx.container.resolve("em").fork();
1998
+ return { before: await loadClaimSnapshot(em, input.id, scope) };
1999
+ },
2000
+ async execute(rawInput, ctx) {
2001
+ const input = parseCommandInput(claimCreateCreditMemoSchema, rawInput);
2002
+ const scope = resolveScope(ctx, input);
2003
+ const em = ctx.container.resolve("em").fork();
2004
+ const claim = await requireScopedClaim(em, input.id, scope);
2005
+ await enforceWarrantyClaimOptimisticLock(ctx, claim, WARRANTY_CLAIM_RESOURCE_KIND, input.updatedAt ?? void 0);
2006
+ if (!claim.orderId) {
2007
+ throw new CrudHttpError(400, { error: "warranty_claims.errors.creditMemoRequiresOrder" });
2008
+ }
2009
+ if (!CREDIT_MEMO_ELIGIBLE_CLAIM_STATUSES.has(claim.status)) {
2010
+ throw new CrudHttpError(400, { error: "warranty_claims.errors.creditMemoInvalidStatus" });
2011
+ }
2012
+ if (claim.creditMemoId) {
2013
+ throw new CrudHttpError(400, { error: "warranty_claims.errors.creditMemoAlreadyLinked" });
2014
+ }
2015
+ if (!resolveOptionalEntityId("sales", "sales_credit_memo")) {
2016
+ throw new CrudHttpError(400, { error: "warranty_claims.errors.creditMemoSalesUnavailable" });
2017
+ }
2018
+ const sourceOrder = await querySalesReferenceRow(
2019
+ ctx,
2020
+ "sales_orders",
2021
+ scope,
2022
+ claim.orderId,
2023
+ ["id", "currency_code"]
2024
+ );
2025
+ const orderCurrencyCode = sourceOrder ? readString(sourceOrder, "currency_code") : null;
2026
+ if (!sourceOrder || !orderCurrencyCode) {
2027
+ throw new CrudHttpError(400, { error: "warranty_claims.errors.creditMemoRequiresOrder" });
2028
+ }
2029
+ const claimLines = await findWithDecryption(
2030
+ em,
2031
+ WarrantyClaimLine,
2032
+ { claim: claim.id, tenantId: scope.tenantId, organizationId: scope.organizationId, deletedAt: null },
2033
+ {},
2034
+ scope
2035
+ );
2036
+ const sourceLineRows = await querySalesOrderLineReferences(
2037
+ ctx,
2038
+ scope,
2039
+ Array.from(new Set(claimLines.flatMap((line) => line.orderLineId ? [line.orderLineId] : []))),
2040
+ ["id", "order_id", "name", "currency_code", "quantity", "total_net_amount", "total_gross_amount", "tax_rate"]
2041
+ );
2042
+ const sourceLinesById = new Map((sourceLineRows ?? []).map((row) => [readString(row, "id"), row]));
2043
+ const preparedLines = [];
2044
+ const skippedLineIds = [];
2045
+ for (const claimLine of claimLines) {
2046
+ const approvedQuantityUnits = claimedQuantityUnits(claimLine.qtyApproved ?? claimLine.qtyClaimed) ?? 0n;
2047
+ const receivedQuantityUnits = claimedQuantityUnits(claimLine.qtyReceived) ?? 0n;
2048
+ const creditedQuantityUnits = minimumUnits(approvedQuantityUnits, receivedQuantityUnits);
2049
+ const initiallyEligible = Boolean(claimLine.orderLineId) && CREDIT_MEMO_ELIGIBLE_DISPOSITIONS.has(claimLine.disposition) && CREDIT_MEMO_ELIGIBLE_LINE_STATUSES.has(claimLine.lineStatus) && creditedQuantityUnits > 0n;
2050
+ if (!initiallyEligible || !claimLine.orderLineId) {
2051
+ skippedLineIds.push(claimLine.id);
2052
+ continue;
2053
+ }
2054
+ const sourceLine = sourceLinesById.get(claimLine.orderLineId) ?? null;
2055
+ const sourceCurrencyCode = sourceLine ? readString(sourceLine, "currency_code") : null;
2056
+ if (!sourceLine || sourceLine.order_id !== claim.orderId || sourceCurrencyCode !== orderCurrencyCode) {
2057
+ skippedLineIds.push(claimLine.id);
2058
+ continue;
2059
+ }
2060
+ const preparedLine = prepareCreditMemoLine(
2061
+ claimLine,
2062
+ sourceLine,
2063
+ creditedQuantityUnits,
2064
+ orderCurrencyCode
2065
+ );
2066
+ if (!preparedLine) {
2067
+ skippedLineIds.push(claimLine.id);
2068
+ continue;
2069
+ }
2070
+ preparedLines.push(preparedLine);
2071
+ }
2072
+ if (preparedLines.length === 0) {
2073
+ throw new CrudHttpError(400, { error: "warranty_claims.errors.creditMemoNoEligibleLines" });
2074
+ }
2075
+ const totals = preparedLines.reduce(
2076
+ (result, line) => ({
2077
+ netUnits: result.netUnits + line.netUnits,
2078
+ grossUnits: result.grossUnits + line.grossUnits,
2079
+ taxUnits: result.taxUnits + line.taxUnits
2080
+ }),
2081
+ { netUnits: 0n, grossUnits: 0n, taxUnits: 0n }
2082
+ );
2083
+ const grandTotalNetAmount = formatScaledUnits(totals.netUnits);
2084
+ const grandTotalGrossAmount = formatScaledUnits(totals.grossUnits);
2085
+ const creditMemoInput = {
2086
+ organizationId: scope.organizationId,
2087
+ tenantId: scope.tenantId,
2088
+ orderId: claim.orderId,
2089
+ currencyCode: orderCurrencyCode,
2090
+ reason: claim.claimNumber,
2091
+ metadata: { warrantyClaimId: claim.id, warrantyClaimNumber: claim.claimNumber },
2092
+ lines: preparedLines.map((line) => line.input),
2093
+ subtotalNetAmount: grandTotalNetAmount,
2094
+ subtotalGrossAmount: grandTotalGrossAmount,
2095
+ taxTotalAmount: formatScaledUnits(totals.taxUnits),
2096
+ grandTotalNetAmount,
2097
+ grandTotalGrossAmount
2098
+ };
2099
+ const commandBus = ctx.container.resolve("commandBus");
2100
+ let creditMemoId = null;
2101
+ let creditMemoUpdatedAt = null;
2102
+ try {
2103
+ const { result: dispatchedResult } = await commandBus.execute(
2104
+ "sales.credit_memos.create",
2105
+ { input: creditMemoInput, ctx: scrubbedDispatchContext(ctx) }
2106
+ );
2107
+ if (!dispatchedResult?.creditMemoId) {
2108
+ throw new CrudHttpError(400, { error: "warranty_claims.errors.save_failed" });
2109
+ }
2110
+ creditMemoId = dispatchedResult.creditMemoId;
2111
+ const createdCreditMemoRow = await querySalesReferenceRow(
2112
+ ctx,
2113
+ "sales_credit_memos",
2114
+ scope,
2115
+ creditMemoId,
2116
+ ["id", "updated_at"]
2117
+ );
2118
+ creditMemoUpdatedAt = createdCreditMemoRow?.updated_at ? new Date(createdCreditMemoRow.updated_at).toISOString() : null;
2119
+ await em.transactional(async (tx) => {
2120
+ const lockedClaim = await requireScopedClaim(tx, claim.id, scope, { lockMode: LockMode.PESSIMISTIC_WRITE });
2121
+ if (lockedClaim.creditMemoId) {
2122
+ throw new CrudHttpError(400, { error: "warranty_claims.errors.creditMemoAlreadyLinked" });
2123
+ }
2124
+ lockedClaim.creditMemoId = creditMemoId;
2125
+ lockedClaim.updatedAt = /* @__PURE__ */ new Date();
2126
+ appendClaimEvent(tx, lockedClaim, "system", {
2127
+ visibility: "internal",
2128
+ payload: { action: "credit_memo_created", creditMemoId, grandTotalGrossAmount, currencyCode: orderCurrencyCode },
2129
+ actorUserId: ctx.auth?.sub ?? null
2130
+ });
2131
+ claim.creditMemoId = creditMemoId;
2132
+ claim.updatedAt = lockedClaim.updatedAt;
2133
+ await tx.flush();
2134
+ });
2135
+ } catch (err) {
2136
+ if (!creditMemoId) throw err;
2137
+ try {
2138
+ await dispatchCreditMemoDelete(ctx, scope, creditMemoId);
2139
+ } catch (compensationErr) {
2140
+ creditMemoBridgeLogger.error("warranty_claims.claim.create_credit_memo compensation failed \u2014 orphaned credit memo", {
2141
+ err: compensationErr,
2142
+ claimId: claim.id,
2143
+ creditMemoId
2144
+ });
2145
+ try {
2146
+ const orphanEm = ctx.container.resolve("em").fork();
2147
+ const orphanClaim = await findOneWithDecryption(
2148
+ orphanEm,
2149
+ WarrantyClaim,
2150
+ { id: claim.id, tenantId: scope.tenantId, organizationId: scope.organizationId, deletedAt: null },
2151
+ {},
2152
+ scope
2153
+ );
2154
+ if (orphanClaim) {
2155
+ appendClaimEvent(orphanEm, orphanClaim, "system", {
2156
+ visibility: "internal",
2157
+ payload: { action: "credit_memo_orphaned", creditMemoId },
2158
+ actorUserId: ctx.auth?.sub ?? null
2159
+ });
2160
+ await orphanEm.flush();
2161
+ }
2162
+ } catch (orphanEventErr) {
2163
+ creditMemoBridgeLogger.error("warranty_claims.claim.create_credit_memo orphan timeline write failed", {
2164
+ err: orphanEventErr,
2165
+ claimId: claim.id,
2166
+ creditMemoId
2167
+ });
2168
+ }
2169
+ throw new CrudHttpError(500, { error: "warranty_claims.errors.save_failed" });
2170
+ }
2171
+ throw err;
2172
+ }
2173
+ await emitClaimCrud(ctx, "updated", claim);
2174
+ return {
2175
+ claimId: claim.id,
2176
+ creditMemoId,
2177
+ creditMemoUpdatedAt,
2178
+ skippedLineIds,
2179
+ grandTotalGrossAmount,
2180
+ currencyCode: orderCurrencyCode
2181
+ };
2182
+ },
2183
+ captureAfter: async (rawInput, result, ctx) => {
2184
+ const input = parseCommandInput(claimCreateCreditMemoSchema, rawInput);
2185
+ const scope = resolveScope(ctx, input);
2186
+ const em = ctx.container.resolve("em").fork();
2187
+ return loadClaimSnapshot(em, result.claimId, scope);
2188
+ },
2189
+ buildLog: async ({ result, snapshots }) => {
2190
+ const base = buildClaimLog("warranty_claims.audit.claim.create_credit_memo", result.claimId, snapshots);
2191
+ const undoPayload = {
2192
+ ...base.payload.undo,
2193
+ creditMemo: { id: result.creditMemoId, updatedAt: result.creditMemoUpdatedAt }
2194
+ };
2195
+ return { ...base, payload: { undo: undoPayload } };
2196
+ },
2197
+ undo: async ({ logEntry, ctx }) => {
2198
+ const payload = extractUndoPayload(logEntry);
2199
+ const before = payload?.before;
2200
+ if (!before) return;
2201
+ const after = payload?.after;
2202
+ if (!after) throwClaimUndoConflict();
2203
+ await assertUndoSnapshotCurrent(ctx, after);
2204
+ const scope = { tenantId: before.tenantId, organizationId: before.organizationId };
2205
+ const creditMemo = payload?.creditMemo ?? null;
2206
+ if (creditMemo?.id) {
2207
+ const currentRow = await querySalesReferenceRow(ctx, "sales_credit_memos", scope, creditMemo.id, ["id", "updated_at"]);
2208
+ if (currentRow === void 0) {
2209
+ throw new CrudHttpError(409, { error: "warranty_claims.errors.creditMemoChangedUndoAborted" });
2210
+ }
2211
+ if (currentRow) {
2212
+ const currentUpdatedAt = currentRow.updated_at ? new Date(currentRow.updated_at).toISOString() : null;
2213
+ if (!creditMemo.updatedAt || !currentUpdatedAt || currentUpdatedAt !== creditMemo.updatedAt) {
2214
+ throw new CrudHttpError(409, { error: "warranty_claims.errors.creditMemoChangedUndoAborted" });
2215
+ }
2216
+ await dispatchCreditMemoDelete(ctx, scope, creditMemo.id);
2217
+ } else {
2218
+ creditMemoBridgeLogger.info("warranty_claims.claim.create_credit_memo undo found credit memo already absent", {
2219
+ claimId: before.id,
2220
+ creditMemoId: creditMemo.id
2221
+ });
2222
+ }
2223
+ }
2224
+ const em = ctx.container.resolve("em").fork();
2225
+ let claim;
2226
+ await withAtomicFlush(em, [
2227
+ async () => {
2228
+ claim = await restoreSnapshot(em, before, after);
2229
+ appendClaimEvent(em, claim, "system", {
2230
+ visibility: "internal",
2231
+ payload: { action: "undo_credit_memo_created" },
2232
+ actorUserId: ctx.auth?.sub ?? null
2233
+ });
2234
+ }
2235
+ ], { transaction: true, label: "warranty_claims.claim.create_credit_memo.undo" });
2236
+ await emitClaimUndoCrud(ctx, "updated", claim);
2237
+ }
2238
+ };
2239
+ const replacementOrderBridgeLogger = createLogger("warranty_claims");
2240
+ const REPLACEMENT_ELIGIBLE_CLAIM_STATUSES = /* @__PURE__ */ new Set([
2241
+ "approved",
2242
+ "awaiting_return",
2243
+ "received",
2244
+ "inspecting",
2245
+ "resolved"
2246
+ ]);
2247
+ const REPLACEMENT_ELIGIBLE_LINE_STATUSES = /* @__PURE__ */ new Set(["approved", "received", "inspected", "resolved"]);
2248
+ const ADVANCE_REPLACEMENT_CLAIM_STATUSES = /* @__PURE__ */ new Set(["approved", "awaiting_return"]);
2249
+ const SALES_ORDER_LINE_KINDS = /* @__PURE__ */ new Set(["product", "service", "shipping", "discount", "adjustment"]);
2250
+ function readSalesOrderLineKind(value) {
2251
+ return typeof value === "string" && SALES_ORDER_LINE_KINDS.has(value) ? value : null;
2252
+ }
2253
+ async function dispatchSalesOrderDelete(ctx, scope, replacementOrderId) {
2254
+ const commandBus = ctx.container.resolve("commandBus");
2255
+ await commandBus.execute(
2256
+ "sales.orders.delete",
2257
+ {
2258
+ input: { id: replacementOrderId, tenantId: scope.tenantId, organizationId: scope.organizationId },
2259
+ ctx: scrubbedDispatchContext(ctx)
2260
+ }
2261
+ );
2262
+ }
2263
+ const createReplacementOrderCommand = {
2264
+ id: "warranty_claims.claim.create_replacement_order",
2265
+ isUndoable: true,
2266
+ prepare: async (rawInput, ctx) => {
2267
+ const input = parseCommandInput(claimCreateReplacementOrderSchema, rawInput);
2268
+ const scope = resolveScope(ctx, input);
2269
+ const em = ctx.container.resolve("em").fork();
2270
+ return { before: await loadClaimSnapshot(em, input.id, scope) };
2271
+ },
2272
+ async execute(rawInput, ctx) {
2273
+ const input = parseCommandInput(claimCreateReplacementOrderSchema, rawInput);
2274
+ const scope = resolveScope(ctx, input);
2275
+ const em = ctx.container.resolve("em").fork();
2276
+ const claim = await requireScopedClaim(em, input.id, scope);
2277
+ await enforceWarrantyClaimOptimisticLock(ctx, claim, WARRANTY_CLAIM_RESOURCE_KIND, input.updatedAt ?? void 0);
2278
+ if (!claim.orderId) {
2279
+ throw new CrudHttpError(400, { error: "warranty_claims.errors.replacementOrderRequiresOrder" });
2280
+ }
2281
+ if (!REPLACEMENT_ELIGIBLE_CLAIM_STATUSES.has(claim.status)) {
2282
+ throw new CrudHttpError(400, { error: "warranty_claims.errors.replacementInvalidStatus" });
2283
+ }
2284
+ if (claim.replacementOrderId) {
2285
+ throw new CrudHttpError(400, { error: "warranty_claims.errors.replacementAlreadyLinked" });
2286
+ }
2287
+ if (!resolveOptionalEntityId("sales", "sales_order")) {
2288
+ throw new CrudHttpError(400, { error: "warranty_claims.errors.replacementSalesUnavailable" });
2289
+ }
2290
+ const sourceOrder = await querySalesReferenceRow(
2291
+ ctx,
2292
+ "sales_orders",
2293
+ scope,
2294
+ claim.orderId,
2295
+ [
2296
+ "id",
2297
+ "currency_code",
2298
+ "customer_entity_id",
2299
+ "customer_contact_id",
2300
+ "billing_address_id",
2301
+ "shipping_address_id",
2302
+ "channel_id"
2303
+ ]
2304
+ );
2305
+ const orderCurrencyCode = sourceOrder ? readString(sourceOrder, "currency_code") : null;
2306
+ if (!sourceOrder || !orderCurrencyCode) {
2307
+ throw new CrudHttpError(400, { error: "warranty_claims.errors.replacementOrderRequiresOrder" });
2308
+ }
2309
+ const lines = await findWithDecryption(
2310
+ em,
2311
+ WarrantyClaimLine,
2312
+ { claim: claim.id, tenantId: scope.tenantId, organizationId: scope.organizationId, deletedAt: null },
2313
+ {},
2314
+ scope
2315
+ );
2316
+ const sourceLineRows = await querySalesOrderLineReferences(
2317
+ ctx,
2318
+ scope,
2319
+ Array.from(new Set(lines.flatMap((line) => line.orderLineId ? [line.orderLineId] : []))),
2320
+ [
2321
+ "id",
2322
+ "order_id",
2323
+ "product_id",
2324
+ "product_variant_id",
2325
+ "name",
2326
+ "kind",
2327
+ "currency_code",
2328
+ "unit_price_net",
2329
+ "unit_price_gross",
2330
+ "tax_rate"
2331
+ ]
2332
+ );
2333
+ const sourceLinesById = new Map((sourceLineRows ?? []).map((row) => [readString(row, "id"), row]));
2334
+ const replacementLines = [];
2335
+ const skippedLineIds = [];
2336
+ for (const line of lines) {
2337
+ const effectiveQty = line.qtyApproved ?? line.qtyClaimed;
2338
+ const quantityUnits = claimedQuantityUnits(effectiveQty) ?? 0n;
2339
+ const initiallyEligible = line.disposition === "replace" && Boolean(line.orderLineId) && REPLACEMENT_ELIGIBLE_LINE_STATUSES.has(line.lineStatus) && quantityUnits > 0n && quantityUnits % CLAIMED_QUANTITY_SCALE === 0n;
2340
+ if (!initiallyEligible || !line.orderLineId) {
2341
+ skippedLineIds.push(line.id);
2342
+ continue;
2343
+ }
2344
+ const sourceLine = sourceLinesById.get(line.orderLineId) ?? null;
2345
+ const productId = sourceLine ? readString(sourceLine, "product_id") : null;
2346
+ const productVariantId = sourceLine ? readString(sourceLine, "product_variant_id") : null;
2347
+ const name = sourceLine ? readString(sourceLine, "name") : null;
2348
+ const kind = sourceLine ? readSalesOrderLineKind(sourceLine.kind) : null;
2349
+ const currencyCode = sourceLine ? readString(sourceLine, "currency_code") : null;
2350
+ const unitPriceNet = sourceLine ? readString(sourceLine, "unit_price_net") : null;
2351
+ const unitPriceGross = sourceLine ? readString(sourceLine, "unit_price_gross") : null;
2352
+ const taxRate = sourceLine ? readString(sourceLine, "tax_rate") : null;
2353
+ const originalPricingIncomplete = input.pricing === "original" && (unitPriceNet === null || unitPriceGross === null || taxRate === null);
2354
+ if (!sourceLine || sourceLine.order_id !== claim.orderId || !productId && !productVariantId && !name || !kind || !currencyCode || originalPricingIncomplete) {
2355
+ skippedLineIds.push(line.id);
2356
+ continue;
2357
+ }
2358
+ const replacementLine = {
2359
+ kind,
2360
+ currencyCode,
2361
+ quantity: String(effectiveQty),
2362
+ unitPriceNet: input.pricing === "zero" ? "0" : unitPriceNet,
2363
+ unitPriceGross: input.pricing === "zero" ? "0" : unitPriceGross
2364
+ };
2365
+ if (productId) replacementLine.productId = productId;
2366
+ if (productVariantId) replacementLine.productVariantId = productVariantId;
2367
+ if (name) replacementLine.name = name;
2368
+ if (input.pricing === "original") replacementLine.taxRate = taxRate;
2369
+ replacementLines.push(replacementLine);
2370
+ }
2371
+ if (replacementLines.length === 0) {
2372
+ throw new CrudHttpError(400, { error: "warranty_claims.errors.replacementNoEligibleLines" });
2373
+ }
2374
+ const orderInput = {
2375
+ organizationId: scope.organizationId,
2376
+ tenantId: scope.tenantId,
2377
+ currencyCode: orderCurrencyCode,
2378
+ metadata: { warrantyClaimId: claim.id, warrantyClaimNumber: claim.claimNumber },
2379
+ lines: replacementLines
2380
+ };
2381
+ const customerEntityId = readString(sourceOrder, "customer_entity_id");
2382
+ const customerContactId = readString(sourceOrder, "customer_contact_id");
2383
+ const billingAddressId = readString(sourceOrder, "billing_address_id");
2384
+ const shippingAddressId = readString(sourceOrder, "shipping_address_id");
2385
+ const channelId = readString(sourceOrder, "channel_id");
2386
+ if (customerEntityId) orderInput.customerEntityId = customerEntityId;
2387
+ if (customerContactId) orderInput.customerContactId = customerContactId;
2388
+ if (billingAddressId) orderInput.billingAddressId = billingAddressId;
2389
+ if (shippingAddressId) orderInput.shippingAddressId = shippingAddressId;
2390
+ if (channelId) orderInput.channelId = channelId;
2391
+ const commandBus = ctx.container.resolve("commandBus");
2392
+ const { result: dispatchedResult } = await commandBus.execute(
2393
+ "sales.orders.create",
2394
+ { input: orderInput, ctx: scrubbedDispatchContext(ctx) }
2395
+ );
2396
+ if (!dispatchedResult?.orderId) {
2397
+ throw new CrudHttpError(400, { error: "warranty_claims.errors.save_failed" });
2398
+ }
2399
+ const replacementOrderId = dispatchedResult.orderId;
2400
+ let replacementOrderUpdatedAt = null;
2401
+ try {
2402
+ const createdOrderRow = await querySalesReferenceRow(ctx, "sales_orders", scope, replacementOrderId, ["id", "updated_at"]);
2403
+ replacementOrderUpdatedAt = createdOrderRow?.updated_at ? new Date(createdOrderRow.updated_at).toISOString() : null;
2404
+ await em.transactional(async (tx) => {
2405
+ const lockedClaim = await requireScopedClaim(tx, claim.id, scope, { lockMode: LockMode.PESSIMISTIC_WRITE });
2406
+ if (lockedClaim.replacementOrderId) {
2407
+ throw new CrudHttpError(400, { error: "warranty_claims.errors.replacementAlreadyLinked" });
2408
+ }
2409
+ lockedClaim.replacementOrderId = replacementOrderId;
2410
+ if (ADVANCE_REPLACEMENT_CLAIM_STATUSES.has(lockedClaim.status)) {
2411
+ lockedClaim.advanceReplacement = true;
2412
+ }
2413
+ lockedClaim.updatedAt = /* @__PURE__ */ new Date();
2414
+ appendClaimEvent(tx, lockedClaim, "system", {
2415
+ visibility: "internal",
2416
+ payload: { action: "replacement_order_created", replacementOrderId, pricing: input.pricing },
2417
+ actorUserId: ctx.auth?.sub ?? null
2418
+ });
2419
+ claim.replacementOrderId = replacementOrderId;
2420
+ claim.advanceReplacement = lockedClaim.advanceReplacement;
2421
+ claim.updatedAt = lockedClaim.updatedAt;
2422
+ await tx.flush();
2423
+ });
2424
+ } catch (err) {
2425
+ try {
2426
+ await dispatchSalesOrderDelete(ctx, scope, replacementOrderId);
2427
+ } catch (compensationErr) {
2428
+ replacementOrderBridgeLogger.error("warranty_claims.claim.create_replacement_order compensation failed \u2014 orphaned sales order", {
2429
+ err: compensationErr,
2430
+ claimId: claim.id,
2431
+ replacementOrderId
2432
+ });
2433
+ try {
2434
+ const orphanEm = ctx.container.resolve("em").fork();
2435
+ const orphanClaim = await findOneWithDecryption(
2436
+ orphanEm,
2437
+ WarrantyClaim,
2438
+ { id: claim.id, tenantId: scope.tenantId, organizationId: scope.organizationId, deletedAt: null },
2439
+ {},
2440
+ scope
2441
+ );
2442
+ if (orphanClaim) {
2443
+ appendClaimEvent(orphanEm, orphanClaim, "system", {
2444
+ visibility: "internal",
2445
+ payload: { action: "replacement_order_orphaned", replacementOrderId },
2446
+ actorUserId: ctx.auth?.sub ?? null
2447
+ });
2448
+ await orphanEm.flush();
2449
+ }
2450
+ } catch (orphanEventErr) {
2451
+ replacementOrderBridgeLogger.error("warranty_claims.claim.create_replacement_order orphan timeline write failed", {
2452
+ err: orphanEventErr,
2453
+ claimId: claim.id,
2454
+ replacementOrderId
2455
+ });
2456
+ }
2457
+ throw new CrudHttpError(500, { error: "warranty_claims.errors.save_failed" });
2458
+ }
2459
+ throw err;
2460
+ }
2461
+ await emitClaimCrud(ctx, "updated", claim);
2462
+ return {
2463
+ claimId: claim.id,
2464
+ replacementOrderId,
2465
+ replacementOrderUpdatedAt,
2466
+ skippedLineIds,
2467
+ pricing: input.pricing
2468
+ };
2469
+ },
2470
+ captureAfter: async (rawInput, result, ctx) => {
2471
+ const input = parseCommandInput(claimCreateReplacementOrderSchema, rawInput);
2472
+ const scope = resolveScope(ctx, input);
2473
+ const em = ctx.container.resolve("em").fork();
2474
+ return loadClaimSnapshot(em, result.claimId, scope);
2475
+ },
2476
+ buildLog: async ({ result, snapshots }) => {
2477
+ const base = buildClaimLog("warranty_claims.audit.claim.create_replacement_order", result.claimId, snapshots);
2478
+ const undoPayload = {
2479
+ ...base.payload.undo,
2480
+ replacementOrder: {
2481
+ id: result.replacementOrderId,
2482
+ updatedAt: result.replacementOrderUpdatedAt
2483
+ }
2484
+ };
2485
+ return { ...base, payload: { undo: undoPayload } };
2486
+ },
2487
+ undo: async ({ logEntry, ctx }) => {
2488
+ const payload = extractUndoPayload(logEntry);
2489
+ const before = payload?.before;
2490
+ if (!before) return;
2491
+ const after = payload?.after;
2492
+ if (!after) throwClaimUndoConflict();
2493
+ await assertUndoSnapshotCurrent(ctx, after);
2494
+ const scope = { tenantId: before.tenantId, organizationId: before.organizationId };
2495
+ const replacementOrder = payload?.replacementOrder ?? null;
2496
+ if (replacementOrder?.id) {
2497
+ const currentRow = await querySalesReferenceRow(ctx, "sales_orders", scope, replacementOrder.id, ["id", "updated_at"]);
2498
+ if (currentRow === void 0) {
2499
+ throw new CrudHttpError(409, { error: "warranty_claims.errors.replacementOrderChangedUndoAborted" });
2500
+ }
2501
+ if (currentRow) {
2502
+ const currentUpdatedAt = currentRow.updated_at ? new Date(currentRow.updated_at).toISOString() : null;
2503
+ if (!replacementOrder.updatedAt || !currentUpdatedAt || currentUpdatedAt !== replacementOrder.updatedAt) {
2504
+ throw new CrudHttpError(409, { error: "warranty_claims.errors.replacementOrderChangedUndoAborted" });
2505
+ }
2506
+ await dispatchSalesOrderDelete(ctx, scope, replacementOrder.id);
2507
+ } else {
2508
+ replacementOrderBridgeLogger.info("warranty_claims.claim.create_replacement_order undo found replacement order already absent", {
2509
+ claimId: before.id,
2510
+ replacementOrderId: replacementOrder.id
2511
+ });
2512
+ }
2513
+ }
2514
+ const em = ctx.container.resolve("em").fork();
2515
+ let claim;
2516
+ await withAtomicFlush(em, [
2517
+ async () => {
2518
+ claim = await restoreSnapshot(em, before, after);
2519
+ appendClaimEvent(em, claim, "system", {
2520
+ visibility: "internal",
2521
+ payload: { action: "undo_replacement_order_created" },
2522
+ actorUserId: ctx.auth?.sub ?? null
2523
+ });
2524
+ }
2525
+ ], { transaction: true, label: "warranty_claims.claim.create_replacement_order.undo" });
2526
+ await emitClaimUndoCrud(ctx, "updated", claim);
2527
+ }
2528
+ };
2529
+ registerCommand(createClaimCommand);
2530
+ registerCommand(updateClaimCommand);
2531
+ registerCommand(deleteClaimCommand);
2532
+ registerCommand(submitClaimCommand);
2533
+ registerCommand(transitionClaimCommand);
2534
+ registerCommand(assignClaimCommand);
2535
+ registerCommand(setReturnLabelCommand);
2536
+ registerCommand(escalateClaimCommand);
2537
+ registerCommand(commentClaimCommand);
2538
+ registerCommand(createVendorRecoveryCommand);
2539
+ registerCommand(createSalesReturnCommand);
2540
+ registerCommand(createCreditMemoCommand);
2541
+ registerCommand(createReplacementOrderCommand);
2542
+ const claimCommands = [
2543
+ createClaimCommand,
2544
+ updateClaimCommand,
2545
+ deleteClaimCommand,
2546
+ submitClaimCommand,
2547
+ transitionClaimCommand,
2548
+ assignClaimCommand,
2549
+ setReturnLabelCommand,
2550
+ escalateClaimCommand,
2551
+ commentClaimCommand,
2552
+ createVendorRecoveryCommand,
2553
+ createSalesReturnCommand,
2554
+ createCreditMemoCommand,
2555
+ createReplacementOrderCommand
2556
+ ];
2557
+ export {
2558
+ assertClaimedQtyWithinSold,
2559
+ assertPendingClaimQuantitiesWithinSold,
2560
+ assignClaimCommand,
2561
+ claimCommands,
2562
+ claimCrudEvents,
2563
+ commentClaimCommand,
2564
+ createClaimCommand,
2565
+ createCreditMemoCommand,
2566
+ createReplacementOrderCommand,
2567
+ createSalesReturnCommand,
2568
+ createVendorRecoveryCommand,
2569
+ deleteClaimCommand,
2570
+ escalateClaimCommand,
2571
+ setReturnLabelCommand,
2572
+ submitClaimCommand,
2573
+ transitionClaimCommand,
2574
+ updateClaimCommand,
2575
+ validateClaimReferences
2576
+ };
2577
+ //# sourceMappingURL=claims.js.map