@endora-commerce/mod-returns 0.100.0

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 (211) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +60 -0
  3. package/dist/admin/api/returns-client.d.ts +69 -0
  4. package/dist/admin/api/returns-client.d.ts.map +1 -0
  5. package/dist/admin/api/returns-client.js +36 -0
  6. package/dist/admin/api/returns-client.js.map +1 -0
  7. package/dist/admin/index.d.ts +37 -0
  8. package/dist/admin/index.d.ts.map +1 -0
  9. package/dist/admin/index.js +57 -0
  10. package/dist/admin/index.js.map +1 -0
  11. package/dist/admin/pages/ReturnDeliveryMethodsPage.d.ts +5 -0
  12. package/dist/admin/pages/ReturnDeliveryMethodsPage.d.ts.map +1 -0
  13. package/dist/admin/pages/ReturnDeliveryMethodsPage.js +44 -0
  14. package/dist/admin/pages/ReturnDeliveryMethodsPage.js.map +1 -0
  15. package/dist/admin/pages/ReturnDetail.d.ts +5 -0
  16. package/dist/admin/pages/ReturnDetail.d.ts.map +1 -0
  17. package/dist/admin/pages/ReturnDetail.js +115 -0
  18. package/dist/admin/pages/ReturnDetail.js.map +1 -0
  19. package/dist/admin/pages/ReturnReasonsPage.d.ts +5 -0
  20. package/dist/admin/pages/ReturnReasonsPage.d.ts.map +1 -0
  21. package/dist/admin/pages/ReturnReasonsPage.js +44 -0
  22. package/dist/admin/pages/ReturnReasonsPage.js.map +1 -0
  23. package/dist/admin/pages/ReturnStatusesConfigPage.d.ts +5 -0
  24. package/dist/admin/pages/ReturnStatusesConfigPage.d.ts.map +1 -0
  25. package/dist/admin/pages/ReturnStatusesConfigPage.js +71 -0
  26. package/dist/admin/pages/ReturnStatusesConfigPage.js.map +1 -0
  27. package/dist/admin/pages/ReturnsList.d.ts +5 -0
  28. package/dist/admin/pages/ReturnsList.d.ts.map +1 -0
  29. package/dist/admin/pages/ReturnsList.js +189 -0
  30. package/dist/admin/pages/ReturnsList.js.map +1 -0
  31. package/dist/backend/domain/default-reasons.d.ts +14 -0
  32. package/dist/backend/domain/default-reasons.d.ts.map +1 -0
  33. package/dist/backend/domain/default-reasons.js +9 -0
  34. package/dist/backend/domain/default-reasons.js.map +1 -0
  35. package/dist/backend/domain/free-return-window.d.ts +2 -0
  36. package/dist/backend/domain/free-return-window.d.ts.map +1 -0
  37. package/dist/backend/domain/free-return-window.js +19 -0
  38. package/dist/backend/domain/free-return-window.js.map +1 -0
  39. package/dist/backend/domain/refund-math.d.ts +19 -0
  40. package/dist/backend/domain/refund-math.d.ts.map +1 -0
  41. package/dist/backend/domain/refund-math.js +27 -0
  42. package/dist/backend/domain/refund-math.js.map +1 -0
  43. package/dist/backend/domain/return-status-graph.d.ts +91 -0
  44. package/dist/backend/domain/return-status-graph.d.ts.map +1 -0
  45. package/dist/backend/domain/return-status-graph.js +143 -0
  46. package/dist/backend/domain/return-status-graph.js.map +1 -0
  47. package/dist/backend/email-templates/return-authorized.d.ts +11 -0
  48. package/dist/backend/email-templates/return-authorized.d.ts.map +1 -0
  49. package/dist/backend/email-templates/return-authorized.js +18 -0
  50. package/dist/backend/email-templates/return-authorized.js.map +1 -0
  51. package/dist/backend/email-templates/return-rejected.d.ts +11 -0
  52. package/dist/backend/email-templates/return-rejected.d.ts.map +1 -0
  53. package/dist/backend/email-templates/return-rejected.js +20 -0
  54. package/dist/backend/email-templates/return-rejected.js.map +1 -0
  55. package/dist/backend/email-templates/transactional-defaults.d.ts +11 -0
  56. package/dist/backend/email-templates/transactional-defaults.d.ts.map +1 -0
  57. package/dist/backend/email-templates/transactional-defaults.js +64 -0
  58. package/dist/backend/email-templates/transactional-defaults.js.map +1 -0
  59. package/dist/backend/entities/refund.entity.d.ts +41 -0
  60. package/dist/backend/entities/refund.entity.d.ts.map +1 -0
  61. package/dist/backend/entities/refund.entity.js +122 -0
  62. package/dist/backend/entities/refund.entity.js.map +1 -0
  63. package/dist/backend/entities/return-case-attachment.entity.d.ts +16 -0
  64. package/dist/backend/entities/return-case-attachment.entity.d.ts.map +1 -0
  65. package/dist/backend/entities/return-case-attachment.entity.js +53 -0
  66. package/dist/backend/entities/return-case-attachment.entity.js.map +1 -0
  67. package/dist/backend/entities/return-case-comment.entity.d.ts +20 -0
  68. package/dist/backend/entities/return-case-comment.entity.d.ts.map +1 -0
  69. package/dist/backend/entities/return-case-comment.entity.js +70 -0
  70. package/dist/backend/entities/return-case-comment.entity.js.map +1 -0
  71. package/dist/backend/entities/return-case-item.entity.d.ts +25 -0
  72. package/dist/backend/entities/return-case-item.entity.d.ts.map +1 -0
  73. package/dist/backend/entities/return-case-item.entity.js +87 -0
  74. package/dist/backend/entities/return-case-item.entity.js.map +1 -0
  75. package/dist/backend/entities/return-case.entity.d.ts +41 -0
  76. package/dist/backend/entities/return-case.entity.d.ts.map +1 -0
  77. package/dist/backend/entities/return-case.entity.js +155 -0
  78. package/dist/backend/entities/return-case.entity.js.map +1 -0
  79. package/dist/backend/entities/return-delivery-method.entity.d.ts +18 -0
  80. package/dist/backend/entities/return-delivery-method.entity.d.ts.map +1 -0
  81. package/dist/backend/entities/return-delivery-method.entity.js +63 -0
  82. package/dist/backend/entities/return-delivery-method.entity.js.map +1 -0
  83. package/dist/backend/entities/return-list-saved-view.entity.d.ts +24 -0
  84. package/dist/backend/entities/return-list-saved-view.entity.d.ts.map +1 -0
  85. package/dist/backend/entities/return-list-saved-view.entity.js +74 -0
  86. package/dist/backend/entities/return-list-saved-view.entity.js.map +1 -0
  87. package/dist/backend/entities/return-reason.entity.d.ts +21 -0
  88. package/dist/backend/entities/return-reason.entity.d.ts.map +1 -0
  89. package/dist/backend/entities/return-reason.entity.js +65 -0
  90. package/dist/backend/entities/return-reason.entity.js.map +1 -0
  91. package/dist/backend/entities/return-shipment.entity.d.ts +22 -0
  92. package/dist/backend/entities/return-shipment.entity.d.ts.map +1 -0
  93. package/dist/backend/entities/return-shipment.entity.js +71 -0
  94. package/dist/backend/entities/return-shipment.entity.js.map +1 -0
  95. package/dist/backend/entities/return-status-transition.entity.d.ts +17 -0
  96. package/dist/backend/entities/return-status-transition.entity.d.ts.map +1 -0
  97. package/dist/backend/entities/return-status-transition.entity.js +55 -0
  98. package/dist/backend/entities/return-status-transition.entity.js.map +1 -0
  99. package/dist/backend/entities/return-status.entity.d.ts +29 -0
  100. package/dist/backend/entities/return-status.entity.d.ts.map +1 -0
  101. package/dist/backend/entities/return-status.entity.js +90 -0
  102. package/dist/backend/entities/return-status.entity.js.map +1 -0
  103. package/dist/backend/events/return-status-events.d.ts +44 -0
  104. package/dist/backend/events/return-status-events.d.ts.map +1 -0
  105. package/dist/backend/events/return-status-events.js +50 -0
  106. package/dist/backend/events/return-status-events.js.map +1 -0
  107. package/dist/backend/index.d.ts +106 -0
  108. package/dist/backend/index.d.ts.map +1 -0
  109. package/dist/backend/index.js +116 -0
  110. package/dist/backend/index.js.map +1 -0
  111. package/dist/backend/plugin.d.ts +33 -0
  112. package/dist/backend/plugin.d.ts.map +1 -0
  113. package/dist/backend/plugin.js +124 -0
  114. package/dist/backend/plugin.js.map +1 -0
  115. package/dist/backend/routes.admin.d.ts +38 -0
  116. package/dist/backend/routes.admin.d.ts.map +1 -0
  117. package/dist/backend/routes.admin.js +181 -0
  118. package/dist/backend/routes.admin.js.map +1 -0
  119. package/dist/backend/routes.customer.d.ts +22 -0
  120. package/dist/backend/routes.customer.d.ts.map +1 -0
  121. package/dist/backend/routes.customer.js +56 -0
  122. package/dist/backend/routes.customer.js.map +1 -0
  123. package/dist/backend/services/notification-context.d.ts +47 -0
  124. package/dist/backend/services/notification-context.d.ts.map +1 -0
  125. package/dist/backend/services/notification-context.js +49 -0
  126. package/dist/backend/services/notification-context.js.map +1 -0
  127. package/dist/backend/services/return-attachment-service.d.ts +20 -0
  128. package/dist/backend/services/return-attachment-service.d.ts.map +1 -0
  129. package/dist/backend/services/return-attachment-service.js +48 -0
  130. package/dist/backend/services/return-attachment-service.js.map +1 -0
  131. package/dist/backend/services/return-authorization-service.d.ts +53 -0
  132. package/dist/backend/services/return-authorization-service.d.ts.map +1 -0
  133. package/dist/backend/services/return-authorization-service.js +72 -0
  134. package/dist/backend/services/return-authorization-service.js.map +1 -0
  135. package/dist/backend/services/return-case-service.d.ts +44 -0
  136. package/dist/backend/services/return-case-service.d.ts.map +1 -0
  137. package/dist/backend/services/return-case-service.js +260 -0
  138. package/dist/backend/services/return-case-service.js.map +1 -0
  139. package/dist/backend/services/return-comment-service.d.ts +43 -0
  140. package/dist/backend/services/return-comment-service.d.ts.map +1 -0
  141. package/dist/backend/services/return-comment-service.js +109 -0
  142. package/dist/backend/services/return-comment-service.js.map +1 -0
  143. package/dist/backend/services/return-delivery-method-service.d.ts +44 -0
  144. package/dist/backend/services/return-delivery-method-service.d.ts.map +1 -0
  145. package/dist/backend/services/return-delivery-method-service.js +114 -0
  146. package/dist/backend/services/return-delivery-method-service.js.map +1 -0
  147. package/dist/backend/services/return-email-notifier.d.ts +89 -0
  148. package/dist/backend/services/return-email-notifier.d.ts.map +1 -0
  149. package/dist/backend/services/return-email-notifier.js +128 -0
  150. package/dist/backend/services/return-email-notifier.js.map +1 -0
  151. package/dist/backend/services/return-export-service.d.ts +16 -0
  152. package/dist/backend/services/return-export-service.d.ts.map +1 -0
  153. package/dist/backend/services/return-export-service.js +29 -0
  154. package/dist/backend/services/return-export-service.js.map +1 -0
  155. package/dist/backend/services/return-list-service.d.ts +27 -0
  156. package/dist/backend/services/return-list-service.d.ts.map +1 -0
  157. package/dist/backend/services/return-list-service.js +114 -0
  158. package/dist/backend/services/return-list-service.js.map +1 -0
  159. package/dist/backend/services/return-list-view-service.d.ts +36 -0
  160. package/dist/backend/services/return-list-view-service.d.ts.map +1 -0
  161. package/dist/backend/services/return-list-view-service.js +82 -0
  162. package/dist/backend/services/return-list-view-service.js.map +1 -0
  163. package/dist/backend/services/return-reason-service.d.ts +33 -0
  164. package/dist/backend/services/return-reason-service.d.ts.map +1 -0
  165. package/dist/backend/services/return-reason-service.js +90 -0
  166. package/dist/backend/services/return-reason-service.js.map +1 -0
  167. package/dist/backend/services/return-settlement-service.d.ts +67 -0
  168. package/dist/backend/services/return-settlement-service.d.ts.map +1 -0
  169. package/dist/backend/services/return-settlement-service.js +295 -0
  170. package/dist/backend/services/return-settlement-service.js.map +1 -0
  171. package/dist/backend/services/return-shipment-service.d.ts +40 -0
  172. package/dist/backend/services/return-shipment-service.d.ts.map +1 -0
  173. package/dist/backend/services/return-shipment-service.js +113 -0
  174. package/dist/backend/services/return-shipment-service.js.map +1 -0
  175. package/dist/backend/services/return-status-graph-service.d.ts +59 -0
  176. package/dist/backend/services/return-status-graph-service.d.ts.map +1 -0
  177. package/dist/backend/services/return-status-graph-service.js +197 -0
  178. package/dist/backend/services/return-status-graph-service.js.map +1 -0
  179. package/dist/backend/services/return-transition-service.d.ts +48 -0
  180. package/dist/backend/services/return-transition-service.d.ts.map +1 -0
  181. package/dist/backend/services/return-transition-service.js +116 -0
  182. package/dist/backend/services/return-transition-service.js.map +1 -0
  183. package/dist/backend/services/returns-seeder.d.ts +18 -0
  184. package/dist/backend/services/returns-seeder.d.ts.map +1 -0
  185. package/dist/backend/services/returns-seeder.js +63 -0
  186. package/dist/backend/services/returns-seeder.js.map +1 -0
  187. package/dist/backend/services/rma-number-generator.d.ts +35 -0
  188. package/dist/backend/services/rma-number-generator.d.ts.map +1 -0
  189. package/dist/backend/services/rma-number-generator.js +45 -0
  190. package/dist/backend/services/rma-number-generator.js.map +1 -0
  191. package/dist/manifest.d.ts +182 -0
  192. package/dist/manifest.d.ts.map +1 -0
  193. package/dist/manifest.js +216 -0
  194. package/dist/manifest.js.map +1 -0
  195. package/dist/migrations/20260625T144227_returns_init.d.ts +15 -0
  196. package/dist/migrations/20260625T144227_returns_init.d.ts.map +1 -0
  197. package/dist/migrations/20260625T144227_returns_init.js +241 -0
  198. package/dist/migrations/20260625T144227_returns_init.js.map +1 -0
  199. package/dist/migrations/20260817T203206_returns_refund_corrective_invoice_outcome.d.ts +25 -0
  200. package/dist/migrations/20260817T203206_returns_refund_corrective_invoice_outcome.d.ts.map +1 -0
  201. package/dist/migrations/20260817T203206_returns_refund_corrective_invoice_outcome.js +46 -0
  202. package/dist/migrations/20260817T203206_returns_refund_corrective_invoice_outcome.js.map +1 -0
  203. package/dist/migrations/index.d.ts +28 -0
  204. package/dist/migrations/index.d.ts.map +1 -0
  205. package/dist/migrations/index.js +31 -0
  206. package/dist/migrations/index.js.map +1 -0
  207. package/docs/returns.md +103 -0
  208. package/i18n/en.json +28 -0
  209. package/i18n/pl.json +28 -0
  210. package/package.json +101 -0
  211. package/tailwind.css +14 -0
