@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.
- package/LICENSE +21 -0
- package/README.md +60 -0
- package/dist/admin/api/returns-client.d.ts +69 -0
- package/dist/admin/api/returns-client.d.ts.map +1 -0
- package/dist/admin/api/returns-client.js +36 -0
- package/dist/admin/api/returns-client.js.map +1 -0
- package/dist/admin/index.d.ts +37 -0
- package/dist/admin/index.d.ts.map +1 -0
- package/dist/admin/index.js +57 -0
- package/dist/admin/index.js.map +1 -0
- package/dist/admin/pages/ReturnDeliveryMethodsPage.d.ts +5 -0
- package/dist/admin/pages/ReturnDeliveryMethodsPage.d.ts.map +1 -0
- package/dist/admin/pages/ReturnDeliveryMethodsPage.js +44 -0
- package/dist/admin/pages/ReturnDeliveryMethodsPage.js.map +1 -0
- package/dist/admin/pages/ReturnDetail.d.ts +5 -0
- package/dist/admin/pages/ReturnDetail.d.ts.map +1 -0
- package/dist/admin/pages/ReturnDetail.js +115 -0
- package/dist/admin/pages/ReturnDetail.js.map +1 -0
- package/dist/admin/pages/ReturnReasonsPage.d.ts +5 -0
- package/dist/admin/pages/ReturnReasonsPage.d.ts.map +1 -0
- package/dist/admin/pages/ReturnReasonsPage.js +44 -0
- package/dist/admin/pages/ReturnReasonsPage.js.map +1 -0
- package/dist/admin/pages/ReturnStatusesConfigPage.d.ts +5 -0
- package/dist/admin/pages/ReturnStatusesConfigPage.d.ts.map +1 -0
- package/dist/admin/pages/ReturnStatusesConfigPage.js +71 -0
- package/dist/admin/pages/ReturnStatusesConfigPage.js.map +1 -0
- package/dist/admin/pages/ReturnsList.d.ts +5 -0
- package/dist/admin/pages/ReturnsList.d.ts.map +1 -0
- package/dist/admin/pages/ReturnsList.js +189 -0
- package/dist/admin/pages/ReturnsList.js.map +1 -0
- package/dist/backend/domain/default-reasons.d.ts +14 -0
- package/dist/backend/domain/default-reasons.d.ts.map +1 -0
- package/dist/backend/domain/default-reasons.js +9 -0
- package/dist/backend/domain/default-reasons.js.map +1 -0
- package/dist/backend/domain/free-return-window.d.ts +2 -0
- package/dist/backend/domain/free-return-window.d.ts.map +1 -0
- package/dist/backend/domain/free-return-window.js +19 -0
- package/dist/backend/domain/free-return-window.js.map +1 -0
- package/dist/backend/domain/refund-math.d.ts +19 -0
- package/dist/backend/domain/refund-math.d.ts.map +1 -0
- package/dist/backend/domain/refund-math.js +27 -0
- package/dist/backend/domain/refund-math.js.map +1 -0
- package/dist/backend/domain/return-status-graph.d.ts +91 -0
- package/dist/backend/domain/return-status-graph.d.ts.map +1 -0
- package/dist/backend/domain/return-status-graph.js +143 -0
- package/dist/backend/domain/return-status-graph.js.map +1 -0
- package/dist/backend/email-templates/return-authorized.d.ts +11 -0
- package/dist/backend/email-templates/return-authorized.d.ts.map +1 -0
- package/dist/backend/email-templates/return-authorized.js +18 -0
- package/dist/backend/email-templates/return-authorized.js.map +1 -0
- package/dist/backend/email-templates/return-rejected.d.ts +11 -0
- package/dist/backend/email-templates/return-rejected.d.ts.map +1 -0
- package/dist/backend/email-templates/return-rejected.js +20 -0
- package/dist/backend/email-templates/return-rejected.js.map +1 -0
- package/dist/backend/email-templates/transactional-defaults.d.ts +11 -0
- package/dist/backend/email-templates/transactional-defaults.d.ts.map +1 -0
- package/dist/backend/email-templates/transactional-defaults.js +64 -0
- package/dist/backend/email-templates/transactional-defaults.js.map +1 -0
- package/dist/backend/entities/refund.entity.d.ts +41 -0
- package/dist/backend/entities/refund.entity.d.ts.map +1 -0
- package/dist/backend/entities/refund.entity.js +122 -0
- package/dist/backend/entities/refund.entity.js.map +1 -0
- package/dist/backend/entities/return-case-attachment.entity.d.ts +16 -0
- package/dist/backend/entities/return-case-attachment.entity.d.ts.map +1 -0
- package/dist/backend/entities/return-case-attachment.entity.js +53 -0
- package/dist/backend/entities/return-case-attachment.entity.js.map +1 -0
- package/dist/backend/entities/return-case-comment.entity.d.ts +20 -0
- package/dist/backend/entities/return-case-comment.entity.d.ts.map +1 -0
- package/dist/backend/entities/return-case-comment.entity.js +70 -0
- package/dist/backend/entities/return-case-comment.entity.js.map +1 -0
- package/dist/backend/entities/return-case-item.entity.d.ts +25 -0
- package/dist/backend/entities/return-case-item.entity.d.ts.map +1 -0
- package/dist/backend/entities/return-case-item.entity.js +87 -0
- package/dist/backend/entities/return-case-item.entity.js.map +1 -0
- package/dist/backend/entities/return-case.entity.d.ts +41 -0
- package/dist/backend/entities/return-case.entity.d.ts.map +1 -0
- package/dist/backend/entities/return-case.entity.js +155 -0
- package/dist/backend/entities/return-case.entity.js.map +1 -0
- package/dist/backend/entities/return-delivery-method.entity.d.ts +18 -0
- package/dist/backend/entities/return-delivery-method.entity.d.ts.map +1 -0
- package/dist/backend/entities/return-delivery-method.entity.js +63 -0
- package/dist/backend/entities/return-delivery-method.entity.js.map +1 -0
- package/dist/backend/entities/return-list-saved-view.entity.d.ts +24 -0
- package/dist/backend/entities/return-list-saved-view.entity.d.ts.map +1 -0
- package/dist/backend/entities/return-list-saved-view.entity.js +74 -0
- package/dist/backend/entities/return-list-saved-view.entity.js.map +1 -0
- package/dist/backend/entities/return-reason.entity.d.ts +21 -0
- package/dist/backend/entities/return-reason.entity.d.ts.map +1 -0
- package/dist/backend/entities/return-reason.entity.js +65 -0
- package/dist/backend/entities/return-reason.entity.js.map +1 -0
- package/dist/backend/entities/return-shipment.entity.d.ts +22 -0
- package/dist/backend/entities/return-shipment.entity.d.ts.map +1 -0
- package/dist/backend/entities/return-shipment.entity.js +71 -0
- package/dist/backend/entities/return-shipment.entity.js.map +1 -0
- package/dist/backend/entities/return-status-transition.entity.d.ts +17 -0
- package/dist/backend/entities/return-status-transition.entity.d.ts.map +1 -0
- package/dist/backend/entities/return-status-transition.entity.js +55 -0
- package/dist/backend/entities/return-status-transition.entity.js.map +1 -0
- package/dist/backend/entities/return-status.entity.d.ts +29 -0
- package/dist/backend/entities/return-status.entity.d.ts.map +1 -0
- package/dist/backend/entities/return-status.entity.js +90 -0
- package/dist/backend/entities/return-status.entity.js.map +1 -0
- package/dist/backend/events/return-status-events.d.ts +44 -0
- package/dist/backend/events/return-status-events.d.ts.map +1 -0
- package/dist/backend/events/return-status-events.js +50 -0
- package/dist/backend/events/return-status-events.js.map +1 -0
- package/dist/backend/index.d.ts +106 -0
- package/dist/backend/index.d.ts.map +1 -0
- package/dist/backend/index.js +116 -0
- package/dist/backend/index.js.map +1 -0
- package/dist/backend/plugin.d.ts +33 -0
- package/dist/backend/plugin.d.ts.map +1 -0
- package/dist/backend/plugin.js +124 -0
- package/dist/backend/plugin.js.map +1 -0
- package/dist/backend/routes.admin.d.ts +38 -0
- package/dist/backend/routes.admin.d.ts.map +1 -0
- package/dist/backend/routes.admin.js +181 -0
- package/dist/backend/routes.admin.js.map +1 -0
- package/dist/backend/routes.customer.d.ts +22 -0
- package/dist/backend/routes.customer.d.ts.map +1 -0
- package/dist/backend/routes.customer.js +56 -0
- package/dist/backend/routes.customer.js.map +1 -0
- package/dist/backend/services/notification-context.d.ts +47 -0
- package/dist/backend/services/notification-context.d.ts.map +1 -0
- package/dist/backend/services/notification-context.js +49 -0
- package/dist/backend/services/notification-context.js.map +1 -0
- package/dist/backend/services/return-attachment-service.d.ts +20 -0
- package/dist/backend/services/return-attachment-service.d.ts.map +1 -0
- package/dist/backend/services/return-attachment-service.js +48 -0
- package/dist/backend/services/return-attachment-service.js.map +1 -0
- package/dist/backend/services/return-authorization-service.d.ts +53 -0
- package/dist/backend/services/return-authorization-service.d.ts.map +1 -0
- package/dist/backend/services/return-authorization-service.js +72 -0
- package/dist/backend/services/return-authorization-service.js.map +1 -0
- package/dist/backend/services/return-case-service.d.ts +44 -0
- package/dist/backend/services/return-case-service.d.ts.map +1 -0
- package/dist/backend/services/return-case-service.js +260 -0
- package/dist/backend/services/return-case-service.js.map +1 -0
- package/dist/backend/services/return-comment-service.d.ts +43 -0
- package/dist/backend/services/return-comment-service.d.ts.map +1 -0
- package/dist/backend/services/return-comment-service.js +109 -0
- package/dist/backend/services/return-comment-service.js.map +1 -0
- package/dist/backend/services/return-delivery-method-service.d.ts +44 -0
- package/dist/backend/services/return-delivery-method-service.d.ts.map +1 -0
- package/dist/backend/services/return-delivery-method-service.js +114 -0
- package/dist/backend/services/return-delivery-method-service.js.map +1 -0
- package/dist/backend/services/return-email-notifier.d.ts +89 -0
- package/dist/backend/services/return-email-notifier.d.ts.map +1 -0
- package/dist/backend/services/return-email-notifier.js +128 -0
- package/dist/backend/services/return-email-notifier.js.map +1 -0
- package/dist/backend/services/return-export-service.d.ts +16 -0
- package/dist/backend/services/return-export-service.d.ts.map +1 -0
- package/dist/backend/services/return-export-service.js +29 -0
- package/dist/backend/services/return-export-service.js.map +1 -0
- package/dist/backend/services/return-list-service.d.ts +27 -0
- package/dist/backend/services/return-list-service.d.ts.map +1 -0
- package/dist/backend/services/return-list-service.js +114 -0
- package/dist/backend/services/return-list-service.js.map +1 -0
- package/dist/backend/services/return-list-view-service.d.ts +36 -0
- package/dist/backend/services/return-list-view-service.d.ts.map +1 -0
- package/dist/backend/services/return-list-view-service.js +82 -0
- package/dist/backend/services/return-list-view-service.js.map +1 -0
- package/dist/backend/services/return-reason-service.d.ts +33 -0
- package/dist/backend/services/return-reason-service.d.ts.map +1 -0
- package/dist/backend/services/return-reason-service.js +90 -0
- package/dist/backend/services/return-reason-service.js.map +1 -0
- package/dist/backend/services/return-settlement-service.d.ts +67 -0
- package/dist/backend/services/return-settlement-service.d.ts.map +1 -0
- package/dist/backend/services/return-settlement-service.js +295 -0
- package/dist/backend/services/return-settlement-service.js.map +1 -0
- package/dist/backend/services/return-shipment-service.d.ts +40 -0
- package/dist/backend/services/return-shipment-service.d.ts.map +1 -0
- package/dist/backend/services/return-shipment-service.js +113 -0
- package/dist/backend/services/return-shipment-service.js.map +1 -0
- package/dist/backend/services/return-status-graph-service.d.ts +59 -0
- package/dist/backend/services/return-status-graph-service.d.ts.map +1 -0
- package/dist/backend/services/return-status-graph-service.js +197 -0
- package/dist/backend/services/return-status-graph-service.js.map +1 -0
- package/dist/backend/services/return-transition-service.d.ts +48 -0
- package/dist/backend/services/return-transition-service.d.ts.map +1 -0
- package/dist/backend/services/return-transition-service.js +116 -0
- package/dist/backend/services/return-transition-service.js.map +1 -0
- package/dist/backend/services/returns-seeder.d.ts +18 -0
- package/dist/backend/services/returns-seeder.d.ts.map +1 -0
- package/dist/backend/services/returns-seeder.js +63 -0
- package/dist/backend/services/returns-seeder.js.map +1 -0
- package/dist/backend/services/rma-number-generator.d.ts +35 -0
- package/dist/backend/services/rma-number-generator.d.ts.map +1 -0
- package/dist/backend/services/rma-number-generator.js +45 -0
- package/dist/backend/services/rma-number-generator.js.map +1 -0
- package/dist/manifest.d.ts +182 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +216 -0
- package/dist/manifest.js.map +1 -0
- package/dist/migrations/20260625T144227_returns_init.d.ts +15 -0
- package/dist/migrations/20260625T144227_returns_init.d.ts.map +1 -0
- package/dist/migrations/20260625T144227_returns_init.js +241 -0
- package/dist/migrations/20260625T144227_returns_init.js.map +1 -0
- package/dist/migrations/20260817T203206_returns_refund_corrective_invoice_outcome.d.ts +25 -0
- package/dist/migrations/20260817T203206_returns_refund_corrective_invoice_outcome.d.ts.map +1 -0
- package/dist/migrations/20260817T203206_returns_refund_corrective_invoice_outcome.js +46 -0
- package/dist/migrations/20260817T203206_returns_refund_corrective_invoice_outcome.js.map +1 -0
- package/dist/migrations/index.d.ts +28 -0
- package/dist/migrations/index.d.ts.map +1 -0
- package/dist/migrations/index.js +31 -0
- package/dist/migrations/index.js.map +1 -0
- package/docs/returns.md +103 -0
- package/i18n/en.json +28 -0
- package/i18n/pl.json +28 -0
- package/package.json +101 -0
- 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"}
|