@endora-commerce/mod-shipments 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 +51 -0
- package/dist/backend/email-templates/transactional-defaults.d.ts +6 -0
- package/dist/backend/email-templates/transactional-defaults.d.ts.map +1 -0
- package/dist/backend/email-templates/transactional-defaults.js +27 -0
- package/dist/backend/email-templates/transactional-defaults.js.map +1 -0
- package/dist/backend/entities/shipment.entity.d.ts +40 -0
- package/dist/backend/entities/shipment.entity.d.ts.map +1 -0
- package/dist/backend/entities/shipment.entity.js +98 -0
- package/dist/backend/entities/shipment.entity.js.map +1 -0
- package/dist/backend/index.d.ts +71 -0
- package/dist/backend/index.d.ts.map +1 -0
- package/dist/backend/index.js +151 -0
- package/dist/backend/index.js.map +1 -0
- package/dist/backend/routes.d.ts +29 -0
- package/dist/backend/routes.d.ts.map +1 -0
- package/dist/backend/routes.js +35 -0
- package/dist/backend/routes.js.map +1 -0
- package/dist/backend/services/auto-shipment-on-paid.d.ts +28 -0
- package/dist/backend/services/auto-shipment-on-paid.d.ts.map +1 -0
- package/dist/backend/services/auto-shipment-on-paid.js +58 -0
- package/dist/backend/services/auto-shipment-on-paid.js.map +1 -0
- package/dist/backend/services/events.d.ts +40 -0
- package/dist/backend/services/events.d.ts.map +1 -0
- package/dist/backend/services/events.js +2 -0
- package/dist/backend/services/events.js.map +1 -0
- package/dist/backend/services/receive-shipment-handler.d.ts +93 -0
- package/dist/backend/services/receive-shipment-handler.d.ts.map +1 -0
- package/dist/backend/services/receive-shipment-handler.js +203 -0
- package/dist/backend/services/receive-shipment-handler.js.map +1 -0
- package/dist/backend/services/shipment-email-notifier.d.ts +78 -0
- package/dist/backend/services/shipment-email-notifier.d.ts.map +1 -0
- package/dist/backend/services/shipment-email-notifier.js +76 -0
- package/dist/backend/services/shipment-email-notifier.js.map +1 -0
- package/dist/backend/services/shipment-read.service.d.ts +19 -0
- package/dist/backend/services/shipment-read.service.d.ts.map +1 -0
- package/dist/backend/services/shipment-read.service.js +38 -0
- package/dist/backend/services/shipment-read.service.js.map +1 -0
- package/dist/backend/services/shipment-service.d.ts +79 -0
- package/dist/backend/services/shipment-service.d.ts.map +1 -0
- package/dist/backend/services/shipment-service.js +203 -0
- package/dist/backend/services/shipment-service.js.map +1 -0
- package/dist/backend/services/shipment-usage.service.d.ts +23 -0
- package/dist/backend/services/shipment-usage.service.d.ts.map +1 -0
- package/dist/backend/services/shipment-usage.service.js +26 -0
- package/dist/backend/services/shipment-usage.service.js.map +1 -0
- package/dist/backend/services/shipping-email-renderer.d.ts +22 -0
- package/dist/backend/services/shipping-email-renderer.d.ts.map +1 -0
- package/dist/backend/services/shipping-email-renderer.js +14 -0
- package/dist/backend/services/shipping-email-renderer.js.map +1 -0
- package/dist/manifest.d.ts +200 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +63 -0
- package/dist/manifest.js.map +1 -0
- package/dist/migrations/20260817T194652_shipments_order_fk.d.ts +32 -0
- package/dist/migrations/20260817T194652_shipments_order_fk.d.ts.map +1 -0
- package/dist/migrations/20260817T194652_shipments_order_fk.js +66 -0
- package/dist/migrations/20260817T194652_shipments_order_fk.js.map +1 -0
- package/dist/migrations/20260819T171006_shipments_status_pending_manual.d.ts +19 -0
- package/dist/migrations/20260819T171006_shipments_status_pending_manual.d.ts.map +1 -0
- package/dist/migrations/20260819T171006_shipments_status_pending_manual.js +32 -0
- package/dist/migrations/20260819T171006_shipments_status_pending_manual.js.map +1 -0
- package/dist/migrations/index.d.ts +35 -0
- package/dist/migrations/index.d.ts.map +1 -0
- package/dist/migrations/index.js +38 -0
- package/dist/migrations/index.js.map +1 -0
- package/docs/shipments.md +96 -0
- package/package.json +69 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Endora sp. z o.o. and the Endora Commerce contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
<!-- Generated by `pnpm --filter backend run manifests:generate`. Delete this line to take the file over; a README without it is never regenerated. -->
|
|
2
|
+
|
|
3
|
+
# @endora-commerce/mod-shipments
|
|
4
|
+
|
|
5
|
+
Shipment record and the order_created / shipment_created / receive_shipment lifecycle.
|
|
6
|
+
|
|
7
|
+
## What this is
|
|
8
|
+
|
|
9
|
+
An **Endora Commerce module package**. Its module id is `shipments` — the identity of record in the platform's registries, in its migrations and in its setting, permission and translation codes.
|
|
10
|
+
|
|
11
|
+
A module is not imported by application code. The platform discovers the extension packages an instance has installed, reads the manifest on the root subpath and composes the module from it, so installing this package and starting the instance is the whole integration.
|
|
12
|
+
|
|
13
|
+
## Entry points
|
|
14
|
+
|
|
15
|
+
| Import | Contents |
|
|
16
|
+
| --- | --- |
|
|
17
|
+
| `@endora-commerce/mod-shipments` | the module manifest — its id, version, dependencies, settings and activation |
|
|
18
|
+
| `@endora-commerce/mod-shipments/backend` | the composition root the platform calls, with the entities, services, routes and workers it registers |
|
|
19
|
+
| `@endora-commerce/mod-shipments/migrations` | the module’s own schema migrations, in the order the platform runs them |
|
|
20
|
+
|
|
21
|
+
## Depends on
|
|
22
|
+
|
|
23
|
+
Everything below is a **peer** dependency, so the application resolves exactly one copy of each — two copies of a host or of React are a runtime failure rather than a type error. An entry marked *optional* is needed only by the layers that use it.
|
|
24
|
+
|
|
25
|
+
**Endora packages**
|
|
26
|
+
|
|
27
|
+
- `@endora-commerce/contracts`
|
|
28
|
+
- `@endora-commerce/email-components`
|
|
29
|
+
- `@endora-commerce/platform`
|
|
30
|
+
|
|
31
|
+
**Third-party**
|
|
32
|
+
|
|
33
|
+
- `@mikro-orm/core` ^6
|
|
34
|
+
- `@mikro-orm/migrations` ^6
|
|
35
|
+
- `@mikro-orm/postgresql` ^6
|
|
36
|
+
- `fastify` ^5
|
|
37
|
+
|
|
38
|
+
## What the tarball carries
|
|
39
|
+
|
|
40
|
+
- `dist/` — the compiled JavaScript and its type declarations
|
|
41
|
+
- `docs/` — the package’s own pages in the Endora Commerce documentation site
|
|
42
|
+
|
|
43
|
+
## Install
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
pnpm add @endora-commerce/mod-shipments
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Licence
|
|
50
|
+
|
|
51
|
+
MIT — the text is in `LICENSE`, beside this file.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transactional-defaults.d.ts","sourceRoot":"","sources":["../../../src/backend/email-templates/transactional-defaults.ts"],"names":[],"mappings":"AAOA,eAAO,MAAM,wBAAwB;oBAI9B,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;oBAetB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;CAE7B,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// Default subject + content for the shipment_created transactional email
|
|
2
|
+
// (feature 047). Simple layout via shared header/footer embeds.
|
|
3
|
+
import { simpleEmailBodyTree } from '@endora-commerce/email-components/defaults/simple-email-body';
|
|
4
|
+
const LANGS = ['en-US', 'pl-PL'];
|
|
5
|
+
export const SHIPMENT_CREATED_DEFAULT = {
|
|
6
|
+
defaultSubject: {
|
|
7
|
+
'en-US': 'Your order {{var order.businessId}} has shipped',
|
|
8
|
+
'pl-PL': 'Twoje zamówienie {{var order.businessId}} zostało wysłane',
|
|
9
|
+
},
|
|
10
|
+
defaultContent: {
|
|
11
|
+
schema_version: 1,
|
|
12
|
+
languages: {
|
|
13
|
+
'en-US': simpleEmailBodyTree({
|
|
14
|
+
idPrefix: 'shipment',
|
|
15
|
+
heading: 'Your order has shipped',
|
|
16
|
+
text: 'Good news — a shipment has been created for your order {{var order.businessId}}. You will receive tracking details soon.',
|
|
17
|
+
}),
|
|
18
|
+
'pl-PL': simpleEmailBodyTree({
|
|
19
|
+
idPrefix: 'shipment',
|
|
20
|
+
heading: 'Zamówienie wysłane',
|
|
21
|
+
text: 'Dobra wiadomość — utworzono przesyłkę dla Twojego zamówienia {{var order.businessId}}. Wkrótce otrzymasz dane do śledzenia.',
|
|
22
|
+
}),
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
languages: LANGS,
|
|
26
|
+
};
|
|
27
|
+
//# sourceMappingURL=transactional-defaults.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transactional-defaults.js","sourceRoot":"","sources":["../../../src/backend/email-templates/transactional-defaults.ts"],"names":[],"mappings":"AAAA,yEAAyE;AACzE,gEAAgE;AAEhE,OAAO,EAAE,mBAAmB,EAAE,MAAM,8DAA8D,CAAC;AAEnG,MAAM,KAAK,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;AAEjC,MAAM,CAAC,MAAM,wBAAwB,GAAG;IACtC,cAAc,EAAE;QACd,OAAO,EAAE,iDAAiD;QAC1D,OAAO,EAAE,2DAA2D;KAC3C;IAC3B,cAAc,EAAE;QACd,cAAc,EAAE,CAAC;QACjB,SAAS,EAAE;YACT,OAAO,EAAE,mBAAmB,CAAC;gBAC3B,QAAQ,EAAE,UAAU;gBACpB,OAAO,EAAE,wBAAwB;gBACjC,IAAI,EAAE,0HAA0H;aACjI,CAAC;YACF,OAAO,EAAE,mBAAmB,CAAC;gBAC3B,QAAQ,EAAE,UAAU;gBACpB,OAAO,EAAE,oBAAoB;gBAC7B,IAAI,EAAE,6HAA6H;aACpI,CAAC;SACH;KACyB;IAC5B,SAAS,EAAE,KAAK;CACjB,CAAC"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { OptionalProps } from '@mikro-orm/core';
|
|
2
|
+
/**
|
|
3
|
+
* Shipment (`Przesyłka` / `Paczka`) — a first-class record of one
|
|
4
|
+
* shipment-generation attempt against an Order (feature 035). An Order may
|
|
5
|
+
* have several: a failed generation followed by a successful retry, or a
|
|
6
|
+
* future split into multiple parcels. The delivery-side twin of `Payment`.
|
|
7
|
+
*
|
|
8
|
+
* `status` is the shipment-process status (pending → success | failure),
|
|
9
|
+
* distinct from the Order status the method maps to on a `receive_shipment`
|
|
10
|
+
* outcome.
|
|
11
|
+
*/
|
|
12
|
+
export declare class Shipment {
|
|
13
|
+
[OptionalProps]?: 'id' | 'createdAt' | 'updatedAt' | 'status' | 'externalReference' | 'providerDetails' | 'failureReason' | 'attemptNo';
|
|
14
|
+
id: string;
|
|
15
|
+
orderId: string;
|
|
16
|
+
deliveryMethodId: string;
|
|
17
|
+
/**
|
|
18
|
+
* `pending` → `success` | `failure` is the carrier's path. `pending_manual`
|
|
19
|
+
* is the path where there was no carrier: the adapter that would have been
|
|
20
|
+
* asked belongs to a module that is not present, so the row was opened
|
|
21
|
+
* without a request ever leaving the platform and a person has to finish it
|
|
22
|
+
* (issue #250). Same word as the refund settlement state, on purpose.
|
|
23
|
+
*/
|
|
24
|
+
status: 'pending' | 'pending_manual' | 'success' | 'failure';
|
|
25
|
+
/** Carrier/adapter reference (e.g. tracking number) for idempotent matching. */
|
|
26
|
+
externalReference?: string | null;
|
|
27
|
+
/** Adapter/carrier payload captured on receive_shipment. */
|
|
28
|
+
providerDetails?: Record<string, unknown> | null;
|
|
29
|
+
/**
|
|
30
|
+
* Populated on a failure outcome, and on `pending_manual` with the sentence
|
|
31
|
+
* naming the module that was not asked — the two cases where a row needs to
|
|
32
|
+
* say why it is not a plain success.
|
|
33
|
+
*/
|
|
34
|
+
failureReason?: string | null;
|
|
35
|
+
/** Retry sequence per order: 1 = first attempt. */
|
|
36
|
+
attemptNo: number;
|
|
37
|
+
createdAt: Date;
|
|
38
|
+
updatedAt: Date;
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=shipment.entity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shipment.entity.d.ts","sourceRoot":"","sources":["../../../src/backend/entities/shipment.entity.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiB,aAAa,EAAwB,MAAM,iBAAiB,CAAC;AAIrF;;;;;;;;;GASG;AACH,qBAEa,QAAQ;IACnB,CAAC,aAAa,CAAC,CAAC,EACZ,IAAI,GACJ,WAAW,GACX,WAAW,GACX,QAAQ,GACR,mBAAmB,GACnB,iBAAiB,GACjB,eAAe,GACf,WAAW,CAAC;IAGhB,EAAE,EAAE,MAAM,CAAgB;IAI1B,OAAO,EAAG,MAAM,CAAC;IAGjB,gBAAgB,EAAG,MAAM,CAAC;IAE1B;;;;;;OAMG;IAGH,MAAM,EAAE,SAAS,GAAG,gBAAgB,GAAG,SAAS,GAAG,SAAS,CAAa;IAEzE,gFAAgF;IAEhF,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAElC,4DAA4D;IAE5D,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAEjD;;;;OAIG;IAEH,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAE9B,mDAAmD;IAEnD,SAAS,EAAE,MAAM,CAAK;IAGtB,SAAS,EAAE,IAAI,CAAc;IAG7B,SAAS,EAAE,IAAI,CAAc;CAC9B"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
+
};
|
|
10
|
+
import { Entity, Index, OptionalProps, PrimaryKey, Property } from '@mikro-orm/core';
|
|
11
|
+
import { GlobalEntity } from '@endora-commerce/platform/tenancy';
|
|
12
|
+
import { randomUUID } from 'crypto';
|
|
13
|
+
/**
|
|
14
|
+
* Shipment (`Przesyłka` / `Paczka`) — a first-class record of one
|
|
15
|
+
* shipment-generation attempt against an Order (feature 035). An Order may
|
|
16
|
+
* have several: a failed generation followed by a successful retry, or a
|
|
17
|
+
* future split into multiple parcels. The delivery-side twin of `Payment`.
|
|
18
|
+
*
|
|
19
|
+
* `status` is the shipment-process status (pending → success | failure),
|
|
20
|
+
* distinct from the Order status the method maps to on a `receive_shipment`
|
|
21
|
+
* outcome.
|
|
22
|
+
*/
|
|
23
|
+
let Shipment = class Shipment {
|
|
24
|
+
[OptionalProps];
|
|
25
|
+
id = randomUUID();
|
|
26
|
+
orderId;
|
|
27
|
+
deliveryMethodId;
|
|
28
|
+
/**
|
|
29
|
+
* `pending` → `success` | `failure` is the carrier's path. `pending_manual`
|
|
30
|
+
* is the path where there was no carrier: the adapter that would have been
|
|
31
|
+
* asked belongs to a module that is not present, so the row was opened
|
|
32
|
+
* without a request ever leaving the platform and a person has to finish it
|
|
33
|
+
* (issue #250). Same word as the refund settlement state, on purpose.
|
|
34
|
+
*/
|
|
35
|
+
status = 'pending';
|
|
36
|
+
/** Carrier/adapter reference (e.g. tracking number) for idempotent matching. */
|
|
37
|
+
externalReference;
|
|
38
|
+
/** Adapter/carrier payload captured on receive_shipment. */
|
|
39
|
+
providerDetails;
|
|
40
|
+
/**
|
|
41
|
+
* Populated on a failure outcome, and on `pending_manual` with the sentence
|
|
42
|
+
* naming the module that was not asked — the two cases where a row needs to
|
|
43
|
+
* say why it is not a plain success.
|
|
44
|
+
*/
|
|
45
|
+
failureReason;
|
|
46
|
+
/** Retry sequence per order: 1 = first attempt. */
|
|
47
|
+
attemptNo = 1;
|
|
48
|
+
createdAt = new Date();
|
|
49
|
+
updatedAt = new Date();
|
|
50
|
+
};
|
|
51
|
+
__decorate([
|
|
52
|
+
PrimaryKey({ type: 'uuid' }),
|
|
53
|
+
__metadata("design:type", String)
|
|
54
|
+
], Shipment.prototype, "id", void 0);
|
|
55
|
+
__decorate([
|
|
56
|
+
Property({ type: 'uuid' }),
|
|
57
|
+
Index(),
|
|
58
|
+
__metadata("design:type", String)
|
|
59
|
+
], Shipment.prototype, "orderId", void 0);
|
|
60
|
+
__decorate([
|
|
61
|
+
Property({ type: 'uuid' }),
|
|
62
|
+
__metadata("design:type", String)
|
|
63
|
+
], Shipment.prototype, "deliveryMethodId", void 0);
|
|
64
|
+
__decorate([
|
|
65
|
+
Property({ type: 'string', length: 32 }),
|
|
66
|
+
Index(),
|
|
67
|
+
__metadata("design:type", String)
|
|
68
|
+
], Shipment.prototype, "status", void 0);
|
|
69
|
+
__decorate([
|
|
70
|
+
Property({ type: 'string', length: 255, nullable: true }),
|
|
71
|
+
__metadata("design:type", Object)
|
|
72
|
+
], Shipment.prototype, "externalReference", void 0);
|
|
73
|
+
__decorate([
|
|
74
|
+
Property({ type: 'json', nullable: true }),
|
|
75
|
+
__metadata("design:type", Object)
|
|
76
|
+
], Shipment.prototype, "providerDetails", void 0);
|
|
77
|
+
__decorate([
|
|
78
|
+
Property({ type: 'text', nullable: true }),
|
|
79
|
+
__metadata("design:type", Object)
|
|
80
|
+
], Shipment.prototype, "failureReason", void 0);
|
|
81
|
+
__decorate([
|
|
82
|
+
Property({ type: 'integer' }),
|
|
83
|
+
__metadata("design:type", Number)
|
|
84
|
+
], Shipment.prototype, "attemptNo", void 0);
|
|
85
|
+
__decorate([
|
|
86
|
+
Property({ type: 'datetime', onCreate: () => new Date() }),
|
|
87
|
+
__metadata("design:type", Date)
|
|
88
|
+
], Shipment.prototype, "createdAt", void 0);
|
|
89
|
+
__decorate([
|
|
90
|
+
Property({ type: 'datetime', onUpdate: () => new Date() }),
|
|
91
|
+
__metadata("design:type", Date)
|
|
92
|
+
], Shipment.prototype, "updatedAt", void 0);
|
|
93
|
+
Shipment = __decorate([
|
|
94
|
+
GlobalEntity(),
|
|
95
|
+
Entity({ tableName: 'shipments' })
|
|
96
|
+
], Shipment);
|
|
97
|
+
export { Shipment };
|
|
98
|
+
//# sourceMappingURL=shipment.entity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shipment.entity.js","sourceRoot":"","sources":["../../../src/backend/entities/shipment.entity.ts"],"names":[],"mappings":";;;;;;;;;AAAA,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AACrF,OAAO,EAAE,YAAY,EAAE,MAAM,mCAAmC,CAAC;AACjE,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAEpC;;;;;;;;;GASG;AAGI,IAAM,QAAQ,GAAd,MAAM,QAAQ;IACnB,CAAC,aAAa,CAAC,CAQC;IAGhB,EAAE,GAAW,UAAU,EAAE,CAAC;IAI1B,OAAO,CAAU;IAGjB,gBAAgB,CAAU;IAE1B;;;;;;OAMG;IAGH,MAAM,GAAyD,SAAS,CAAC;IAEzE,gFAAgF;IAEhF,iBAAiB,CAAiB;IAElC,4DAA4D;IAE5D,eAAe,CAAkC;IAEjD;;;;OAIG;IAEH,aAAa,CAAiB;IAE9B,mDAAmD;IAEnD,SAAS,GAAW,CAAC,CAAC;IAGtB,SAAS,GAAS,IAAI,IAAI,EAAE,CAAC;IAG7B,SAAS,GAAS,IAAI,IAAI,EAAE,CAAC;CAC9B,CAAA;AA7CC;IADC,UAAU,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;;oCACH;AAI1B;IAFC,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;IAC1B,KAAK,EAAE;;yCACS;AAGjB;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;;kDACD;AAW1B;IAFC,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;IACxC,KAAK,EAAE;;wCACiE;AAIzE;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;mDACxB;AAIlC;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;iDACM;AAQjD;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;+CACb;AAI9B;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;;2CACR;AAGtB;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,IAAI,IAAI,EAAE,EAAE,CAAC;8BAChD,IAAI;2CAAc;AAG7B;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,IAAI,IAAI,EAAE,EAAE,CAAC;8BAChD,IAAI;2CAAc;AAxDlB,QAAQ;IAFpB,YAAY,EAAE;IACd,MAAM,CAAC,EAAE,SAAS,EAAE,WAAW,EAAE,CAAC;GACtB,QAAQ,CAyDpB"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { EntityManager } from '@mikro-orm/postgresql';
|
|
2
|
+
import type { EventBus } from '@endora-commerce/platform/events';
|
|
3
|
+
import type { ShippingAdapterRegistryPort } from '@endora-commerce/contracts';
|
|
4
|
+
import type { ModuleContext } from '@endora-commerce/platform/kernel';
|
|
5
|
+
import type { AuditPort } from '@endora-commerce/platform/kernel';
|
|
6
|
+
import type { RequireAdminFactory } from '@endora-commerce/platform/kernel';
|
|
7
|
+
import { ShipmentService } from './services/shipment-service.js';
|
|
8
|
+
import { ReceiveShipmentHandler } from './services/receive-shipment-handler.js';
|
|
9
|
+
import { ShipmentEmailNotifier } from './services/shipment-email-notifier.js';
|
|
10
|
+
import type { ShipmentEmailNotifierDeps } from './services/shipment-email-notifier.js';
|
|
11
|
+
import { AutoShipmentOnPaidNotifier } from './services/auto-shipment-on-paid.js';
|
|
12
|
+
import { Shipment } from './entities/shipment.entity.js';
|
|
13
|
+
/**
|
|
14
|
+
* `shipments` — a module that owned everything except its own registration
|
|
15
|
+
* (feature 072, wave 2, T124).
|
|
16
|
+
*
|
|
17
|
+
* It has a manifest, an entity, two services, a route file, a transactional
|
|
18
|
+
* email and a migration — and none of it was wired by the module. `orders`
|
|
19
|
+
* imported both services and the route file, constructed them inside its own
|
|
20
|
+
* plugin and mounted them there, which its own manifest comment recorded as
|
|
21
|
+
* "routes are wired through the commerce composition root". So the shipment
|
|
22
|
+
* lifecycle existed for exactly as long as `orders` happened to be composed,
|
|
23
|
+
* and Principle I's "design every module so it can be detached" was false for
|
|
24
|
+
* it in the same literal way it was for `audit_logs` before T084.
|
|
25
|
+
*
|
|
26
|
+
* Nothing in the split needed inventing: both registries the services take are
|
|
27
|
+
* already ports `delivery_methods` provides, and the manifest already declares
|
|
28
|
+
* that dependency. What changes is who registers the routes — and therefore
|
|
29
|
+
* what switching `shipments` off does. Before, nothing: the module had no gate
|
|
30
|
+
* of its own and its routes answered whenever `orders` was on.
|
|
31
|
+
*
|
|
32
|
+
* The `shipment.created.v1` notifier moves in for the same reason. Both roots
|
|
33
|
+
* built it and called `attach(eventBus)` directly, so it subscribed to the raw
|
|
34
|
+
* bus rather than through `subscribeForModule` — a shipment-created e-mail went
|
|
35
|
+
* out whether or not this module was on. `ctx.subscribe` gates it.
|
|
36
|
+
*
|
|
37
|
+
* The transactional-email *sender* stays contributed: it is announced by
|
|
38
|
+
* `transactional_emails` through a callback a root holds, so a port would point
|
|
39
|
+
* the dependency at a module that does not yet have the value. Absent, the
|
|
40
|
+
* notifier is silent — which is exactly what it already did when the sender had
|
|
41
|
+
* not been exposed.
|
|
42
|
+
*/
|
|
43
|
+
/** What `shipments` resolves from the container, and the names it owns. */
|
|
44
|
+
export interface ShipmentsCradle {
|
|
45
|
+
readonly emFactory: () => EntityManager;
|
|
46
|
+
readonly eventBus: EventBus;
|
|
47
|
+
/** Platform-owned name — the sink for the `pending_manual` audit row. */
|
|
48
|
+
readonly auditLogService: AuditPort;
|
|
49
|
+
readonly requireAdmin: RequireAdminFactory;
|
|
50
|
+
readonly shippingAdapterRegistry: ShippingAdapterRegistryPort;
|
|
51
|
+
readonly shipmentService: ShipmentService;
|
|
52
|
+
readonly receiveShipmentHandler: ReceiveShipmentHandler;
|
|
53
|
+
/** Contribution point: absent means a shipment-created e-mail is not sent. */
|
|
54
|
+
readonly shipmentEmailSender: ShipmentEmailNotifierDeps['getTransactionalEmailSender'];
|
|
55
|
+
readonly shipmentEmailNotifier: ShipmentEmailNotifier;
|
|
56
|
+
readonly autoShipmentOnPaid: AutoShipmentOnPaidNotifier;
|
|
57
|
+
}
|
|
58
|
+
export declare function registerModule(ctx: ModuleContext): void;
|
|
59
|
+
/**
|
|
60
|
+
* The module's persisted entity classes, on the `./backend` subpath, as one
|
|
61
|
+
* array and **no named class export** (D-168).
|
|
62
|
+
*
|
|
63
|
+
* This is the shape the platform reads when the package is *installed*: the
|
|
64
|
+
* boot-time loader (`src/packages/package-runtime.ts`, `exported['entities']`)
|
|
65
|
+
* and the static declaration reader (`scripts/lib/package-declarations.ts`),
|
|
66
|
+
* which is the third source of `check:module-boundary`'s `table→owner` map and
|
|
67
|
+
* the package pass of `check-entity-tenant-classification`. A missing array is
|
|
68
|
+
* answered with `[]` — zero entities registered, no error anywhere.
|
|
69
|
+
*/
|
|
70
|
+
export declare const entities: (typeof Shipment)[];
|
|
71
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/backend/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kCAAkC,CAAC;AACjE,OAAO,KAAK,EAUV,2BAA2B,EAE5B,MAAM,4BAA4B,CAAC;AACpC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kCAAkC,CAAC;AAEtE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kCAAkC,CAAC;AAClE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AAC5E,OAAO,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AAIjE,OAAO,EAAE,sBAAsB,EAAE,MAAM,wCAAwC,CAAC;AAGhF,OAAO,EAAE,qBAAqB,EAAE,MAAM,uCAAuC,CAAC;AAC9E,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,uCAAuC,CAAC;AACvF,OAAO,EAAE,0BAA0B,EAAE,MAAM,qCAAqC,CAAC;AAEjF,OAAO,EAAE,QAAQ,EAAE,MAAM,+BAA+B,CAAC;AAEzD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,2EAA2E;AAC3E,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,SAAS,EAAE,MAAM,aAAa,CAAC;IACxC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,yEAAyE;IACzE,QAAQ,CAAC,eAAe,EAAE,SAAS,CAAC;IACpC,QAAQ,CAAC,YAAY,EAAE,mBAAmB,CAAC;IAC3C,QAAQ,CAAC,uBAAuB,EAAE,2BAA2B,CAAC;IAC9D,QAAQ,CAAC,eAAe,EAAE,eAAe,CAAC;IAC1C,QAAQ,CAAC,sBAAsB,EAAE,sBAAsB,CAAC;IACxD,8EAA8E;IAC9E,QAAQ,CAAC,mBAAmB,EAAE,yBAAyB,CAAC,6BAA6B,CAAC,CAAC;IACvF,QAAQ,CAAC,qBAAqB,EAAE,qBAAqB,CAAC;IACtD,QAAQ,CAAC,kBAAkB,EAAE,0BAA0B,CAAC;CACzD;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,aAAa,GAAG,IAAI,CA+LvD;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,QAAQ,qBAEpB,CAAC"}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { lazyPort } from '@endora-commerce/platform/kernel';
|
|
2
|
+
import { ShipmentService } from './services/shipment-service.js';
|
|
3
|
+
import { ShipmentUsageService } from './services/shipment-usage.service.js';
|
|
4
|
+
import { ShipmentReadService } from './services/shipment-read.service.js';
|
|
5
|
+
import { resolveShippingEmailRenderer } from './services/shipping-email-renderer.js';
|
|
6
|
+
import { ReceiveShipmentHandler } from './services/receive-shipment-handler.js';
|
|
7
|
+
import { registerShipmentsRoutes } from './routes.js';
|
|
8
|
+
import { ShipmentEmailNotifier } from './services/shipment-email-notifier.js';
|
|
9
|
+
import { AutoShipmentOnPaidNotifier } from './services/auto-shipment-on-paid.js';
|
|
10
|
+
import { SHIPMENT_CREATED_DEFAULT } from './email-templates/transactional-defaults.js';
|
|
11
|
+
import { Shipment } from './entities/shipment.entity.js';
|
|
12
|
+
export function registerModule(ctx) {
|
|
13
|
+
ctx.di.register({
|
|
14
|
+
// Contribution point, defaulted to no sender: a deployment without a
|
|
15
|
+
// transactional-email surface sends nothing rather than failing to ship.
|
|
16
|
+
shipmentEmailSender: ctx
|
|
17
|
+
.asFunction(() => () => undefined)
|
|
18
|
+
.singleton(),
|
|
19
|
+
shipmentEmailNotifier: ctx
|
|
20
|
+
.asFunction(({ emFactory }) => new ShipmentEmailNotifier({
|
|
21
|
+
emFactory,
|
|
22
|
+
orderRead: lazyPort(ctx, 'orderReadPort'),
|
|
23
|
+
customerAccountRead: lazyPort(ctx, 'customerAccountReadPort'),
|
|
24
|
+
// Read per call: a root contributes the sender after
|
|
25
|
+
// `transactional_emails` announces it, which is later than this.
|
|
26
|
+
getTransactionalEmailSender: () => ctx.cradle().shipmentEmailSender(),
|
|
27
|
+
}))
|
|
28
|
+
.singleton(),
|
|
29
|
+
autoShipmentOnPaid: ctx
|
|
30
|
+
.asFunction(({ emFactory }) => new AutoShipmentOnPaidNotifier(emFactory, lazyPort(ctx, 'shippingAdapterRegistry'), lazyPort(ctx, 'orderReadPort'), lazyPort(ctx, 'deliveryMethodReadPort'), lazyPort(ctx, 'shipmentService')))
|
|
31
|
+
.singleton(),
|
|
32
|
+
});
|
|
33
|
+
ctx.di.providePort('shipmentService', ctx
|
|
34
|
+
.asFunction(({ emFactory, eventBus, auditLogService }) => new ShipmentService(emFactory, lazyPort(ctx, 'shippingAdapterRegistry'), lazyPort(ctx, 'orderReadPort'), lazyPort(ctx, 'deliveryMethodReadPort'), auditLogService, eventBus))
|
|
35
|
+
.singleton());
|
|
36
|
+
/**
|
|
37
|
+
* Feature 075 — the delete guard `delivery_methods` used to write itself.
|
|
38
|
+
*
|
|
39
|
+
* That module counted this one's table in raw SQL, which names no import
|
|
40
|
+
* specifier and so crossed the boundary invisibly until D-87 gave
|
|
41
|
+
* `check:module-boundary` a second predicate. The count is a question about
|
|
42
|
+
* shipment rows, so it is answered here.
|
|
43
|
+
*
|
|
44
|
+
* A port and not a contribution into a registry `delivery_methods` owns:
|
|
45
|
+
* a contributed edge is filtered by contributor at enumeration, so switching
|
|
46
|
+
* this module off would silently withdraw the guard — and with no foreign key
|
|
47
|
+
* on `shipments.delivery_method_id` that means orphaned history rather than a
|
|
48
|
+
* refused delete. The consumer decides this module's presence itself and
|
|
49
|
+
* refuses; see its manifest's `nonBindingDependencies` entry.
|
|
50
|
+
*/
|
|
51
|
+
ctx.di.providePort('shipmentUsagePort', ctx
|
|
52
|
+
.asFunction(({ emFactory }) => new ShipmentUsageService(emFactory))
|
|
53
|
+
.singleton());
|
|
54
|
+
/**
|
|
55
|
+
* Feature 068 — "what state is this shipment in?", asked by a carrier module.
|
|
56
|
+
*
|
|
57
|
+
* `inpost` loaded this module's `Shipment` entity for it: once to correlate an
|
|
58
|
+
* inbound ShipX webhook that names only the carrier's own id, and once for the
|
|
59
|
+
* admin label route. Both are reads of a row the caller does not write, so
|
|
60
|
+
* they are a port rather than an apply seam, and both are on this module's own
|
|
61
|
+
* `EntityManager`.
|
|
62
|
+
*/
|
|
63
|
+
ctx.di.providePort('shipmentReadPort', ctx
|
|
64
|
+
.asFunction(({ emFactory }) => new ShipmentReadService(emFactory))
|
|
65
|
+
.singleton());
|
|
66
|
+
/**
|
|
67
|
+
* Feature 075, Phase P — the shipping line of the order-confirmation e-mail.
|
|
68
|
+
*
|
|
69
|
+
* The delivery-side twin of `payments`' `paymentEmailRendererPort`, and the
|
|
70
|
+
* same collapse: `orders` reaches this module's resolver and then calls the
|
|
71
|
+
* function it gets back. Doing both behind one call is what keeps the
|
|
72
|
+
* default text on this side — a consumer that resolved a renderer and got
|
|
73
|
+
* `undefined` would have to hold a copy of it, and two copies of a default
|
|
74
|
+
* are how a default stops being one.
|
|
75
|
+
*/
|
|
76
|
+
ctx.di.providePort('shippingEmailRendererPort', ctx
|
|
77
|
+
.asFunction(() => ({
|
|
78
|
+
render: (rendererKey, emailContext) => resolveShippingEmailRenderer(rendererKey)(emailContext),
|
|
79
|
+
}))
|
|
80
|
+
.singleton());
|
|
81
|
+
/**
|
|
82
|
+
* The carrier callback ingress.
|
|
83
|
+
*
|
|
84
|
+
* **`orderTransitionPort` replaces two of the names this took** (feature 085
|
|
85
|
+
* Phase D). `orderStatusAnnouncePort` goes because the transition seam emits
|
|
86
|
+
* the templated `.after` events itself, so announcing beside it would double
|
|
87
|
+
* every subscriber's reaction; `shippingOrderStatusRegistry` goes because "is
|
|
88
|
+
* this a status code" was never the question a carrier callback needed
|
|
89
|
+
* answered, and the port answers the real one — may this order go there —
|
|
90
|
+
* against the configured graph.
|
|
91
|
+
*/
|
|
92
|
+
ctx.di.providePort('receiveShipmentHandler', ctx
|
|
93
|
+
.asFunction(({ emFactory, eventBus }) => new ReceiveShipmentHandler(emFactory, lazyPort(ctx, 'deliveryMethodReadPort'), lazyPort(ctx, 'orderTransitionPort'), ctx.log, eventBus))
|
|
94
|
+
.singleton());
|
|
95
|
+
ctx.subscribe('shipment.created.v1', async (payload) => {
|
|
96
|
+
const { orderId, shipmentId, status } = payload;
|
|
97
|
+
// The state travels with the event because the notifier's decision depends
|
|
98
|
+
// on it: a shipment no carrier was asked for sends no "your order has
|
|
99
|
+
// shipped" (issue #250).
|
|
100
|
+
await ctx.cradle().shipmentEmailNotifier.notify(orderId, shipmentId, status);
|
|
101
|
+
});
|
|
102
|
+
ctx.subscribe('payment.received.v1', async (payload) => {
|
|
103
|
+
const { orderId } = payload;
|
|
104
|
+
await ctx.cradle().autoShipmentOnPaid.maybeCreate(orderId);
|
|
105
|
+
});
|
|
106
|
+
ctx.routes(async (app) => {
|
|
107
|
+
const { requireAdmin } = ctx.cradle();
|
|
108
|
+
await registerShipmentsRoutes(app, {
|
|
109
|
+
requireAdmin,
|
|
110
|
+
// Lazily, even though the module owns both ports: route *registration*
|
|
111
|
+
// runs inside `buildServer` whatever the module's effective state is, so
|
|
112
|
+
// destructuring the gates here would stop the next start instead of
|
|
113
|
+
// stopping the routes (D-40).
|
|
114
|
+
receiveHandler: lazyPort(ctx, 'receiveShipmentHandler'),
|
|
115
|
+
shipmentService: lazyPort(ctx, 'shipmentService'),
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
/**
|
|
119
|
+
* The default subject and content for the 1 transactional email this
|
|
120
|
+
* module declares in its manifest (T143a).
|
|
121
|
+
*
|
|
122
|
+
* These were fourteen `emailDefaultsRegistry.register(...)` calls in
|
|
123
|
+
* `composition.ts`, each importing a template constant out of the module that
|
|
124
|
+
* owns it — a root reaching into seven modules to hand their own content to
|
|
125
|
+
* an eighth. Each module registers its own now.
|
|
126
|
+
*
|
|
127
|
+
* `ctx.onBoot` rather than a registration: the registry is *read* once, by
|
|
128
|
+
* `transactional_emails`' boot reconciler inside its plugin body. Boot hooks
|
|
129
|
+
* run during composition and plugin bodies only when the Fastify app is
|
|
130
|
+
* built, so this always lands first — by construction, not by ordering luck.
|
|
131
|
+
*/
|
|
132
|
+
ctx.onBoot(async () => {
|
|
133
|
+
const defaults = lazyPort(ctx, 'emailDefaultsPort');
|
|
134
|
+
defaults.register('shipment_created', SHIPMENT_CREATED_DEFAULT, 'shipments');
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* The module's persisted entity classes, on the `./backend` subpath, as one
|
|
139
|
+
* array and **no named class export** (D-168).
|
|
140
|
+
*
|
|
141
|
+
* This is the shape the platform reads when the package is *installed*: the
|
|
142
|
+
* boot-time loader (`src/packages/package-runtime.ts`, `exported['entities']`)
|
|
143
|
+
* and the static declaration reader (`scripts/lib/package-declarations.ts`),
|
|
144
|
+
* which is the third source of `check:module-boundary`'s `table→owner` map and
|
|
145
|
+
* the package pass of `check-entity-tenant-classification`. A missing array is
|
|
146
|
+
* answered with `[]` — zero entities registered, no error anywhere.
|
|
147
|
+
*/
|
|
148
|
+
export const entities = [
|
|
149
|
+
Shipment,
|
|
150
|
+
];
|
|
151
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/backend/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { FastifyInstance } from 'fastify';
|
|
2
|
+
import type { ReceiveShipmentHandler } from './services/receive-shipment-handler.js';
|
|
3
|
+
import type { ShipmentService } from './services/shipment-service.js';
|
|
4
|
+
import type { RequireAdminFactory } from '@endora-commerce/platform/kernel';
|
|
5
|
+
/**
|
|
6
|
+
* Shipments routes (feature 035).
|
|
7
|
+
*
|
|
8
|
+
* POST /api/v1/admin/orders/:id/shipments — shipment_created (FR-021),
|
|
9
|
+
* and the retry path (FR-024)
|
|
10
|
+
* GET /api/v1/admin/orders/:id/shipments — full shipment history (FR-026)
|
|
11
|
+
* POST /api/v1/shipments/receive — receive_shipment ingress (FR-022)
|
|
12
|
+
*
|
|
13
|
+
* The ingress is admin-guarded for the MVP; a signed carrier-webhook auth path
|
|
14
|
+
* is a follow-up (the offline reference adapters are settled by an admin anyway).
|
|
15
|
+
*
|
|
16
|
+
* `POST .../shipments/retry` was removed by issue #257. It opened attempt n+1
|
|
17
|
+
* and contacted no carrier, which is exactly the silence issue #250 removed
|
|
18
|
+
* from the generate path; its own contract called it "equivalent to calling the
|
|
19
|
+
* generate route again", and the generate route is what every caller used —
|
|
20
|
+
* including the recovery button #250 added, which was pointed at generate
|
|
21
|
+
* precisely because retry asked nobody. Retrying is generating again.
|
|
22
|
+
*/
|
|
23
|
+
export interface ShipmentsRoutesDeps {
|
|
24
|
+
requireAdmin: RequireAdminFactory;
|
|
25
|
+
receiveHandler: ReceiveShipmentHandler;
|
|
26
|
+
shipmentService: ShipmentService;
|
|
27
|
+
}
|
|
28
|
+
export declare function registerShipmentsRoutes(app: FastifyInstance, deps: ShipmentsRoutesDeps): Promise<void>;
|
|
29
|
+
//# sourceMappingURL=routes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../src/backend/routes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAG/C,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,wCAAwC,CAAC;AACrF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AACtE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AAE5E;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,WAAW,mBAAmB;IAClC,YAAY,EAAE,mBAAmB,CAAC;IAClC,cAAc,EAAE,sBAAsB,CAAC;IACvC,eAAe,EAAE,eAAe,CAAC;CAClC;AAED,wBAAsB,uBAAuB,CAC3C,GAAG,EAAE,eAAe,EACpB,IAAI,EAAE,mBAAmB,GACxB,OAAO,CAAC,IAAI,CAAC,CAgCf"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { receiveShipmentSchema } from '@endora-commerce/contracts';
|
|
2
|
+
export async function registerShipmentsRoutes(app, deps) {
|
|
3
|
+
app.post('/api/v1/admin/orders/:id/shipments', { preHandler: deps.requireAdmin('orders:write') }, async (request, reply) => {
|
|
4
|
+
const shipment = await deps.shipmentService.createShipment(request.params.id);
|
|
5
|
+
reply.status(201);
|
|
6
|
+
return { data: serializeShipment(shipment) };
|
|
7
|
+
});
|
|
8
|
+
app.get('/api/v1/admin/orders/:id/shipments', { preHandler: deps.requireAdmin('orders:read') }, async (request) => {
|
|
9
|
+
const shipments = await deps.shipmentService.listForOrder(request.params.id);
|
|
10
|
+
return { data: shipments.map(serializeShipment) };
|
|
11
|
+
});
|
|
12
|
+
app.post('/api/v1/shipments/receive', {
|
|
13
|
+
preHandler: deps.requireAdmin('orders:write'),
|
|
14
|
+
schema: { body: receiveShipmentSchema },
|
|
15
|
+
}, async (request) => {
|
|
16
|
+
const body = receiveShipmentSchema.parse(request.body);
|
|
17
|
+
const result = await deps.receiveHandler.receive(body);
|
|
18
|
+
return { data: result };
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
function serializeShipment(s) {
|
|
22
|
+
return {
|
|
23
|
+
id: s.id,
|
|
24
|
+
orderId: s.orderId,
|
|
25
|
+
deliveryMethodId: s.deliveryMethodId,
|
|
26
|
+
status: s.status,
|
|
27
|
+
externalReference: s.externalReference ?? null,
|
|
28
|
+
providerDetails: s.providerDetails ?? null,
|
|
29
|
+
failureReason: s.failureReason ?? null,
|
|
30
|
+
attemptNo: s.attemptNo,
|
|
31
|
+
createdAt: s.createdAt.toISOString(),
|
|
32
|
+
updatedAt: s.updatedAt.toISOString(),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=routes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routes.js","sourceRoot":"","sources":["../../src/backend/routes.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AA8BnE,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAC3C,GAAoB,EACpB,IAAyB;IAEzB,GAAG,CAAC,IAAI,CACN,oCAAoC,EACpC,EAAE,UAAU,EAAE,IAAI,CAAC,YAAY,CAAC,cAAc,CAAC,EAAE,EACjD,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE;QACvB,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,eAAe,CAAC,cAAc,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QAC9E,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAClB,OAAO,EAAE,IAAI,EAAE,iBAAiB,CAAC,QAAQ,CAAC,EAAE,CAAC;IAC/C,CAAC,CACF,CAAC;IAEF,GAAG,CAAC,GAAG,CACL,oCAAoC,EACpC,EAAE,UAAU,EAAE,IAAI,CAAC,YAAY,CAAC,aAAa,CAAC,EAAE,EAChD,KAAK,EAAE,OAAO,EAAE,EAAE;QAChB,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,eAAe,CAAC,YAAY,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QAC7E,OAAO,EAAE,IAAI,EAAE,SAAS,CAAC,GAAG,CAAC,iBAAiB,CAAC,EAAE,CAAC;IACpD,CAAC,CACF,CAAC;IAEF,GAAG,CAAC,IAAI,CACN,2BAA2B,EAC3B;QACE,UAAU,EAAE,IAAI,CAAC,YAAY,CAAC,cAAc,CAAC;QAC7C,MAAM,EAAE,EAAE,IAAI,EAAE,qBAAqB,EAAE;KACxC,EACD,KAAK,EAAE,OAAO,EAAE,EAAE;QAChB,MAAM,IAAI,GAAG,qBAAqB,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QACvD,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,cAAc,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QACvD,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;IAC1B,CAAC,CACF,CAAC;AACJ,CAAC;AAED,SAAS,iBAAiB,CAAC,CAAW;IACpC,OAAO;QACL,EAAE,EAAE,CAAC,CAAC,EAAE;QACR,OAAO,EAAE,CAAC,CAAC,OAAO;QAClB,gBAAgB,EAAE,CAAC,CAAC,gBAAgB;QACpC,MAAM,EAAE,CAAC,CAAC,MAAM;QAChB,iBAAiB,EAAE,CAAC,CAAC,iBAAiB,IAAI,IAAI;QAC9C,eAAe,EAAE,CAAC,CAAC,eAAe,IAAI,IAAI;QAC1C,aAAa,EAAE,CAAC,CAAC,aAAa,IAAI,IAAI;QACtC,SAAS,EAAE,CAAC,CAAC,SAAS;QACtB,SAAS,EAAE,CAAC,CAAC,SAAS,CAAC,WAAW,EAAE;QACpC,SAAS,EAAE,CAAC,CAAC,SAAS,CAAC,WAAW,EAAE;KACrC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { EntityManager } from '@mikro-orm/postgresql';
|
|
2
|
+
import type { DeliveryMethodReadPort, OrderReadPort, ShippingAdapterRegistryPort } from '@endora-commerce/contracts';
|
|
3
|
+
import type { ShipmentService } from './shipment-service.js';
|
|
4
|
+
/**
|
|
5
|
+
* Creates a shipment when payment settles and the order's shipping adapter
|
|
6
|
+
* opts in via `shouldAutoCreateOnPaid()` (feature 068).
|
|
7
|
+
*
|
|
8
|
+
* Wired from `shipments/backend.ts` through `ctx.subscribe('payment.received.v1')`
|
|
9
|
+
* — never via a bare `eventBus.on` (issue #107). Best-effort: ordinary failures
|
|
10
|
+
* are swallowed so payment settlement is never blocked; `ModuleDisabledError`
|
|
11
|
+
* is re-thrown so fail-closed stays fail-closed.
|
|
12
|
+
*
|
|
13
|
+
* The order and the delivery method are read over their owners' ports, exactly
|
|
14
|
+
* as `ShipmentService` reads them (feature 075 Phase C): both rows are ones this
|
|
15
|
+
* operation does not modify, so nothing is lost by leaving them on the owner's
|
|
16
|
+
* EntityManager, and both fail closed when their owner is off.
|
|
17
|
+
*/
|
|
18
|
+
export declare class AutoShipmentOnPaidNotifier {
|
|
19
|
+
private readonly emFactory;
|
|
20
|
+
private readonly registry;
|
|
21
|
+
private readonly orderRead;
|
|
22
|
+
private readonly deliveryMethodRead;
|
|
23
|
+
private readonly shipmentService;
|
|
24
|
+
constructor(emFactory: () => EntityManager, registry: ShippingAdapterRegistryPort, orderRead: OrderReadPort, deliveryMethodRead: DeliveryMethodReadPort, shipmentService: ShipmentService);
|
|
25
|
+
/** Exposed for unit tests. */
|
|
26
|
+
maybeCreate(orderId: string): Promise<void>;
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=auto-shipment-on-paid.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auto-shipment-on-paid.d.ts","sourceRoot":"","sources":["../../../src/backend/services/auto-shipment-on-paid.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EACV,sBAAsB,EACtB,aAAa,EACb,2BAA2B,EAC5B,MAAM,4BAA4B,CAAC;AAGpC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAE7D;;;;;;;;;;;;;GAaG;AACH,qBAAa,0BAA0B;IAEnC,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,kBAAkB;IACnC,OAAO,CAAC,QAAQ,CAAC,eAAe;gBAJf,SAAS,EAAE,MAAM,aAAa,EAC9B,QAAQ,EAAE,2BAA2B,EACrC,SAAS,EAAE,aAAa,EACxB,kBAAkB,EAAE,sBAAsB,EAC1C,eAAe,EAAE,eAAe;IAGnD,8BAA8B;IACxB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CA4BlD"}
|