@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,90 @@
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 { ReturnReason } from '../entities/return-reason.entity.js';
5
+ /**
6
+ * ReturnReasonService — feature 046 (US7, FR-030).
7
+ *
8
+ * Manages the return/complaint reason list. The storefront form reads the active
9
+ * reasons (optionally filtered by case kind) in `weight` order.
10
+ */
11
+ export class ReturnReasonService {
12
+ emFactory;
13
+ auditLog;
14
+ constructor(emFactory, auditLog) {
15
+ this.emFactory = emFactory;
16
+ this.auditLog = auditLog;
17
+ }
18
+ #audit(em, action, objectId, stateBefore, stateAfter) {
19
+ if (this.auditLog) {
20
+ recordAuditFromContext(this.auditLog, em, {
21
+ action,
22
+ objectType: 'return_reason',
23
+ objectId,
24
+ stateBefore,
25
+ stateAfter,
26
+ });
27
+ }
28
+ }
29
+ /** All reasons (admin view), ordered by weight. */
30
+ async listAll() {
31
+ const em = this.emFactory();
32
+ const rows = await em.find(ReturnReason, {}, { orderBy: { weight: 'asc' } });
33
+ return rows.map(toDto);
34
+ }
35
+ /** Active reasons for a case kind (storefront), ordered by weight. */
36
+ async listActive(kind) {
37
+ const em = this.emFactory();
38
+ const rows = await em.find(ReturnReason, { isActive: true }, { orderBy: { weight: 'asc' } });
39
+ const applicable = kind
40
+ ? rows.filter((r) => r.appliesTo === kind || r.appliesTo === 'both')
41
+ : rows;
42
+ return applicable.map(toDto);
43
+ }
44
+ async create(input) {
45
+ const em = this.emFactory();
46
+ const row = em.create(ReturnReason, {
47
+ label: input.label,
48
+ appliesTo: input.appliesTo,
49
+ isActive: input.isActive ?? true,
50
+ weight: input.weight ?? 100,
51
+ });
52
+ em.persist(row);
53
+ this.#audit(em, 'return_reason.create', row.id, null, { appliesTo: row.appliesTo, isActive: row.isActive });
54
+ await em.flush();
55
+ return toDto(row);
56
+ }
57
+ async update(id, patch) {
58
+ const em = this.emFactory();
59
+ const row = await em.findOne(ReturnReason, { id });
60
+ if (!row)
61
+ throw new HttpError(404, ERROR_CODES.NOT_FOUND, 'Return reason not found.');
62
+ if (patch.label !== undefined)
63
+ row.label = patch.label;
64
+ if (patch.appliesTo !== undefined)
65
+ row.appliesTo = patch.appliesTo;
66
+ if (patch.isActive !== undefined)
67
+ row.isActive = patch.isActive;
68
+ if (patch.weight !== undefined)
69
+ row.weight = patch.weight;
70
+ this.#audit(em, 'return_reason.update', row.id, null, {
71
+ appliesTo: row.appliesTo,
72
+ isActive: row.isActive,
73
+ weight: row.weight,
74
+ });
75
+ await em.flush();
76
+ return toDto(row);
77
+ }
78
+ async remove(id) {
79
+ const em = this.emFactory();
80
+ const row = await em.findOne(ReturnReason, { id });
81
+ if (!row)
82
+ throw new HttpError(404, ERROR_CODES.NOT_FOUND, 'Return reason not found.');
83
+ this.#audit(em, 'return_reason.delete', row.id, { appliesTo: row.appliesTo }, null);
84
+ await em.removeAndFlush(row);
85
+ }
86
+ }
87
+ function toDto(r) {
88
+ return { id: r.id, label: r.label, appliesTo: r.appliesTo, isActive: r.isActive, weight: r.weight };
89
+ }
90
+ //# sourceMappingURL=return-reason-service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"return-reason-service.js","sourceRoot":"","sources":["../../../src/backend/services/return-reason-service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,67 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql';
2
+ import type { CorrectiveInvoicePort, CreditTopupPort, PaymentRefundPort, SettlementPrefill, SettlementRequest, SettlementResult } from '@endora-commerce/contracts';
3
+ import type { EventBus } from '@endora-commerce/platform/events';
4
+ import type { AuditPort } from '@endora-commerce/platform/kernel';
5
+ import type { ReturnTransitionService } from './return-transition-service.js';
6
+ import type { ReturnStatusGraphService } from './return-status-graph-service.js';
7
+ export interface ReturnSettlementServiceDeps {
8
+ emFactory: () => EntityManager;
9
+ events: EventBus;
10
+ transitions: ReturnTransitionService;
11
+ graphService: ReturnStatusGraphService;
12
+ paymentRefund: PaymentRefundPort;
13
+ correctiveInvoice: CorrectiveInvoicePort;
14
+ creditTopup: CreditTopupPort;
15
+ auditLog?: AuditPort;
16
+ }
17
+ /**
18
+ * ReturnSettlementService — feature 046 (US5).
19
+ *
20
+ * Validates per-line refund amounts (never exceeding the paid amount), advances
21
+ * the case to `resolved`, and orchestrates the financial side-effects through
22
+ * the documented ports: money refund (payments), store credit (credit_limits),
23
+ * and the corrective invoice (invoices). Replacement/repair resolutions move no
24
+ * money.
25
+ *
26
+ * **The ordering law (D-91, issue #157). Every external effect is attempted
27
+ * before any state is written, and the case reaches `resolved` in the same unit
28
+ * of work as the `Refund` row that records what happened.** In order:
29
+ *
30
+ * 1. **Validate** — the graph transition, the refund method, the per-line
31
+ * caps. Nothing is assigned and nothing is flushed.
32
+ * 2. **Attempt every external effect: money, then document.** The gateway
33
+ * refund or the store credit, then the corrective invoice. Outcomes are
34
+ * held in memory. A refusal here — a `ModuleDisabledError` from a
35
+ * switched-off `payments`, `credit_limits` or `invoices`, a gateway
36
+ * rejection — propagates, and because nothing has been written the case is
37
+ * still `received` and the whole settlement is retryable.
38
+ * 3. **Build the `Refund` row from those outcomes**, including its
39
+ * `settlementState` and its provider details.
40
+ * 4. **One unit of work** — the item amounts, the `Refund` row, the audit
41
+ * entry and the transition to `resolved`, in the transition's single flush.
42
+ * Either the case is resolved with a complete record of what happened, or
43
+ * it is untouched.
44
+ * 5. **After commit** — the settled event.
45
+ *
46
+ * The gateway arm has followed this since feature 046 ("a PSP rejection must
47
+ * not leave the RMA looking successfully settled"); the `credit` and
48
+ * corrective-invoice arms called their ports *after* the case was resolved and
49
+ * flushed, so a refusal arrived too late to prevent anything and left "case
50
+ * resolved, refund row absent, money possibly already moved" behind. That is a
51
+ * fact about the relationship between an irreversible effect and a state
52
+ * change, not a fact about PSPs — so it holds for a store credit and for a VAT
53
+ * document, and any arm added later follows it too.
54
+ *
55
+ * The retry the law makes reachable is what makes the two non-gateway effects
56
+ * take a key: `creditFromReturn` applies once per return case and
57
+ * `createCorrection` takes `idempotencyKey: rc.id`, the same key
58
+ * `paymentRefund.refund` has always taken.
59
+ */
60
+ export declare class ReturnSettlementService {
61
+ private readonly deps;
62
+ constructor(deps: ReturnSettlementServiceDeps);
63
+ getPrefill(id: string): Promise<SettlementPrefill>;
64
+ settle(id: string, adminUserId: string, input: SettlementRequest): Promise<SettlementResult>;
65
+ private emitSettled;
66
+ }
67
+ //# sourceMappingURL=return-settlement-service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"return-settlement-service.d.ts","sourceRoot":"","sources":["../../../src/backend/services/return-settlement-service.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAE3D,OAAO,KAAK,EACV,qBAAqB,EACrB,eAAe,EACf,iBAAiB,EACjB,iBAAiB,EACjB,iBAAiB,EACjB,gBAAgB,EACjB,MAAM,4BAA4B,CAAC;AACpC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kCAAkC,CAAC;AACjE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kCAAkC,CAAC;AAOlE,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AAC9E,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,kCAAkC,CAAC;AAEjF,MAAM,WAAW,2BAA2B;IAC1C,SAAS,EAAE,MAAM,aAAa,CAAC;IAC/B,MAAM,EAAE,QAAQ,CAAC;IACjB,WAAW,EAAE,uBAAuB,CAAC;IACrC,YAAY,EAAE,wBAAwB,CAAC;IACvC,aAAa,EAAE,iBAAiB,CAAC;IACjC,iBAAiB,EAAE,qBAAqB,CAAC;IACzC,WAAW,EAAE,eAAe,CAAC;IAC7B,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,qBAAa,uBAAuB;IACtB,OAAO,CAAC,QAAQ,CAAC,IAAI;gBAAJ,IAAI,EAAE,2BAA2B;IAExD,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAkBlD,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,gBAAgB,CAAC;IA8OlG,OAAO,CAAC,WAAW;CAapB"}
@@ -0,0 +1,295 @@
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 { ReturnCaseItem } from '../entities/return-case-item.entity.js';
6
+ import { Refund } from '../entities/refund.entity.js';
7
+ import { RETURN_STATUS_RESOLVED } from '../domain/return-status-graph.js';
8
+ import { exceedsCap, sumApproved } from '../domain/refund-math.js';
9
+ /**
10
+ * ReturnSettlementService — feature 046 (US5).
11
+ *
12
+ * Validates per-line refund amounts (never exceeding the paid amount), advances
13
+ * the case to `resolved`, and orchestrates the financial side-effects through
14
+ * the documented ports: money refund (payments), store credit (credit_limits),
15
+ * and the corrective invoice (invoices). Replacement/repair resolutions move no
16
+ * money.
17
+ *
18
+ * **The ordering law (D-91, issue #157). Every external effect is attempted
19
+ * before any state is written, and the case reaches `resolved` in the same unit
20
+ * of work as the `Refund` row that records what happened.** In order:
21
+ *
22
+ * 1. **Validate** — the graph transition, the refund method, the per-line
23
+ * caps. Nothing is assigned and nothing is flushed.
24
+ * 2. **Attempt every external effect: money, then document.** The gateway
25
+ * refund or the store credit, then the corrective invoice. Outcomes are
26
+ * held in memory. A refusal here — a `ModuleDisabledError` from a
27
+ * switched-off `payments`, `credit_limits` or `invoices`, a gateway
28
+ * rejection — propagates, and because nothing has been written the case is
29
+ * still `received` and the whole settlement is retryable.
30
+ * 3. **Build the `Refund` row from those outcomes**, including its
31
+ * `settlementState` and its provider details.
32
+ * 4. **One unit of work** — the item amounts, the `Refund` row, the audit
33
+ * entry and the transition to `resolved`, in the transition's single flush.
34
+ * Either the case is resolved with a complete record of what happened, or
35
+ * it is untouched.
36
+ * 5. **After commit** — the settled event.
37
+ *
38
+ * The gateway arm has followed this since feature 046 ("a PSP rejection must
39
+ * not leave the RMA looking successfully settled"); the `credit` and
40
+ * corrective-invoice arms called their ports *after* the case was resolved and
41
+ * flushed, so a refusal arrived too late to prevent anything and left "case
42
+ * resolved, refund row absent, money possibly already moved" behind. That is a
43
+ * fact about the relationship between an irreversible effect and a state
44
+ * change, not a fact about PSPs — so it holds for a store credit and for a VAT
45
+ * document, and any arm added later follows it too.
46
+ *
47
+ * The retry the law makes reachable is what makes the two non-gateway effects
48
+ * take a key: `creditFromReturn` applies once per return case and
49
+ * `createCorrection` takes `idempotencyKey: rc.id`, the same key
50
+ * `paymentRefund.refund` has always taken.
51
+ */
52
+ export class ReturnSettlementService {
53
+ deps;
54
+ constructor(deps) {
55
+ this.deps = deps;
56
+ }
57
+ async getPrefill(id) {
58
+ const em = this.deps.emFactory();
59
+ const rc = await em.findOne(ReturnCase, { id });
60
+ if (!rc)
61
+ throw new HttpError(404, ERROR_CODES.NOT_FOUND, 'Return case not found.');
62
+ const items = await em.find(ReturnCaseItem, { returnCaseId: id });
63
+ return {
64
+ currency: rc.currency,
65
+ resolutionOptions: ['refund', 'credit', 'replacement', 'repair'],
66
+ items: items.map((it) => ({
67
+ returnCaseItemId: it.id,
68
+ productName: it.productName,
69
+ quantity: it.quantity,
70
+ defaultRefundAmount: Number(it.defaultRefundAmount),
71
+ approvedRefundAmount: Number(it.approvedRefundAmount),
72
+ })),
73
+ };
74
+ }
75
+ async settle(id, adminUserId, input) {
76
+ const em = this.deps.emFactory();
77
+ const rc = await em.findOne(ReturnCase, { id });
78
+ if (!rc)
79
+ throw new HttpError(404, ERROR_CODES.NOT_FOUND, 'Return case not found.');
80
+ // Fail fast if the case cannot reach `resolved` before mutating anything.
81
+ const graph = await this.deps.graphService.loadGraph();
82
+ if (!graph.canTransition(rc.statusCode, RETURN_STATUS_RESOLVED)) {
83
+ throw new HttpError(409, ERROR_CODES.INVALID_TRANSITION, `Cannot settle a case in status "${rc.statusCode}".`);
84
+ }
85
+ if (input.resolutionType === 'refund' && !input.refundPaymentMethodId) {
86
+ throw new HttpError(422, ERROR_CODES.VALIDATION_FAILED, 'A refund payment method is required.', {
87
+ code: 'payment_method_required',
88
+ });
89
+ }
90
+ // Step 1 — validate. The approved amounts are computed into a map and
91
+ // **not** assigned: an assignment on a managed entity is a write the next
92
+ // flush carries, and under the ordering law nothing is written until step 4.
93
+ const items = await em.find(ReturnCaseItem, { returnCaseId: id });
94
+ const byId = new Map(items.map((it) => [it.id, it]));
95
+ const approvedByItemId = new Map();
96
+ const perLine = [];
97
+ for (const line of input.lines) {
98
+ const item = byId.get(line.returnCaseItemId);
99
+ if (!item) {
100
+ throw new HttpError(422, ERROR_CODES.VALIDATION_FAILED, 'Unknown return line.', {
101
+ code: 'unknown_line',
102
+ });
103
+ }
104
+ if (exceedsCap(line.approvedRefundAmount, Number(item.defaultRefundAmount))) {
105
+ throw new HttpError(422, ERROR_CODES.VALIDATION_FAILED, 'Refund amount cannot exceed the amount paid for the line.', { code: 'refund_exceeds_paid' });
106
+ }
107
+ approvedByItemId.set(line.returnCaseItemId, line.approvedRefundAmount);
108
+ perLine.push(line.approvedRefundAmount);
109
+ }
110
+ const total = sumApproved(perLine);
111
+ const movesMoney = input.resolutionType === 'refund' || input.resolutionType === 'credit';
112
+ // Step 2 — every external effect, money first, then the document. A refusal
113
+ // from any of them propagates with the case still in `received` and nothing
114
+ // written, which is what makes the settlement retryable.
115
+ let gatewayOutcome = null;
116
+ let creditApplied = null;
117
+ let correction = null;
118
+ if (movesMoney) {
119
+ if (input.resolutionType === 'refund') {
120
+ gatewayOutcome = await this.deps.paymentRefund.refund({
121
+ orderId: rc.orderId,
122
+ amount: total,
123
+ currency: rc.currency,
124
+ ...(input.refundPaymentMethodId ? { paymentMethodId: input.refundPaymentMethodId } : {}),
125
+ idempotencyKey: rc.id,
126
+ });
127
+ if (gatewayOutcome.state === 'failed') {
128
+ throw new HttpError(502, ERROR_CODES.VALIDATION_FAILED, gatewayOutcome.failureReason ?? 'Payment gateway rejected the refund.', {
129
+ code: 'gateway_refund_failed',
130
+ settlementState: gatewayOutcome.state,
131
+ providerDetails: gatewayOutcome.providerDetails ?? null,
132
+ });
133
+ }
134
+ }
135
+ else {
136
+ // `credit_limits` applies once per return case, so this survives the
137
+ // retry the law above makes reachable.
138
+ const credit = rc.organizationId
139
+ ? await this.deps.creditTopup.creditFromReturn({
140
+ organizationId: rc.organizationId,
141
+ amount: total,
142
+ currency: rc.currency,
143
+ returnCaseId: rc.id,
144
+ })
145
+ : { applied: false };
146
+ creditApplied = credit.applied;
147
+ }
148
+ if (input.createCorrectiveInvoice ?? true) {
149
+ correction = await this.deps.correctiveInvoice.createCorrection({
150
+ orderId: rc.orderId,
151
+ lines: items.map((it) => ({
152
+ // The order item is what ties this credit to the invoice line it
153
+ // corrects, so the correction can mirror that line's VAT rate.
154
+ orderItemId: it.orderItemId,
155
+ productName: it.productName,
156
+ quantity: it.quantity,
157
+ // The approved amount for this settlement, which is not on the row
158
+ // yet: the rows are written in step 4.
159
+ amount: approvedByItemId.get(it.id) ?? Number(it.approvedRefundAmount),
160
+ })),
161
+ total,
162
+ currency: rc.currency,
163
+ // One correction per return case, however often the settlement is
164
+ // retried. Not per order: a second partial return against the same
165
+ // order is a different case and gets its own document.
166
+ idempotencyKey: rc.id,
167
+ });
168
+ }
169
+ }
170
+ // Step 3 — the outcomes, as the values the row and the response carry.
171
+ const result = { totalRefundAmount: total };
172
+ const refundFields = movesMoney
173
+ ? {
174
+ returnCaseId: id,
175
+ resolutionType: (input.resolutionType === 'refund' ? 'refund' : 'credit'),
176
+ amount: total.toFixed(2),
177
+ currency: rc.currency,
178
+ paymentMethodId: input.refundPaymentMethodId ?? null,
179
+ settlementState: 'pending_manual',
180
+ externalReference: null,
181
+ providerDetails: null,
182
+ failureReason: null,
183
+ creditLimitTopupApplied: false,
184
+ correctiveInvoiceId: null,
185
+ // D-92 — the three-way answer on the row, so it survives a page
186
+ // reload. `not_requested` is the default because the caller can
187
+ // switch the correction off; the two other values are set below,
188
+ // inside the branch that asked for one.
189
+ correctiveInvoiceOutcome: 'not_requested',
190
+ }
191
+ : null;
192
+ if (refundFields) {
193
+ if (gatewayOutcome) {
194
+ refundFields.settlementState = gatewayOutcome.state;
195
+ refundFields.externalReference = gatewayOutcome.externalReference ?? null;
196
+ refundFields.providerDetails = gatewayOutcome.providerDetails ?? null;
197
+ refundFields.failureReason = gatewayOutcome.failureReason ?? null;
198
+ result.refund = {
199
+ settlementState: gatewayOutcome.state,
200
+ externalReference: gatewayOutcome.externalReference ?? null,
201
+ failureReason: gatewayOutcome.failureReason ?? null,
202
+ };
203
+ }
204
+ else {
205
+ refundFields.settlementState = creditApplied ? 'issued' : 'pending_manual';
206
+ refundFields.creditLimitTopupApplied = creditApplied ?? false;
207
+ result.creditLimitTopupApplied = creditApplied ?? false;
208
+ result.refund = { settlementState: refundFields.settlementState };
209
+ }
210
+ if (correction) {
211
+ // An order that was never invoiced has nothing to correct (#135). The
212
+ // settlement still stands — the refund is recorded on the return case
213
+ // and on the payment record — and the result says which of the two
214
+ // happened, because a silent absence would read as a lost document.
215
+ if (correction.issued) {
216
+ refundFields.correctiveInvoiceId = correction.invoiceId;
217
+ refundFields.correctiveInvoiceOutcome = 'issued';
218
+ result.correctiveInvoiceId = correction.invoiceId;
219
+ result.correctiveInvoice = {
220
+ issued: true,
221
+ invoiceId: correction.invoiceId,
222
+ number: correction.number,
223
+ };
224
+ }
225
+ else {
226
+ refundFields.correctiveInvoiceOutcome = 'not_due';
227
+ result.correctiveInvoiceId = null;
228
+ result.correctiveInvoice = { issued: false, reason: correction.reason };
229
+ }
230
+ }
231
+ }
232
+ // Step 4 — one unit of work. `transitions.apply` flushes once, so the item
233
+ // amounts, the `Refund` row, the audit entry and the resolution commit
234
+ // together or not at all.
235
+ let refund = null;
236
+ await this.deps.transitions.apply(id, RETURN_STATUS_RESOLVED, { kind: 'admin', adminUserId, source: 'settlement' }, {
237
+ mutate: async (target, tx) => {
238
+ target.resolutionType = input.resolutionType;
239
+ target.refundPaymentMethodId = input.refundPaymentMethodId ?? null;
240
+ target.totalRefundAmount = total.toFixed(2);
241
+ target.resolvedAt = new Date();
242
+ for (const it of await tx.find(ReturnCaseItem, { returnCaseId: id })) {
243
+ const approved = approvedByItemId.get(it.id);
244
+ if (approved !== undefined)
245
+ it.approvedRefundAmount = approved.toFixed(2);
246
+ }
247
+ if (refundFields) {
248
+ refund = tx.create(Refund, refundFields);
249
+ tx.persist(refund);
250
+ }
251
+ // The audit entry is part of the write, not a best-effort call after
252
+ // it: it used to sit behind `catch { /* ignore audit failures */ }`,
253
+ // which is a comment rather than a decision (Principle XIII's
254
+ // co-transactional clause).
255
+ this.deps.auditLog?.recordWithin(tx, {
256
+ actorAdminUserId: adminUserId,
257
+ action: 'return.settled',
258
+ objectType: 'return_case',
259
+ objectId: rc.id,
260
+ stateAfter: {
261
+ resolutionType: input.resolutionType,
262
+ amount: total.toFixed(2),
263
+ currency: rc.currency,
264
+ settlementState: refundFields?.settlementState ?? null,
265
+ // Says which of the three happened — issued, not due, or never
266
+ // asked for — so "no corrective invoice" is readable after the
267
+ // fact instead of being an absent field (#135).
268
+ correctiveInvoice: result.correctiveInvoice
269
+ ? result.correctiveInvoice.issued
270
+ ? 'issued'
271
+ : result.correctiveInvoice.reason
272
+ : 'not_requested',
273
+ },
274
+ });
275
+ },
276
+ });
277
+ // Step 5 — after commit.
278
+ this.emitSettled(rc, refund, total);
279
+ return result;
280
+ }
281
+ emitSettled(rc, refund, total) {
282
+ const payload = {
283
+ eventId: randomUUID(),
284
+ occurredAt: new Date().toISOString(),
285
+ returnCaseId: rc.id,
286
+ refundId: refund?.id ?? null,
287
+ resolutionType: rc.resolutionType,
288
+ amount: total,
289
+ currency: rc.currency,
290
+ settlementState: refund?.settlementState ?? null,
291
+ };
292
+ this.deps.events.emit('return.refund.settled.v1', payload);
293
+ }
294
+ }
295
+ //# sourceMappingURL=return-settlement-service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"return-settlement-service.js","sourceRoot":"","sources":["../../../src/backend/services/return-settlement-service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,40 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql';
2
+ import type { ReturnShipmentDto } from '@endora-commerce/contracts';
3
+ import type { AuditPort } from '@endora-commerce/platform/kernel';
4
+ import type { ReturnTransitionService } from './return-transition-service.js';
5
+ export interface ReturnShipmentServiceDeps {
6
+ emFactory: () => EntityManager;
7
+ transitions: ReturnTransitionService;
8
+ /** Feature 054 — audits shipment writes co-transactionally when provided. */
9
+ auditLog?: AuditPort;
10
+ }
11
+ /**
12
+ * ReturnShipmentService — feature 046 (US6).
13
+ *
14
+ * Records inbound return shipments (customer → shop) and replacement shipments
15
+ * (shop → customer) against a case. Receiving an inbound shipment drives the
16
+ * case `authorized → received` (FR-024).
17
+ */
18
+ export declare class ReturnShipmentService {
19
+ #private;
20
+ private readonly deps;
21
+ constructor(deps: ReturnShipmentServiceDeps);
22
+ listForCase(caseId: string): Promise<ReturnShipmentDto[]>;
23
+ create(caseId: string, input: {
24
+ direction: 'inbound' | 'replacement';
25
+ deliveryMethodId?: string | undefined;
26
+ externalReference?: string | undefined;
27
+ }): Promise<ReturnShipmentDto>;
28
+ /**
29
+ * Mark an inbound shipment received and advance the case to `received`.
30
+ *
31
+ * The case read comes first, and that ordering is the point rather than a
32
+ * tidiness: the shipment row is flushed before the transition is attempted,
33
+ * so a caller that reached the transition's own refusal had already written
34
+ * `received` onto a case in an organization it cannot see, and been told 404.
35
+ * Both ids on this route are the caller's, and neither entity carries a
36
+ * filter.
37
+ */
38
+ receive(caseId: string, shipmentId: string, adminUserId: string): Promise<ReturnShipmentDto>;
39
+ }
40
+ //# sourceMappingURL=return-shipment-service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"return-shipment-service.d.ts","sourceRoot":"","sources":["../../../src/backend/services/return-shipment-service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAE3D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAGpE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kCAAkC,CAAC;AAIlE,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AAE9E,MAAM,WAAW,yBAAyB;IACxC,SAAS,EAAE,MAAM,aAAa,CAAC;IAC/B,WAAW,EAAE,uBAAuB,CAAC;IACrC,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB;AAED;;;;;;GAMG;AACH,qBAAa,qBAAqB;;IACpB,OAAO,CAAC,QAAQ,CAAC,IAAI;gBAAJ,IAAI,EAAE,yBAAyB;IAoBtD,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,EAAE,CAAC;IAOzD,MAAM,CACV,MAAM,EAAE,MAAM,EACd,KAAK,EAAE;QACL,SAAS,EAAE,SAAS,GAAG,aAAa,CAAC;QACrC,gBAAgB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;QACtC,iBAAiB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;KACxC,GACA,OAAO,CAAC,iBAAiB,CAAC;IAwB7B;;;;;;;;;OASG;IACG,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC;CAyBnG"}
@@ -0,0 +1,113 @@
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 { ReturnCase } from '../entities/return-case.entity.js';
5
+ import { ReturnShipment } from '../entities/return-shipment.entity.js';
6
+ import { RETURN_STATUS_RECEIVED } from '../domain/return-status-graph.js';
7
+ /**
8
+ * ReturnShipmentService — feature 046 (US6).
9
+ *
10
+ * Records inbound return shipments (customer → shop) and replacement shipments
11
+ * (shop → customer) against a case. Receiving an inbound shipment drives the
12
+ * case `authorized → received` (FR-024).
13
+ */
14
+ export class ReturnShipmentService {
15
+ deps;
16
+ constructor(deps) {
17
+ this.deps = deps;
18
+ }
19
+ /**
20
+ * Resolve the case every method below is keyed on.
21
+ *
22
+ * `ReturnShipment` is `@GlobalEntity` — no organization column, no filter —
23
+ * so a read keyed on `returnCaseId` answers for every case on the platform.
24
+ * `ReturnCase` is `@OrgScoped`, so this read *is* the tenant boundary: an
25
+ * assignment-scoped administrator gets nothing back and the caller stops.
26
+ *
27
+ * `create` has made this read since it was written, for its own reasons;
28
+ * `listForCase` and `receive` had no reason of their own to make it, which is
29
+ * exactly how they came to have none.
30
+ */
31
+ async #loadCase(em, caseId) {
32
+ const rc = await em.findOne(ReturnCase, { id: caseId });
33
+ if (!rc)
34
+ throw new HttpError(404, ERROR_CODES.NOT_FOUND, 'Return case not found.');
35
+ return rc;
36
+ }
37
+ async listForCase(caseId) {
38
+ const em = this.deps.emFactory();
39
+ await this.#loadCase(em, caseId);
40
+ const rows = await em.find(ReturnShipment, { returnCaseId: caseId }, { orderBy: { createdAt: 'asc' } });
41
+ return rows.map(toDto);
42
+ }
43
+ async create(caseId, input) {
44
+ const em = this.deps.emFactory();
45
+ await this.#loadCase(em, caseId);
46
+ const shipment = em.create(ReturnShipment, {
47
+ returnCaseId: caseId,
48
+ direction: input.direction,
49
+ deliveryMethodId: input.deliveryMethodId ?? null,
50
+ externalReference: input.externalReference ?? null,
51
+ status: 'pending',
52
+ });
53
+ em.persist(shipment);
54
+ if (this.deps.auditLog) {
55
+ recordAuditFromContext(this.deps.auditLog, em, {
56
+ action: 'return_shipment.create',
57
+ objectType: 'return_shipment',
58
+ objectId: shipment.id,
59
+ stateBefore: null,
60
+ stateAfter: { returnCaseId: caseId, direction: shipment.direction },
61
+ });
62
+ }
63
+ await em.flush();
64
+ return toDto(shipment);
65
+ }
66
+ /**
67
+ * Mark an inbound shipment received and advance the case to `received`.
68
+ *
69
+ * The case read comes first, and that ordering is the point rather than a
70
+ * tidiness: the shipment row is flushed before the transition is attempted,
71
+ * so a caller that reached the transition's own refusal had already written
72
+ * `received` onto a case in an organization it cannot see, and been told 404.
73
+ * Both ids on this route are the caller's, and neither entity carries a
74
+ * filter.
75
+ */
76
+ async receive(caseId, shipmentId, adminUserId) {
77
+ const em = this.deps.emFactory();
78
+ await this.#loadCase(em, caseId);
79
+ const shipment = await em.findOne(ReturnShipment, { id: shipmentId, returnCaseId: caseId });
80
+ if (!shipment)
81
+ throw new HttpError(404, ERROR_CODES.NOT_FOUND, 'Return shipment not found.');
82
+ shipment.status = 'received';
83
+ if (this.deps.auditLog) {
84
+ recordAuditFromContext(this.deps.auditLog, em, {
85
+ action: 'return_shipment.receive',
86
+ objectType: 'return_shipment',
87
+ objectId: shipment.id,
88
+ stateBefore: { status: 'pending' },
89
+ stateAfter: { status: 'received' },
90
+ });
91
+ }
92
+ await em.flush();
93
+ if (shipment.direction === 'inbound') {
94
+ await this.deps.transitions.apply(caseId, RETURN_STATUS_RECEIVED, {
95
+ kind: 'admin',
96
+ adminUserId,
97
+ source: 'shipment',
98
+ });
99
+ }
100
+ return toDto(shipment);
101
+ }
102
+ }
103
+ function toDto(s) {
104
+ return {
105
+ id: s.id,
106
+ direction: s.direction,
107
+ deliveryMethodId: s.deliveryMethodId ?? null,
108
+ externalReference: s.externalReference ?? null,
109
+ status: s.status,
110
+ createdAt: s.createdAt.toISOString(),
111
+ };
112
+ }
113
+ //# sourceMappingURL=return-shipment-service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"return-shipment-service.js","sourceRoot":"","sources":["../../../src/backend/services/return-shipment-service.ts"],"names":[],"mappings":"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"}