@@ -0,0 +1,59 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql';
2
+ import type { AuditPort } from '@endora-commerce/platform/kernel';
3
+ import { ReturnStatusGraph, type ReturnStatusDef, type ReturnTransitionDef } from '../domain/return-status-graph.js';
4
+ /**
5
+ * ReturnStatusGraphService — feature 046 (US3).
6
+ *
7
+ * DB-backed loader + admin CRUD for the configurable RMA lifecycle. Builds an
8
+ * immutable `ReturnStatusGraph` from `return_statuses` + `return_status_transitions`
9
+ * and caches it in-process; every mutation invalidates the cache. Mirrors
10
+ * `OrderStatusGraphService`.
11
+ */
12
+ export declare class ReturnStatusGraphService {
13
+ #private;
14
+ private readonly emFactory;
15
+ /** Feature 054 — audits status-graph writes co-transactionally when provided. */
16
+ private readonly auditLog?;
17
+ private cached;
18
+ constructor(emFactory: () => EntityManager,
19
+ /** Feature 054 — audits status-graph writes co-transactionally when provided. */
20
+ auditLog?: AuditPort | undefined);
21
+ invalidate(): void;
22
+ /** Build (or return cached) the validated graph. */
23
+ loadGraph(): Promise<ReturnStatusGraph>;
24
+ hasStatus(code: string): Promise<boolean>;
25
+ /** The graph plus per-status `inUseCount` for the admin config view. */
26
+ listGraph(): Promise<{
27
+ statuses: Array<ReturnStatusDef & {
28
+ inUseCount: number;
29
+ }>;
30
+ transitions: ReturnTransitionDef[];
31
+ }>;
32
+ private statusUsageCounts;
33
+ createStatus(input: {
34
+ code: string;
35
+ name: Record<string, string>;
36
+ defaultName: string;
37
+ isTerminal?: boolean | undefined;
38
+ weight?: number | undefined;
39
+ color?: string | undefined;
40
+ }): Promise<void>;
41
+ updateStatus(code: string, patch: {
42
+ name?: Record<string, string> | undefined;
43
+ defaultName?: string | undefined;
44
+ isTerminal?: boolean | undefined;
45
+ weight?: number | undefined;
46
+ color?: string | undefined;
47
+ }): Promise<void>;
48
+ deleteStatus(code: string): Promise<void>;
49
+ /**
50
+ * Replace the full transition set (PUT semantics). Validates every pair
51
+ * against the current statuses and the no-edge-from-terminal invariant, then
52
+ * swaps the persisted rows.
53
+ */
54
+ replaceTransitions(pairs: Array<{
55
+ fromStatusCode: string;
56
+ toStatusCode: string;
57
+ }>): Promise<void>;
58
+ }
59
+ //# sourceMappingURL=return-status-graph-service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"return-status-graph-service.d.ts","sourceRoot":"","sources":["../../../src/backend/services/return-status-graph-service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAI3D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kCAAkC,CAAC;AAIlE,OAAO,EAEL,iBAAiB,EACjB,KAAK,eAAe,EACpB,KAAK,mBAAmB,EACzB,MAAM,kCAAkC,CAAC;AAE1C;;;;;;;GAOG;AACH,qBAAa,wBAAwB;;IAIjC,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,iFAAiF;IACjF,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC;IAL5B,OAAO,CAAC,MAAM,CAAkC;gBAG7B,SAAS,EAAE,MAAM,aAAa;IAC/C,iFAAiF;IAChE,QAAQ,CAAC,EAAE,SAAS,YAAA;IAqBvC,UAAU,IAAI,IAAI;IAIlB,oDAAoD;IAC9C,SAAS,IAAI,OAAO,CAAC,iBAAiB,CAAC;IAmBvC,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAI/C,wEAAwE;IAClE,SAAS,IAAI,OAAO,CAAC;QACzB,QAAQ,EAAE,KAAK,CAAC,eAAe,GAAG;YAAE,UAAU,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;QAC1D,WAAW,EAAE,mBAAmB,EAAE,CAAC;KACpC,CAAC;YASY,iBAAiB;IAWzB,YAAY,CAAC,KAAK,EAAE;QACxB,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAC7B,WAAW,EAAE,MAAM,CAAC;QACpB,UAAU,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;QACjC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;QAC5B,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;KAC5B,GAAG,OAAO,CAAC,IAAI,CAAC;IAsBX,YAAY,CAChB,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE;QACL,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAAC;QAC1C,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;QACjC,UAAU,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;QACjC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;QAC5B,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;KAC5B,GACA,OAAO,CAAC,IAAI,CAAC;IA+BV,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAuB/C;;;;OAIG;IACG,kBAAkB,CACtB,KAAK,EAAE,KAAK,CAAC;QAAE,cAAc,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE,CAAC,GAC7D,OAAO,CAAC,IAAI,CAAC;CAsCjB"}
@@ -0,0 +1,197 @@
1
+ import { ERROR_CODES } from '@endora-commerce/contracts';
2
+ import { HttpError } from '@endora-commerce/platform/http';
3
+ import { recordAuditFromContext } from '@endora-commerce/platform/commands';
4
+ import { ReturnStatus } from '../entities/return-status.entity.js';
5
+ import { ReturnStatusTransition } from '../entities/return-status-transition.entity.js';
6
+ import { ReturnCase } from '../entities/return-case.entity.js';
7
+ import { RETURN_STATUS_INITIAL, ReturnStatusGraph, } from '../domain/return-status-graph.js';
8
+ /**
9
+ * ReturnStatusGraphService — feature 046 (US3).
10
+ *
11
+ * DB-backed loader + admin CRUD for the configurable RMA lifecycle. Builds an
12
+ * immutable `ReturnStatusGraph` from `return_statuses` + `return_status_transitions`
13
+ * and caches it in-process; every mutation invalidates the cache. Mirrors
14
+ * `OrderStatusGraphService`.
15
+ */
16
+ export class ReturnStatusGraphService {
17
+ emFactory;
18
+ auditLog;
19
+ cached = null;
20
+ constructor(emFactory,
21
+ /** Feature 054 — audits status-graph writes co-transactionally when provided. */
22
+ auditLog) {
23
+ this.emFactory = emFactory;
24
+ this.auditLog = auditLog;
25
+ }
26
+ #audit(em, action, objectId, stateBefore, stateAfter) {
27
+ if (this.auditLog) {
28
+ recordAuditFromContext(this.auditLog, em, {
29
+ action,
30
+ objectType: 'return_status',
31
+ objectId,
32
+ stateBefore,
33
+ stateAfter,
34
+ });
35
+ }
36
+ }
37
+ invalidate() {
38
+ this.cached = null;
39
+ }
40
+ /** Build (or return cached) the validated graph. */
41
+ async loadGraph() {
42
+ if (this.cached)
43
+ return this.cached;
44
+ const em = this.emFactory();
45
+ const [statuses, transitions] = await Promise.all([
46
+ em.find(ReturnStatus, {}, { orderBy: { weight: 'asc' } }),
47
+ em.find(ReturnStatusTransition, {}),
48
+ ]);
49
+ const graph = new ReturnStatusGraph(statuses.map(toStatusDef), transitions.map((t) => ({
50
+ fromStatusCode: t.fromStatusCode,
51
+ toStatusCode: t.toStatusCode,
52
+ isSystem: t.isSystem,
53
+ })));
54
+ this.cached = graph;
55
+ return graph;
56
+ }
57
+ async hasStatus(code) {
58
+ return (await this.loadGraph()).has(code);
59
+ }
60
+ /** The graph plus per-status `inUseCount` for the admin config view. */
61
+ async listGraph() {
62
+ const graph = await this.loadGraph();
63
+ const counts = await this.statusUsageCounts();
64
+ return {
65
+ statuses: graph.statuses.map((s) => ({ ...s, inUseCount: counts.get(s.code) ?? 0 })),
66
+ transitions: graph.transitions,
67
+ };
68
+ }
69
+ async statusUsageCounts() {
70
+ const em = this.emFactory();
71
+ const rows = await em
72
+ .getKnex()
73
+ .from('return_cases')
74
+ .select('status_code')
75
+ .count('* as count')
76
+ .groupBy('status_code');
77
+ return new Map(rows.map((r) => [r.status_code, Number(r.count)]));
78
+ }
79
+ async createStatus(input) {
80
+ const em = this.emFactory();
81
+ const existing = await em.findOne(ReturnStatus, { code: input.code });
82
+ if (existing) {
83
+ throw new HttpError(409, ERROR_CODES.VALIDATION_FAILED, `Status "${input.code}" already exists.`);
84
+ }
85
+ const status = em.create(ReturnStatus, {
86
+ code: input.code,
87
+ name: input.name,
88
+ defaultName: input.defaultName,
89
+ isInitial: false,
90
+ isTerminal: input.isTerminal ?? false,
91
+ isSystem: false,
92
+ weight: input.weight ?? 100,
93
+ color: input.color ?? '#64748b',
94
+ });
95
+ em.persist(status);
96
+ this.#audit(em, 'return_status.create', input.code, null, { code: input.code, defaultName: input.defaultName });
97
+ await em.flush();
98
+ this.invalidate();
99
+ }
100
+ async updateStatus(code, patch) {
101
+ const em = this.emFactory();
102
+ const status = await em.findOne(ReturnStatus, { code });
103
+ if (!status)
104
+ throw new HttpError(404, ERROR_CODES.NOT_FOUND, `Status "${code}" not found.`);
105
+ if (patch.name !== undefined)
106
+ status.name = patch.name;
107
+ if (patch.defaultName !== undefined)
108
+ status.defaultName = patch.defaultName;
109
+ if (patch.weight !== undefined)
110
+ status.weight = patch.weight;
111
+ if (patch.color !== undefined)
112
+ status.color = patch.color;
113
+ if (patch.isTerminal !== undefined && patch.isTerminal !== status.isTerminal) {
114
+ if (patch.isTerminal) {
115
+ const outgoing = await em.count(ReturnStatusTransition, { fromStatusCode: code });
116
+ if (outgoing > 0) {
117
+ throw new HttpError(409, ERROR_CODES.VALIDATION_FAILED, `Cannot mark "${code}" terminal while it has outgoing transitions.`);
118
+ }
119
+ }
120
+ status.isTerminal = patch.isTerminal;
121
+ }
122
+ this.#audit(em, 'return_status.update', code, null, {
123
+ defaultName: status.defaultName,
124
+ weight: status.weight,
125
+ color: status.color,
126
+ isTerminal: status.isTerminal,
127
+ });
128
+ await em.flush();
129
+ this.invalidate();
130
+ }
131
+ async deleteStatus(code) {
132
+ const em = this.emFactory();
133
+ const status = await em.findOne(ReturnStatus, { code });
134
+ if (!status)
135
+ throw new HttpError(404, ERROR_CODES.NOT_FOUND, `Status "${code}" not found.`);
136
+ if (code === RETURN_STATUS_INITIAL || status.isInitial) {
137
+ throw new HttpError(409, ERROR_CODES.VALIDATION_FAILED, 'The initial status cannot be deleted.');
138
+ }
139
+ const inUse = await em.count(ReturnCase, { statusCode: code });
140
+ if (inUse > 0) {
141
+ throw new HttpError(409, ERROR_CODES.VALIDATION_FAILED, `Cannot delete status "${code}" while ${inUse} case(s) use it.`);
142
+ }
143
+ const edges = await em.find(ReturnStatusTransition, {
144
+ $or: [{ fromStatusCode: code }, { toStatusCode: code }],
145
+ });
146
+ this.#audit(em, 'return_status.delete', code, { code, defaultName: status.defaultName }, null);
147
+ await em.remove(edges).remove(status).flush();
148
+ this.invalidate();
149
+ }
150
+ /**
151
+ * Replace the full transition set (PUT semantics). Validates every pair
152
+ * against the current statuses and the no-edge-from-terminal invariant, then
153
+ * swaps the persisted rows.
154
+ */
155
+ async replaceTransitions(pairs) {
156
+ const em = this.emFactory();
157
+ const graph = await this.loadGraph();
158
+ for (const edge of pairs) {
159
+ if (!graph.has(edge.fromStatusCode) || !graph.has(edge.toStatusCode)) {
160
+ throw new HttpError(422, ERROR_CODES.VALIDATION_FAILED, `Transition references an unknown status: ${edge.fromStatusCode} → ${edge.toStatusCode}.`);
161
+ }
162
+ if (graph.isTerminal(edge.fromStatusCode)) {
163
+ throw new HttpError(422, ERROR_CODES.VALIDATION_FAILED, `Terminal status "${edge.fromStatusCode}" cannot have outgoing transitions.`);
164
+ }
165
+ }
166
+ const existing = await em.find(ReturnStatusTransition, {});
167
+ await em.remove(existing).flush();
168
+ const seen = new Set();
169
+ for (const edge of pairs) {
170
+ const key = `${edge.fromStatusCode} ${edge.toStatusCode}`;
171
+ if (seen.has(key) || edge.fromStatusCode === edge.toStatusCode)
172
+ continue;
173
+ seen.add(key);
174
+ em.persist(em.create(ReturnStatusTransition, {
175
+ fromStatusCode: edge.fromStatusCode,
176
+ toStatusCode: edge.toStatusCode,
177
+ isSystem: false,
178
+ }));
179
+ }
180
+ this.#audit(em, 'return_status.replace_transitions', 'graph', null, { count: seen.size });
181
+ await em.flush();
182
+ this.invalidate();
183
+ }
184
+ }
185
+ function toStatusDef(s) {
186
+ return {
187
+ code: s.code,
188
+ name: s.name,
189
+ defaultName: s.defaultName,
190
+ isInitial: s.isInitial,
191
+ isTerminal: s.isTerminal,
192
+ isSystem: s.isSystem,
193
+ weight: s.weight,
194
+ color: s.color,
195
+ };
196
+ }
197
+ //# sourceMappingURL=return-status-graph-service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"return-status-graph-service.js","sourceRoot":"","sources":["../../../src/backend/services/return-status-graph-service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,48 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql';
2
+ import type { EventBus } from '@endora-commerce/platform/events';
3
+ import type { AuditPort } from '@endora-commerce/platform/kernel';
4
+ import { ReturnCase } from '../entities/return-case.entity.js';
5
+ import type { ReturnStatusGraphService } from './return-status-graph-service.js';
6
+ import { type ReturnStatusActor, type ReturnStatusEvent } from '../events/return-status-events.js';
7
+ type TransitionGuard = (e: ReturnStatusEvent) => void | Promise<void>;
8
+ /** Side-effects applied within the transition, before the after-events fire. */
9
+ export type ReturnTransitionSideEffects = (e: {
10
+ returnCase: ReturnCase;
11
+ from: string;
12
+ to: string;
13
+ em: EntityManager;
14
+ }) => Promise<void>;
15
+ /**
16
+ * ReturnTransitionService — feature 046 (US3).
17
+ *
18
+ * Applies a status transition against the configurable graph and emits the four
19
+ * templated business events. The `.before` events run through a veto-capable
20
+ * guard registry; the `.after` events fire on the EventBus once the write has
21
+ * flushed. Mirrors `OrderTransitionService`.
22
+ */
23
+ export declare class ReturnTransitionService {
24
+ private readonly emFactory;
25
+ private readonly events;
26
+ private readonly graphService;
27
+ private readonly auditLog?;
28
+ private readonly guards;
29
+ constructor(emFactory: () => EntityManager, events: EventBus, graphService: ReturnStatusGraphService, auditLog?: AuditPort | undefined);
30
+ onReturnTransitionGuard(match: {
31
+ from?: string;
32
+ to?: string;
33
+ }, guard: TransitionGuard): () => void;
34
+ /**
35
+ * Transition `returnCaseId` to status code `to`. Throws 404 if the case is
36
+ * missing, 422 for an unknown target, 409 if the transition is not permitted
37
+ * by the graph or a guard vetoes it. `mutate` runs after the graph check and
38
+ * before the status flush (e.g. assign RMA number, set rejection reason).
39
+ */
40
+ apply(returnCaseId: string, to: string, actor: ReturnStatusActor, opts?: {
41
+ reason?: string | null;
42
+ mutate?: (rc: ReturnCase, em: EntityManager) => void | Promise<void>;
43
+ sideEffects?: ReturnTransitionSideEffects;
44
+ }): Promise<ReturnCase>;
45
+ private runBeforeGuards;
46
+ }
47
+ export {};
48
+ //# sourceMappingURL=return-transition-service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"return-transition-service.d.ts","sourceRoot":"","sources":["../../../src/backend/services/return-transition-service.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAE3D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kCAAkC,CAAC;AAEjE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kCAAkC,CAAC;AAClE,OAAO,EAAE,UAAU,EAAE,MAAM,mCAAmC,CAAC;AAC/D,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,kCAAkC,CAAC;AACjF,OAAO,EAIL,KAAK,iBAAiB,EACtB,KAAK,iBAAiB,EACvB,MAAM,mCAAmC,CAAC;AAE3C,KAAK,eAAe,GAAG,CAAC,CAAC,EAAE,iBAAiB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAMtE,gFAAgF;AAChF,MAAM,MAAM,2BAA2B,GAAG,CAAC,CAAC,EAAE;IAC5C,UAAU,EAAE,UAAU,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,EAAE,EAAE,aAAa,CAAC;CACnB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAEpB;;;;;;;GAOG;AACH,qBAAa,uBAAuB;IAIhC,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,YAAY;IAC7B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC;IAN5B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAoB;gBAGxB,SAAS,EAAE,MAAM,aAAa,EAC9B,MAAM,EAAE,QAAQ,EAChB,YAAY,EAAE,wBAAwB,EACtC,QAAQ,CAAC,EAAE,SAAS,YAAA;IAGvC,uBAAuB,CAAC,KAAK,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,EAAE,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,KAAK,EAAE,eAAe,GAAG,MAAM,IAAI;IASlG;;;;;OAKG;IACG,KAAK,CACT,YAAY,EAAE,MAAM,EACpB,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,iBAAiB,EACxB,IAAI,CAAC,EAAE;QACL,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACvB,MAAM,CAAC,EAAE,CAAC,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE,aAAa,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;QACrE,WAAW,CAAC,EAAE,2BAA2B,CAAC;KAC3C,GACA,OAAO,CAAC,UAAU,CAAC;YAkER,eAAe;CAc9B"}
@@ -0,0 +1,116 @@
1
+ import { randomUUID } from 'crypto';
2
+ import { ERROR_CODES } from '@endora-commerce/contracts';
3
+ import { HttpError } from '@endora-commerce/platform/http';
4
+ import { ReturnCase } from '../entities/return-case.entity.js';
5
+ import { emitReturnStatusAfter, returnStatusBeforeEventNames, ReturnTransitionVetoError, } from '../events/return-status-events.js';
6
+ /**
7
+ * ReturnTransitionService — feature 046 (US3).
8
+ *
9
+ * Applies a status transition against the configurable graph and emits the four
10
+ * templated business events. The `.before` events run through a veto-capable
11
+ * guard registry; the `.after` events fire on the EventBus once the write has
12
+ * flushed. Mirrors `OrderTransitionService`.
13
+ */
14
+ export class ReturnTransitionService {
15
+ emFactory;
16
+ events;
17
+ graphService;
18
+ auditLog;
19
+ guards = [];
20
+ constructor(emFactory, events, graphService, auditLog) {
21
+ this.emFactory = emFactory;
22
+ this.events = events;
23
+ this.graphService = graphService;
24
+ this.auditLog = auditLog;
25
+ }
26
+ onReturnTransitionGuard(match, guard) {
27
+ const entry = { match, guard };
28
+ this.guards.push(entry);
29
+ return () => {
30
+ const idx = this.guards.indexOf(entry);
31
+ if (idx >= 0)
32
+ this.guards.splice(idx, 1);
33
+ };
34
+ }
35
+ /**
36
+ * Transition `returnCaseId` to status code `to`. Throws 404 if the case is
37
+ * missing, 422 for an unknown target, 409 if the transition is not permitted
38
+ * by the graph or a guard vetoes it. `mutate` runs after the graph check and
39
+ * before the status flush (e.g. assign RMA number, set rejection reason).
40
+ */
41
+ async apply(returnCaseId, to, actor, opts) {
42
+ const em = this.emFactory();
43
+ const rc = await em.findOne(ReturnCase, { id: returnCaseId });
44
+ if (!rc)
45
+ throw new HttpError(404, ERROR_CODES.NOT_FOUND, 'Return case not found.');
46
+ const from = rc.statusCode;
47
+ if (from === to)
48
+ return rc;
49
+ const graph = await this.graphService.loadGraph();
50
+ if (!graph.has(to)) {
51
+ throw new HttpError(422, ERROR_CODES.VALIDATION_FAILED, `Unknown return status "${to}".`);
52
+ }
53
+ if (!graph.canTransition(from, to)) {
54
+ throw new HttpError(409, ERROR_CODES.INVALID_TRANSITION, `Cannot transition from "${from}" to "${to}".`);
55
+ }
56
+ const event = {
57
+ eventId: randomUUID(),
58
+ occurredAt: new Date().toISOString(),
59
+ returnCaseId: rc.id,
60
+ orderId: rc.orderId,
61
+ organizationId: rc.organizationId ?? null,
62
+ salesChannelId: rc.salesChannelId,
63
+ from,
64
+ to,
65
+ actor,
66
+ reason: opts?.reason ?? null,
67
+ };
68
+ await this.runBeforeGuards(event);
69
+ for (const name of returnStatusBeforeEventNames(from, to)) {
70
+ this.events.emit(name, event);
71
+ }
72
+ if (opts?.mutate)
73
+ await opts.mutate(rc, em);
74
+ rc.statusCode = to;
75
+ await em.flush();
76
+ if (opts?.sideEffects)
77
+ await opts.sideEffects({ returnCase: rc, from, to, em });
78
+ emitReturnStatusAfter(this.events, event);
79
+ // Audit the status change (FR-041); best-effort, never blocks the transition.
80
+ if (this.auditLog) {
81
+ try {
82
+ await this.auditLog.record({
83
+ actorAdminUserId: actor.kind === 'admin' ? (actor.adminUserId ?? null) : null,
84
+ impersonatedCustomerAccountId: actor.kind === 'customer' ? (actor.customerAccountId ?? null) : null,
85
+ action: 'return.status_changed',
86
+ objectType: 'return_case',
87
+ objectId: rc.id,
88
+ stateBefore: { statusCode: from },
89
+ stateAfter: { statusCode: to, reason: opts?.reason ?? null },
90
+ });
91
+ }
92
+ catch {
93
+ // ignore audit failures
94
+ }
95
+ }
96
+ return rc;
97
+ }
98
+ async runBeforeGuards(event) {
99
+ for (const entry of this.guards) {
100
+ if (entry.match.from !== undefined && entry.match.from !== event.from)
101
+ continue;
102
+ if (entry.match.to !== undefined && entry.match.to !== event.to)
103
+ continue;
104
+ try {
105
+ await entry.guard(event);
106
+ }
107
+ catch (err) {
108
+ if (err instanceof ReturnTransitionVetoError) {
109
+ throw new HttpError(409, ERROR_CODES.INVALID_TRANSITION, err.message);
110
+ }
111
+ throw err;
112
+ }
113
+ }
114
+ }
115
+ }
116
+ //# sourceMappingURL=return-transition-service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"return-transition-service.js","sourceRoot":"","sources":["../../../src/backend/services/return-transition-service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql';
2
+ /**
3
+ * ReturnsSeeder — feature 046 (T017).
4
+ *
5
+ * Idempotently ensures the default status graph and reasons exist. The init
6
+ * migration seeds these on a fresh install; this runtime seeder is a no-op when
7
+ * the data is present and restores it when absent (e.g. after a module
8
+ * uninstall/reinstall under the lifecycle), so the module is self-healing. It is
9
+ * invoked once at module boot.
10
+ */
11
+ export declare class ReturnsSeeder {
12
+ private readonly emFactory;
13
+ constructor(emFactory: () => EntityManager);
14
+ ensureDefaults(): Promise<void>;
15
+ private ensureStatusGraph;
16
+ private ensureReasons;
17
+ }
18
+ //# sourceMappingURL=returns-seeder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"returns-seeder.d.ts","sourceRoot":"","sources":["../../../src/backend/services/returns-seeder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAU3D;;;;;;;;GAQG;AACH,qBAAa,aAAa;IACZ,OAAO,CAAC,QAAQ,CAAC,SAAS;gBAAT,SAAS,EAAE,MAAM,aAAa;IAErD,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;YAKvB,iBAAiB;YA+BjB,aAAa;CAU5B"}
@@ -0,0 +1,63 @@
1
+ import { ReturnStatus } from '../entities/return-status.entity.js';
2
+ import { ReturnStatusTransition } from '../entities/return-status-transition.entity.js';
3
+ import { ReturnReason } from '../entities/return-reason.entity.js';
4
+ import { DEFAULT_RETURN_STATUSES, computeDefaultTransitions, } from '../domain/return-status-graph.js';
5
+ import { DEFAULT_RETURN_REASONS } from '../domain/default-reasons.js';
6
+ /**
7
+ * ReturnsSeeder — feature 046 (T017).
8
+ *
9
+ * Idempotently ensures the default status graph and reasons exist. The init
10
+ * migration seeds these on a fresh install; this runtime seeder is a no-op when
11
+ * the data is present and restores it when absent (e.g. after a module
12
+ * uninstall/reinstall under the lifecycle), so the module is self-healing. It is
13
+ * invoked once at module boot.
14
+ */
15
+ export class ReturnsSeeder {
16
+ emFactory;
17
+ constructor(emFactory) {
18
+ this.emFactory = emFactory;
19
+ }
20
+ async ensureDefaults() {
21
+ await this.ensureStatusGraph();
22
+ await this.ensureReasons();
23
+ }
24
+ async ensureStatusGraph() {
25
+ // command-coverage-ignore: idempotent first-run seed of the default status
26
+ // graph (skips when rows exist), a system bootstrap, not an operator write.
27
+ const em = this.emFactory();
28
+ if ((await em.count(ReturnStatus, {})) > 0)
29
+ return;
30
+ for (const s of DEFAULT_RETURN_STATUSES) {
31
+ em.persist(em.create(ReturnStatus, {
32
+ code: s.code,
33
+ name: s.name,
34
+ defaultName: s.defaultName,
35
+ isInitial: s.isInitial,
36
+ isTerminal: s.isTerminal,
37
+ isSystem: s.isSystem,
38
+ weight: s.weight,
39
+ color: s.color,
40
+ }));
41
+ }
42
+ for (const t of computeDefaultTransitions()) {
43
+ em.persist(em.create(ReturnStatusTransition, {
44
+ fromStatusCode: t.fromStatusCode,
45
+ toStatusCode: t.toStatusCode,
46
+ isSystem: t.isSystem,
47
+ }));
48
+ }
49
+ await em.flush();
50
+ }
51
+ async ensureReasons() {
52
+ // command-coverage-ignore: idempotent first-run seed of the default reasons
53
+ // (skips when rows exist), a system bootstrap, not an operator write.
54
+ const em = this.emFactory();
55
+ if ((await em.count(ReturnReason, {})) > 0)
56
+ return;
57
+ for (const r of DEFAULT_RETURN_REASONS) {
58
+ em.persist(em.create(ReturnReason, { label: r.label, appliesTo: r.appliesTo, weight: r.weight }));
59
+ }
60
+ await em.flush();
61
+ }
62
+ }
63
+ //# sourceMappingURL=returns-seeder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"returns-seeder.js","sourceRoot":"","sources":["../../../src/backend/services/returns-seeder.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,qCAAqC,CAAC;AACnE,OAAO,EAAE,sBAAsB,EAAE,MAAM,gDAAgD,CAAC;AACxF,OAAO,EAAE,YAAY,EAAE,MAAM,qCAAqC,CAAC;AACnE,OAAO,EACL,uBAAuB,EACvB,yBAAyB,GAC1B,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EAAE,sBAAsB,EAAE,MAAM,8BAA8B,CAAC;AAEtE;;;;;;;;GAQG;AACH,MAAM,OAAO,aAAa;IACK;IAA7B,YAA6B,SAA8B;QAA9B,cAAS,GAAT,SAAS,CAAqB;IAAG,CAAC;IAE/D,KAAK,CAAC,cAAc;QAClB,MAAM,IAAI,CAAC,iBAAiB,EAAE,CAAC;QAC/B,MAAM,IAAI,CAAC,aAAa,EAAE,CAAC;IAC7B,CAAC;IAEO,KAAK,CAAC,iBAAiB;QAC7B,2EAA2E;QAC3E,4EAA4E;QAC5E,MAAM,EAAE,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC;QAC5B,IAAI,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC;YAAE,OAAO;QACnD,KAAK,MAAM,CAAC,IAAI,uBAAuB,EAAE,CAAC;YACxC,EAAE,CAAC,OAAO,CACR,EAAE,CAAC,MAAM,CAAC,YAAY,EAAE;gBACtB,IAAI,EAAE,CAAC,CAAC,IAAI;gBACZ,IAAI,EAAE,CAAC,CAAC,IAAI;gBACZ,WAAW,EAAE,CAAC,CAAC,WAAW;gBAC1B,SAAS,EAAE,CAAC,CAAC,SAAS;gBACtB,UAAU,EAAE,CAAC,CAAC,UAAU;gBACxB,QAAQ,EAAE,CAAC,CAAC,QAAQ;gBACpB,MAAM,EAAE,CAAC,CAAC,MAAM;gBAChB,KAAK,EAAE,CAAC,CAAC,KAAK;aACf,CAAC,CACH,CAAC;QACJ,CAAC;QACD,KAAK,MAAM,CAAC,IAAI,yBAAyB,EAAE,EAAE,CAAC;YAC5C,EAAE,CAAC,OAAO,CACR,EAAE,CAAC,MAAM,CAAC,sBAAsB,EAAE;gBAChC,cAAc,EAAE,CAAC,CAAC,cAAc;gBAChC,YAAY,EAAE,CAAC,CAAC,YAAY;gBAC5B,QAAQ,EAAE,CAAC,CAAC,QAAQ;aACrB,CAAC,CACH,CAAC;QACJ,CAAC;QACD,MAAM,EAAE,CAAC,KAAK,EAAE,CAAC;IACnB,CAAC;IAEO,KAAK,CAAC,aAAa;QACzB,4EAA4E;QAC5E,sEAAsE;QACtE,MAAM,EAAE,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC;QAC5B,IAAI,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC;YAAE,OAAO;QACnD,KAAK,MAAM,CAAC,IAAI,sBAAsB,EAAE,CAAC;YACvC,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,MAAM,CAAC,YAAY,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC,SAAS,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;QACpG,CAAC;QACD,MAAM,EAAE,CAAC,KAAK,EAAE,CAAC;IACnB,CAAC;CACF"}
@@ -0,0 +1,35 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql';
2
+ /**
3
+ * Resolves the admin-configurable RMA-number prefix/suffix for a Sales Channel.
4
+ * In production these wrap `SettingsService.get('returns.rma_number_*')` (wired
5
+ * in composition); the generator treats any resolver failure as an empty string
6
+ * so authorization never fails because a setting is missing or out of scope.
7
+ */
8
+ export interface RmaNumberSettingsResolver {
9
+ resolvePrefix(salesChannelId: string): Promise<string>;
10
+ resolveSuffix(salesChannelId: string): Promise<string>;
11
+ }
12
+ /** Draws the next value from the monotonic RMA sequence. */
13
+ export type DrawRmaSequence = (tx: EntityManager) => Promise<string>;
14
+ /**
15
+ * Feature 046 — generates the RMA number `${prefix}${sequence}${suffix}`. The
16
+ * numeric core comes from the `return_cases_rma_seq` Postgres sequence
17
+ * (monotonic, never resets), so the number is unique regardless of later
18
+ * prefix/suffix changes. The sequence draw is injected so the generator is
19
+ * unit-testable without a database (mirrors `BusinessIdGenerator`).
20
+ */
21
+ export declare class RmaNumberGenerator {
22
+ private readonly drawSequence;
23
+ private readonly settings?;
24
+ constructor(drawSequence: DrawRmaSequence, settings?: RmaNumberSettingsResolver | undefined);
25
+ generate(tx: EntityManager, salesChannelId: string): Promise<string>;
26
+ /** Resolve a prefix/suffix, swallowing missing/out-of-scope settings as ''. */
27
+ private resolve;
28
+ }
29
+ /**
30
+ * Production factory: draws `nextval('return_cases_rma_seq')` on the
31
+ * transaction's connection so the value is allocated inside the authorize
32
+ * transaction without row locking.
33
+ */
34
+ export declare function createRmaNumberGenerator(settings?: RmaNumberSettingsResolver): RmaNumberGenerator;
35
+ //# sourceMappingURL=rma-number-generator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rma-number-generator.d.ts","sourceRoot":"","sources":["../../../src/backend/services/rma-number-generator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAE3D;;;;;GAKG;AACH,MAAM,WAAW,yBAAyB;IACxC,aAAa,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACvD,aAAa,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CACxD;AAED,4DAA4D;AAC5D,MAAM,MAAM,eAAe,GAAG,CAAC,EAAE,EAAE,aAAa,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;AAErE;;;;;;GAMG;AACH,qBAAa,kBAAkB;IAE3B,OAAO,CAAC,QAAQ,CAAC,YAAY;IAC7B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC;gBADT,YAAY,EAAE,eAAe,EAC7B,QAAQ,CAAC,EAAE,yBAAyB,YAAA;IAGjD,QAAQ,CAAC,EAAE,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAO1E,+EAA+E;YACjE,OAAO;CAUtB;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CACtC,QAAQ,CAAC,EAAE,yBAAyB,GACnC,kBAAkB,CAQpB"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Feature 046 — generates the RMA number `${prefix}${sequence}${suffix}`. The
3
+ * numeric core comes from the `return_cases_rma_seq` Postgres sequence
4
+ * (monotonic, never resets), so the number is unique regardless of later
5
+ * prefix/suffix changes. The sequence draw is injected so the generator is
6
+ * unit-testable without a database (mirrors `BusinessIdGenerator`).
7
+ */
8
+ export class RmaNumberGenerator {
9
+ drawSequence;
10
+ settings;
11
+ constructor(drawSequence, settings) {
12
+ this.drawSequence = drawSequence;
13
+ this.settings = settings;
14
+ }
15
+ async generate(tx, salesChannelId) {
16
+ const sequence = await this.drawSequence(tx);
17
+ const prefix = await this.resolve((s) => s.resolvePrefix(salesChannelId));
18
+ const suffix = await this.resolve((s) => s.resolveSuffix(salesChannelId));
19
+ return `${prefix}${sequence}${suffix}`;
20
+ }
21
+ /** Resolve a prefix/suffix, swallowing missing/out-of-scope settings as ''. */
22
+ async resolve(pick) {
23
+ if (!this.settings)
24
+ return '';
25
+ try {
26
+ return (await pick(this.settings)) ?? '';
27
+ }
28
+ catch {
29
+ return '';
30
+ }
31
+ }
32
+ }
33
+ /**
34
+ * Production factory: draws `nextval('return_cases_rma_seq')` on the
35
+ * transaction's connection so the value is allocated inside the authorize
36
+ * transaction without row locking.
37
+ */
38
+ export function createRmaNumberGenerator(settings) {
39
+ const draw = async (tx) => {
40
+ const rows = (await tx.execute(`select nextval('return_cases_rma_seq') as n`));
41
+ return String(rows[0]?.n ?? '');
42
+ };
43
+ return new RmaNumberGenerator(draw, settings);
44
+ }
45
+ //# sourceMappingURL=rma-number-generator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rma-number-generator.js","sourceRoot":"","sources":["../../../src/backend/services/rma-number-generator.ts"],"names":[],"mappings":"AAgBA;;;;;;GAMG;AACH,MAAM,OAAO,kBAAkB;IAEV;IACA;IAFnB,YACmB,YAA6B,EAC7B,QAAoC;QADpC,iBAAY,GAAZ,YAAY,CAAiB;QAC7B,aAAQ,GAAR,QAAQ,CAA4B;IACpD,CAAC;IAEJ,KAAK,CAAC,QAAQ,CAAC,EAAiB,EAAE,cAAsB;QACtD,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC;QAC7C,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC,cAAc,CAAC,CAAC,CAAC;QAC1E,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC,cAAc,CAAC,CAAC,CAAC;QAC1E,OAAO,GAAG,MAAM,GAAG,QAAQ,GAAG,MAAM,EAAE,CAAC;IACzC,CAAC;IAED,+EAA+E;IACvE,KAAK,CAAC,OAAO,CACnB,IAAuD;QAEvD,IAAI,CAAC,IAAI,CAAC,QAAQ;YAAE,OAAO,EAAE,CAAC;QAC9B,IAAI,CAAC;YACH,OAAO,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,IAAI,EAAE,CAAC;QAC3C,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,EAAE,CAAC;QACZ,CAAC;IACH,CAAC;CACF;AAED;;;;GAIG;AACH,MAAM,UAAU,wBAAwB,CACtC,QAAoC;IAEpC,MAAM,IAAI,GAAoB,KAAK,EAAE,EAAE,EAAE,EAAE;QACzC,MAAM,IAAI,GAAG,CAAC,MAAM,EAAE,CAAC,OAAO,CAC5B,6CAA6C,CAC9C,CAAkC,CAAC;QACpC,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC;IAClC,CAAC,CAAC;IACF,OAAO,IAAI,kBAAkB,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;AAChD,CAAC"}