@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
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Shipment } from '../entities/shipment.entity.js';
|
|
2
|
+
/**
|
|
3
|
+
* The reads behind `shipmentReadPort` — see the port's contract for why a
|
|
4
|
+
* carrier module asks them and what happens when this module is off.
|
|
5
|
+
*
|
|
6
|
+
* Deliberately not methods on {@link ShipmentService}: that service opens
|
|
7
|
+
* attempts and calls carriers, and a lookup by id has no business resolving an
|
|
8
|
+
* adapter registry, an order port and a delivery-method port to answer.
|
|
9
|
+
*/
|
|
10
|
+
export class ShipmentReadService {
|
|
11
|
+
emFactory;
|
|
12
|
+
constructor(emFactory) {
|
|
13
|
+
this.emFactory = emFactory;
|
|
14
|
+
}
|
|
15
|
+
async findById(id) {
|
|
16
|
+
const row = await this.emFactory().findOne(Shipment, { id });
|
|
17
|
+
return row ? toShipmentRecord(row) : null;
|
|
18
|
+
}
|
|
19
|
+
async findByExternalReference(reference) {
|
|
20
|
+
const row = await this.emFactory().findOne(Shipment, { externalReference: reference }, { orderBy: { createdAt: 'desc', attemptNo: 'desc' } });
|
|
21
|
+
return row ? toShipmentRecord(row) : null;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
export function toShipmentRecord(shipment) {
|
|
25
|
+
return {
|
|
26
|
+
id: shipment.id,
|
|
27
|
+
orderId: shipment.orderId,
|
|
28
|
+
deliveryMethodId: shipment.deliveryMethodId,
|
|
29
|
+
status: shipment.status,
|
|
30
|
+
externalReference: shipment.externalReference ?? null,
|
|
31
|
+
providerDetails: shipment.providerDetails ?? null,
|
|
32
|
+
failureReason: shipment.failureReason ?? null,
|
|
33
|
+
attemptNo: shipment.attemptNo,
|
|
34
|
+
createdAt: shipment.createdAt,
|
|
35
|
+
updatedAt: shipment.updatedAt,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=shipment-read.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shipment-read.service.js","sourceRoot":"","sources":["../../../src/backend/services/shipment-read.service.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,QAAQ,EAAE,MAAM,gCAAgC,CAAC;AAE1D;;;;;;;GAOG;AACH,MAAM,OAAO,mBAAmB;IACD;IAA7B,YAA6B,SAA8B;QAA9B,cAAS,GAAT,SAAS,CAAqB;IAAG,CAAC;IAE/D,KAAK,CAAC,QAAQ,CAAC,EAAU;QACvB,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,SAAS,EAAE,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC;QAC7D,OAAO,GAAG,CAAC,CAAC,CAAC,gBAAgB,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAC5C,CAAC;IAED,KAAK,CAAC,uBAAuB,CAAC,SAAiB;QAC7C,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,SAAS,EAAE,CAAC,OAAO,CACxC,QAAQ,EACR,EAAE,iBAAiB,EAAE,SAAS,EAAE,EAChC,EAAE,OAAO,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,EAAE,CACtD,CAAC;QACF,OAAO,GAAG,CAAC,CAAC,CAAC,gBAAgB,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAC5C,CAAC;CACF;AAED,MAAM,UAAU,gBAAgB,CAAC,QAAkB;IACjD,OAAO;QACL,EAAE,EAAE,QAAQ,CAAC,EAAE;QACf,OAAO,EAAE,QAAQ,CAAC,OAAO;QACzB,gBAAgB,EAAE,QAAQ,CAAC,gBAAgB;QAC3C,MAAM,EAAE,QAAQ,CAAC,MAAM;QACvB,iBAAiB,EAAE,QAAQ,CAAC,iBAAiB,IAAI,IAAI;QACrD,eAAe,EAAE,QAAQ,CAAC,eAAe,IAAI,IAAI;QACjD,aAAa,EAAE,QAAQ,CAAC,aAAa,IAAI,IAAI;QAC7C,SAAS,EAAE,QAAQ,CAAC,SAAS;QAC7B,SAAS,EAAE,QAAQ,CAAC,SAAS;QAC7B,SAAS,EAAE,QAAQ,CAAC,SAAS;KAC9B,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { EntityManager } from '@mikro-orm/postgresql';
|
|
2
|
+
import type { DeliveryMethodReadPort, OrderReadPort, ShippingAdapterRegistryPort } from '@endora-commerce/contracts';
|
|
3
|
+
import type { AuditPort } from '@endora-commerce/platform/kernel';
|
|
4
|
+
import { Shipment } from '../entities/shipment.entity.js';
|
|
5
|
+
import type { ShippingEventBus } from './events.js';
|
|
6
|
+
/**
|
|
7
|
+
* What a `pending_manual` shipment says about itself, in one place so the row,
|
|
8
|
+
* the audit entry and the tests cannot drift apart (issue #250).
|
|
9
|
+
*
|
|
10
|
+
* English and persisted, like the payment side's refund reasons: this is the
|
|
11
|
+
* record of what happened, not a rendered string. The admin translates the
|
|
12
|
+
* *state* and shows this sentence as the detail that names the module — which
|
|
13
|
+
* is the one thing the operator needs and no translation can supply.
|
|
14
|
+
*/
|
|
15
|
+
export declare function carrierNotContactedReason(moduleId: string): string;
|
|
16
|
+
/**
|
|
17
|
+
* ShipmentService (feature 035, FR-021/FR-024).
|
|
18
|
+
*
|
|
19
|
+
* `createShipment` raises `shipment_created`: it opens a pending Shipment
|
|
20
|
+
* against the Order, invokes the method adapter's `onShipmentCreated`, and
|
|
21
|
+
* emits `shipment.created.v1`. It is also the retry path (FR-024): called again
|
|
22
|
+
* after a failure it appends attempt n+1 and leaves the prior rows intact,
|
|
23
|
+
* refusing only once an attempt has succeeded. `listForOrder` powers the admin
|
|
24
|
+
* view.
|
|
25
|
+
*
|
|
26
|
+
* Issue #257 — there used to be a second attempt-opening path, `openRetry`, and
|
|
27
|
+
* it asked no adapter in any state, not only while a carrier module was off. It
|
|
28
|
+
* is gone rather than repaired: no admin surface reached it, its own contract
|
|
29
|
+
* described it as "equivalent to calling the generate route again", and the
|
|
30
|
+
* equivalence was false in the one way that mattered — the row appeared, the
|
|
31
|
+
* status read `pending`, and no carrier had heard of the parcel. Repairing it
|
|
32
|
+
* would have left a second copy of `createShipment` to keep honest forever;
|
|
33
|
+
* deleting it leaves one path, which is the one every caller already used.
|
|
34
|
+
*
|
|
35
|
+
* Feature 075 Phase C — the order and the delivery method are read over their
|
|
36
|
+
* owners' ports. Only the `Shipment` rows are this module's to write, and only
|
|
37
|
+
* those stay inside the transaction: the two reads are of rows nothing in this
|
|
38
|
+
* operation modifies, so moving them onto the owner's `EntityManager` costs no
|
|
39
|
+
* consistency. Both fail closed when their owner is off, which is right — a
|
|
40
|
+
* shipment opened against an order the platform will not read is a parcel with
|
|
41
|
+
* no addressee.
|
|
42
|
+
*
|
|
43
|
+
* Issue #250 — the adapter registry is a contribution point, so an adapter
|
|
44
|
+
* whose owner is switched off is filtered out at enumeration rather than
|
|
45
|
+
* throwing (D-39). That gate is right; what used to follow it was not. The
|
|
46
|
+
* hook was skipped and the row was still written `pending`, so a shipment with
|
|
47
|
+
* no label, no tracking number and no carrier that knew about it read exactly
|
|
48
|
+
* like one the carrier had accepted. It now opens `pending_manual` — the state
|
|
49
|
+
* the platform already uses on the payment side for "the money did not move,
|
|
50
|
+
* a person has to finish this" — with the module named in `failureReason` and
|
|
51
|
+
* an audit row recording the attempt.
|
|
52
|
+
*/
|
|
53
|
+
export declare class ShipmentService {
|
|
54
|
+
private readonly emFactory;
|
|
55
|
+
private readonly registry;
|
|
56
|
+
private readonly orderRead;
|
|
57
|
+
private readonly deliveryMethodRead;
|
|
58
|
+
private readonly auditLog;
|
|
59
|
+
private readonly events?;
|
|
60
|
+
constructor(emFactory: () => EntityManager, registry: ShippingAdapterRegistryPort, orderRead: OrderReadPort, deliveryMethodRead: DeliveryMethodReadPort, auditLog: AuditPort, events?: ShippingEventBus | undefined);
|
|
61
|
+
createShipment(orderId: string): Promise<Shipment>;
|
|
62
|
+
/**
|
|
63
|
+
* The order's shipment attempts — for the admin panel.
|
|
64
|
+
*
|
|
65
|
+
* The `orderRead` call is the tenant boundary, not a convenience: `Shipment`
|
|
66
|
+
* is `@GlobalEntity`, so `em.find(Shipment, { orderId })` carries no filter
|
|
67
|
+
* and answers for every order on the platform, while `Order` is `@OrgScoped`
|
|
68
|
+
* and this port reads it through a filtered EntityManager. An
|
|
69
|
+
* assignment-scoped administrator therefore gets `null` here and no tracking
|
|
70
|
+
* number from an organization they are not assigned to.
|
|
71
|
+
*
|
|
72
|
+
* `createShipment` above has always made this read for its own reasons and so
|
|
73
|
+
* was never exposed; this one had no reason to make it, which is exactly how
|
|
74
|
+
* the gap arrived. 404 rather than 403 — an out-of-scope order must read the
|
|
75
|
+
* same as one that is not there.
|
|
76
|
+
*/
|
|
77
|
+
listForOrder(orderId: string): Promise<Shipment[]>;
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=shipment-service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shipment-service.d.ts","sourceRoot":"","sources":["../../../src/backend/services/shipment-service.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAE3D,OAAO,KAAK,EACV,sBAAsB,EACtB,aAAa,EACb,2BAA2B,EAC5B,MAAM,4BAA4B,CAAC;AAGpC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kCAAkC,CAAC;AAClE,OAAO,EAAE,QAAQ,EAAE,MAAM,gCAAgC,CAAC;AAC1D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEpD;;;;;;;;GAQG;AACH,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAMlE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,qBAAa,eAAe;IAExB,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,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC;gBALP,SAAS,EAAE,MAAM,aAAa,EAC9B,QAAQ,EAAE,2BAA2B,EACrC,SAAS,EAAE,aAAa,EACxB,kBAAkB,EAAE,sBAAsB,EAC1C,QAAQ,EAAE,SAAS,EACnB,MAAM,CAAC,EAAE,gBAAgB,YAAA;IAGtC,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC;IA0HxD;;;;;;;;;;;;;;OAcG;IACG,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC;CAQzD"}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { randomUUID } from 'crypto';
|
|
2
|
+
import { ERROR_CODES } from '@endora-commerce/contracts';
|
|
3
|
+
import { HttpError } from '@endora-commerce/platform/http';
|
|
4
|
+
import { recordAuditFromContext } from '@endora-commerce/platform/commands';
|
|
5
|
+
import { Shipment } from '../entities/shipment.entity.js';
|
|
6
|
+
/**
|
|
7
|
+
* What a `pending_manual` shipment says about itself, in one place so the row,
|
|
8
|
+
* the audit entry and the tests cannot drift apart (issue #250).
|
|
9
|
+
*
|
|
10
|
+
* English and persisted, like the payment side's refund reasons: this is the
|
|
11
|
+
* record of what happened, not a rendered string. The admin translates the
|
|
12
|
+
* *state* and shows this sentence as the detail that names the module — which
|
|
13
|
+
* is the one thing the operator needs and no translation can supply.
|
|
14
|
+
*/
|
|
15
|
+
export function carrierNotContactedReason(moduleId) {
|
|
16
|
+
return (`The "${moduleId}" module is not switched on here, so the carrier was never ` +
|
|
17
|
+
`asked to create this shipment. Switch the module back on and generate the ` +
|
|
18
|
+
`shipment again.`);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* ShipmentService (feature 035, FR-021/FR-024).
|
|
22
|
+
*
|
|
23
|
+
* `createShipment` raises `shipment_created`: it opens a pending Shipment
|
|
24
|
+
* against the Order, invokes the method adapter's `onShipmentCreated`, and
|
|
25
|
+
* emits `shipment.created.v1`. It is also the retry path (FR-024): called again
|
|
26
|
+
* after a failure it appends attempt n+1 and leaves the prior rows intact,
|
|
27
|
+
* refusing only once an attempt has succeeded. `listForOrder` powers the admin
|
|
28
|
+
* view.
|
|
29
|
+
*
|
|
30
|
+
* Issue #257 — there used to be a second attempt-opening path, `openRetry`, and
|
|
31
|
+
* it asked no adapter in any state, not only while a carrier module was off. It
|
|
32
|
+
* is gone rather than repaired: no admin surface reached it, its own contract
|
|
33
|
+
* described it as "equivalent to calling the generate route again", and the
|
|
34
|
+
* equivalence was false in the one way that mattered — the row appeared, the
|
|
35
|
+
* status read `pending`, and no carrier had heard of the parcel. Repairing it
|
|
36
|
+
* would have left a second copy of `createShipment` to keep honest forever;
|
|
37
|
+
* deleting it leaves one path, which is the one every caller already used.
|
|
38
|
+
*
|
|
39
|
+
* Feature 075 Phase C — the order and the delivery method are read over their
|
|
40
|
+
* owners' ports. Only the `Shipment` rows are this module's to write, and only
|
|
41
|
+
* those stay inside the transaction: the two reads are of rows nothing in this
|
|
42
|
+
* operation modifies, so moving them onto the owner's `EntityManager` costs no
|
|
43
|
+
* consistency. Both fail closed when their owner is off, which is right — a
|
|
44
|
+
* shipment opened against an order the platform will not read is a parcel with
|
|
45
|
+
* no addressee.
|
|
46
|
+
*
|
|
47
|
+
* Issue #250 — the adapter registry is a contribution point, so an adapter
|
|
48
|
+
* whose owner is switched off is filtered out at enumeration rather than
|
|
49
|
+
* throwing (D-39). That gate is right; what used to follow it was not. The
|
|
50
|
+
* hook was skipped and the row was still written `pending`, so a shipment with
|
|
51
|
+
* no label, no tracking number and no carrier that knew about it read exactly
|
|
52
|
+
* like one the carrier had accepted. It now opens `pending_manual` — the state
|
|
53
|
+
* the platform already uses on the payment side for "the money did not move,
|
|
54
|
+
* a person has to finish this" — with the module named in `failureReason` and
|
|
55
|
+
* an audit row recording the attempt.
|
|
56
|
+
*/
|
|
57
|
+
export class ShipmentService {
|
|
58
|
+
emFactory;
|
|
59
|
+
registry;
|
|
60
|
+
orderRead;
|
|
61
|
+
deliveryMethodRead;
|
|
62
|
+
auditLog;
|
|
63
|
+
events;
|
|
64
|
+
constructor(emFactory, registry, orderRead, deliveryMethodRead, auditLog, events) {
|
|
65
|
+
this.emFactory = emFactory;
|
|
66
|
+
this.registry = registry;
|
|
67
|
+
this.orderRead = orderRead;
|
|
68
|
+
this.deliveryMethodRead = deliveryMethodRead;
|
|
69
|
+
this.auditLog = auditLog;
|
|
70
|
+
this.events = events;
|
|
71
|
+
}
|
|
72
|
+
async createShipment(orderId) {
|
|
73
|
+
// command-coverage-ignore: creates a pending shipment attempt + invokes the
|
|
74
|
+
// delivery adapter; fulfilment mechanics — the order "shipped" transition is
|
|
75
|
+
// audited in the orders flow.
|
|
76
|
+
const run = async () => {
|
|
77
|
+
const em = this.emFactory();
|
|
78
|
+
return em.transactional(async (tx) => {
|
|
79
|
+
const order = await this.orderRead.findById(orderId);
|
|
80
|
+
if (!order) {
|
|
81
|
+
throw new HttpError(404, ERROR_CODES.NOT_FOUND, 'Order not found.');
|
|
82
|
+
}
|
|
83
|
+
const latest = await tx.findOne(Shipment, { orderId }, { orderBy: { attemptNo: 'desc' } });
|
|
84
|
+
if (latest && latest.status === 'success') {
|
|
85
|
+
throw new HttpError(409, ERROR_CODES.VALIDATION_FAILED, 'Order already has a successful shipment; nothing to generate.');
|
|
86
|
+
}
|
|
87
|
+
const method = await this.deliveryMethodRead.findById(order.deliveryMethodId);
|
|
88
|
+
const adapterKey = method?.adapter ?? order.deliveryMethodId;
|
|
89
|
+
const adapter = this.registry.get(adapterKey);
|
|
90
|
+
// Told apart deliberately: an adapter nobody ever contributed is an
|
|
91
|
+
// offline method that has always been finished by hand, and it keeps
|
|
92
|
+
// opening `pending`. Only a contributed adapter whose module is absent
|
|
93
|
+
// produces `pending_manual` — because only that one names a module an
|
|
94
|
+
// operator can switch back on.
|
|
95
|
+
const absentCarrierModule = adapter ? null : this.registry.absentOwnerFor(adapterKey);
|
|
96
|
+
const shipment = tx.create(Shipment, {
|
|
97
|
+
orderId,
|
|
98
|
+
deliveryMethodId: order.deliveryMethodId,
|
|
99
|
+
status: absentCarrierModule ? 'pending_manual' : 'pending',
|
|
100
|
+
...(absentCarrierModule
|
|
101
|
+
? { failureReason: carrierNotContactedReason(absentCarrierModule) }
|
|
102
|
+
: {}),
|
|
103
|
+
attemptNo: (latest?.attemptNo ?? 0) + 1,
|
|
104
|
+
});
|
|
105
|
+
await tx.persistAndFlush(shipment);
|
|
106
|
+
// Invoke the adapter's shipment_created hook. Status stays pending until
|
|
107
|
+
// receive_shipment; a synchronous `generated` result still deposits the
|
|
108
|
+
// tracking number on the row so admin can see it before terminal ingress.
|
|
109
|
+
// Carrier references (ShipX id, etc.) must be applied on *this*
|
|
110
|
+
// transactional EM — a forked emFactory inside the adapter cannot see
|
|
111
|
+
// the uncommitted row and would silently skip the write.
|
|
112
|
+
if (adapter) {
|
|
113
|
+
const started = await adapter.onShipmentCreated({
|
|
114
|
+
orderId,
|
|
115
|
+
shipmentId: shipment.id,
|
|
116
|
+
deliveryMethodId: order.deliveryMethodId,
|
|
117
|
+
attemptNo: shipment.attemptNo,
|
|
118
|
+
});
|
|
119
|
+
if (started.kind === 'pending' || started.kind === 'generated') {
|
|
120
|
+
// `externalReference` is the explicit carrier reference and wins;
|
|
121
|
+
// `trackingNumber` on a `generated` result is the older spelling an
|
|
122
|
+
// adapter that names no reference still relies on.
|
|
123
|
+
const reference = started.externalReference !== undefined
|
|
124
|
+
? (started.externalReference ?? null)
|
|
125
|
+
: started.kind === 'generated' && started.trackingNumber
|
|
126
|
+
? started.trackingNumber
|
|
127
|
+
: undefined;
|
|
128
|
+
if (reference !== undefined) {
|
|
129
|
+
shipment.externalReference = reference;
|
|
130
|
+
}
|
|
131
|
+
if (started.providerDetails) {
|
|
132
|
+
shipment.providerDetails = started.providerDetails;
|
|
133
|
+
}
|
|
134
|
+
if (reference !== undefined || started.providerDetails !== undefined) {
|
|
135
|
+
// `persistAndFlush` and not `flush`: the row is already managed by
|
|
136
|
+
// this transaction, so the two are equivalent here, and this is the
|
|
137
|
+
// spelling every other write in the method uses.
|
|
138
|
+
await tx.persistAndFlush(shipment);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
else if (absentCarrierModule) {
|
|
143
|
+
// Co-transactional with the row it describes (Principle XIII): the
|
|
144
|
+
// shipment and the record of why it is unfinished commit together or
|
|
145
|
+
// not at all. `recordAuditFromContext` rather than the Command Bus
|
|
146
|
+
// for the reason its own docblock gives — this write already owns a
|
|
147
|
+
// transaction the bus would have to fork out of.
|
|
148
|
+
recordAuditFromContext(this.auditLog, tx, {
|
|
149
|
+
action: 'shipment.carrier_not_contacted',
|
|
150
|
+
objectType: 'shipment',
|
|
151
|
+
objectId: shipment.id,
|
|
152
|
+
stateAfter: {
|
|
153
|
+
orderId,
|
|
154
|
+
adapter: adapterKey,
|
|
155
|
+
absentModule: absentCarrierModule,
|
|
156
|
+
attemptNo: shipment.attemptNo,
|
|
157
|
+
status: shipment.status,
|
|
158
|
+
},
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
if (this.events) {
|
|
162
|
+
this.events.emit('shipment.created.v1', {
|
|
163
|
+
eventId: randomUUID(),
|
|
164
|
+
occurredAt: new Date().toISOString(),
|
|
165
|
+
orderId,
|
|
166
|
+
shipmentId: shipment.id,
|
|
167
|
+
deliveryMethodId: order.deliveryMethodId,
|
|
168
|
+
adapter: adapterKey,
|
|
169
|
+
attemptNo: shipment.attemptNo,
|
|
170
|
+
status: shipment.status,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
return { shipment, adapterKey };
|
|
174
|
+
});
|
|
175
|
+
};
|
|
176
|
+
const result = this.events ? await this.events.run(run) : await run();
|
|
177
|
+
return result.shipment;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* The order's shipment attempts — for the admin panel.
|
|
181
|
+
*
|
|
182
|
+
* The `orderRead` call is the tenant boundary, not a convenience: `Shipment`
|
|
183
|
+
* is `@GlobalEntity`, so `em.find(Shipment, { orderId })` carries no filter
|
|
184
|
+
* and answers for every order on the platform, while `Order` is `@OrgScoped`
|
|
185
|
+
* and this port reads it through a filtered EntityManager. An
|
|
186
|
+
* assignment-scoped administrator therefore gets `null` here and no tracking
|
|
187
|
+
* number from an organization they are not assigned to.
|
|
188
|
+
*
|
|
189
|
+
* `createShipment` above has always made this read for its own reasons and so
|
|
190
|
+
* was never exposed; this one had no reason to make it, which is exactly how
|
|
191
|
+
* the gap arrived. 404 rather than 403 — an out-of-scope order must read the
|
|
192
|
+
* same as one that is not there.
|
|
193
|
+
*/
|
|
194
|
+
async listForOrder(orderId) {
|
|
195
|
+
const order = await this.orderRead.findById(orderId);
|
|
196
|
+
if (!order) {
|
|
197
|
+
throw new HttpError(404, ERROR_CODES.ORDER_NOT_FOUND, 'Order not found.');
|
|
198
|
+
}
|
|
199
|
+
const em = this.emFactory();
|
|
200
|
+
return em.find(Shipment, { orderId }, { orderBy: { attemptNo: 'asc' } });
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
//# sourceMappingURL=shipment-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shipment-service.js","sourceRoot":"","sources":["../../../src/backend/services/shipment-service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { EntityManager } from '@mikro-orm/postgresql';
|
|
2
|
+
import type { ShipmentUsagePort } from '@endora-commerce/contracts';
|
|
3
|
+
/**
|
|
4
|
+
* The read behind `shipmentUsagePort` — see the port's contract for why the
|
|
5
|
+
* question belongs to this module and what its caller does when the module is
|
|
6
|
+
* off (feature 075, the `delivery_methods` shard).
|
|
7
|
+
*
|
|
8
|
+
* One `count`, on this module's own `EntityManager`. Deliberately not a method
|
|
9
|
+
* on {@link ShipmentService}: that service opens shipment attempts and calls
|
|
10
|
+
* carriers, and a delete guard has no business resolving an adapter registry,
|
|
11
|
+
* an order port and a delivery-method port to ask how many rows exist.
|
|
12
|
+
*/
|
|
13
|
+
export declare class ShipmentUsageService implements ShipmentUsagePort {
|
|
14
|
+
private readonly emFactory;
|
|
15
|
+
constructor(emFactory: () => EntityManager);
|
|
16
|
+
/**
|
|
17
|
+
* Every status counts, `failure` and `pending_manual` included: a failed
|
|
18
|
+
* attempt is still history an operator can open, and it still names the
|
|
19
|
+
* method it was attempted with.
|
|
20
|
+
*/
|
|
21
|
+
countForDeliveryMethod(deliveryMethodId: string): Promise<number>;
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=shipment-usage.service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shipment-usage.service.d.ts","sourceRoot":"","sources":["../../../src/backend/services/shipment-usage.service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAGpE;;;;;;;;;GASG;AACH,qBAAa,oBAAqB,YAAW,iBAAiB;IAChD,OAAO,CAAC,QAAQ,CAAC,SAAS;gBAAT,SAAS,EAAE,MAAM,aAAa;IAE3D;;;;OAIG;IACG,sBAAsB,CAAC,gBAAgB,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;CAGxE"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Shipment } from '../entities/shipment.entity.js';
|
|
2
|
+
/**
|
|
3
|
+
* The read behind `shipmentUsagePort` — see the port's contract for why the
|
|
4
|
+
* question belongs to this module and what its caller does when the module is
|
|
5
|
+
* off (feature 075, the `delivery_methods` shard).
|
|
6
|
+
*
|
|
7
|
+
* One `count`, on this module's own `EntityManager`. Deliberately not a method
|
|
8
|
+
* on {@link ShipmentService}: that service opens shipment attempts and calls
|
|
9
|
+
* carriers, and a delete guard has no business resolving an adapter registry,
|
|
10
|
+
* an order port and a delivery-method port to ask how many rows exist.
|
|
11
|
+
*/
|
|
12
|
+
export class ShipmentUsageService {
|
|
13
|
+
emFactory;
|
|
14
|
+
constructor(emFactory) {
|
|
15
|
+
this.emFactory = emFactory;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Every status counts, `failure` and `pending_manual` included: a failed
|
|
19
|
+
* attempt is still history an operator can open, and it still names the
|
|
20
|
+
* method it was attempted with.
|
|
21
|
+
*/
|
|
22
|
+
async countForDeliveryMethod(deliveryMethodId) {
|
|
23
|
+
return this.emFactory().count(Shipment, { deliveryMethodId });
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=shipment-usage.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shipment-usage.service.js","sourceRoot":"","sources":["../../../src/backend/services/shipment-usage.service.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,QAAQ,EAAE,MAAM,gCAAgC,CAAC;AAE1D;;;;;;;;;GASG;AACH,MAAM,OAAO,oBAAoB;IACF;IAA7B,YAA6B,SAA8B;QAA9B,cAAS,GAAT,SAAS,CAAqB;IAAG,CAAC;IAE/D;;;;OAIG;IACH,KAAK,CAAC,sBAAsB,CAAC,gBAAwB;QACnD,OAAO,IAAI,CAAC,SAAS,EAAE,CAAC,KAAK,CAAC,QAAQ,EAAE,EAAE,gBAAgB,EAAE,CAAC,CAAC;IAChE,CAAC;CACF"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shipping-method renderer registry for the transactional order-confirmation
|
|
3
|
+
* e-mail (feature 035, FR-016/FR-017). An adapter may register a custom e-mail
|
|
4
|
+
* renderer under a key (the adapter's `renderers.email`); when none is
|
|
5
|
+
* registered, the platform default text builder is used, so the shipping
|
|
6
|
+
* section always renders.
|
|
7
|
+
*
|
|
8
|
+
* E-mail bodies are plain text (see Mailer.MailerSendInput.text), so a renderer
|
|
9
|
+
* is a `(ctx) => string` builder rather than a React component.
|
|
10
|
+
*/
|
|
11
|
+
export interface ShippingEmailContext {
|
|
12
|
+
/** Resolved display name of the shipping method. */
|
|
13
|
+
name: string;
|
|
14
|
+
/** Flat surcharge (`price`) applied for this method (order currency). */
|
|
15
|
+
cost: number;
|
|
16
|
+
currency: string;
|
|
17
|
+
}
|
|
18
|
+
export type ShippingEmailRenderer = (ctx: ShippingEmailContext) => string;
|
|
19
|
+
export declare const defaultShippingEmailRenderer: ShippingEmailRenderer;
|
|
20
|
+
export declare function registerShippingEmailRenderer(key: string, renderer: ShippingEmailRenderer): void;
|
|
21
|
+
export declare function resolveShippingEmailRenderer(rendererKey: string | null): ShippingEmailRenderer;
|
|
22
|
+
//# sourceMappingURL=shipping-email-renderer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shipping-email-renderer.d.ts","sourceRoot":"","sources":["../../../src/backend/services/shipping-email-renderer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,MAAM,WAAW,oBAAoB;IACnC,oDAAoD;IACpD,IAAI,EAAE,MAAM,CAAC;IACb,yEAAyE;IACzE,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,MAAM,qBAAqB,GAAG,CAAC,GAAG,EAAE,oBAAoB,KAAK,MAAM,CAAC;AAE1E,eAAO,MAAM,4BAA4B,EAAE,qBACa,CAAC;AAIzD,wBAAgB,6BAA6B,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,qBAAqB,GAAG,IAAI,CAEhG;AAED,wBAAgB,4BAA4B,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,GAAG,qBAAqB,CAM9F"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export const defaultShippingEmailRenderer = (ctx) => `${ctx.name} — ${ctx.cost.toFixed(2)} ${ctx.currency}`;
|
|
2
|
+
const registry = new Map();
|
|
3
|
+
export function registerShippingEmailRenderer(key, renderer) {
|
|
4
|
+
registry.set(key, renderer);
|
|
5
|
+
}
|
|
6
|
+
export function resolveShippingEmailRenderer(rendererKey) {
|
|
7
|
+
if (rendererKey) {
|
|
8
|
+
const found = registry.get(rendererKey);
|
|
9
|
+
if (found)
|
|
10
|
+
return found;
|
|
11
|
+
}
|
|
12
|
+
return defaultShippingEmailRenderer;
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=shipping-email-renderer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shipping-email-renderer.js","sourceRoot":"","sources":["../../../src/backend/services/shipping-email-renderer.ts"],"names":[],"mappings":"AAoBA,MAAM,CAAC,MAAM,4BAA4B,GAA0B,CAAC,GAAG,EAAE,EAAE,CACzE,GAAG,GAAG,CAAC,IAAI,MAAM,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,QAAQ,EAAE,CAAC;AAEzD,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAiC,CAAC;AAE1D,MAAM,UAAU,6BAA6B,CAAC,GAAW,EAAE,QAA+B;IACxF,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;AAC9B,CAAC;AAED,MAAM,UAAU,4BAA4B,CAAC,WAA0B;IACrE,IAAI,WAAW,EAAE,CAAC;QAChB,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;QACxC,IAAI,KAAK;YAAE,OAAO,KAAK,CAAC;IAC1B,CAAC;IACD,OAAO,4BAA4B,CAAC;AACtC,CAAC"}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
export declare const shipmentsSettingsManifest: {
|
|
2
|
+
moduleCode: string;
|
|
3
|
+
groups: {
|
|
4
|
+
code: string;
|
|
5
|
+
name: string;
|
|
6
|
+
salesChannelCodes?: string[] | undefined;
|
|
7
|
+
isSystemProtected?: boolean | undefined;
|
|
8
|
+
}[];
|
|
9
|
+
settings: {
|
|
10
|
+
code: string;
|
|
11
|
+
name: string;
|
|
12
|
+
valueType: "string" | "number" | "boolean" | "json" | "string_list" | "secret" | "credential_ref";
|
|
13
|
+
defaultValue: unknown;
|
|
14
|
+
description?: string | undefined;
|
|
15
|
+
groupCode?: string | undefined;
|
|
16
|
+
previousDefaultValues?: unknown[] | undefined;
|
|
17
|
+
salesChannelCodes?: string[] | undefined;
|
|
18
|
+
enumOptions?: string[] | undefined;
|
|
19
|
+
configurationType?: string | undefined;
|
|
20
|
+
hidden?: boolean | undefined;
|
|
21
|
+
}[];
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Shipments module (feature 035) — the Shipment entity and its
|
|
25
|
+
* generate/receive lifecycle. The delivery-side twin of `payments`.
|
|
26
|
+
*
|
|
27
|
+
* Generating is also retrying (FR-024): a second call after a failure appends
|
|
28
|
+
* attempt n+1 and asks the carrier for it. The separate retry route that used
|
|
29
|
+
* to sit beside it was deleted by issue #257 — it opened the row and asked
|
|
30
|
+
* nobody.
|
|
31
|
+
*
|
|
32
|
+
* The shipping-method *catalog* (adapter registry, reconciler, eligibility)
|
|
33
|
+
* lives in the sibling `delivery_methods` module; this module owns the
|
|
34
|
+
* first-class Shipment record and the `receive_shipment` ingress. Since feature
|
|
35
|
+
* 072 (T124) it registers its own services and routes through `backend.ts`;
|
|
36
|
+
* before that `orders/plugin.ts` constructed and mounted them, which is why
|
|
37
|
+
* switching this module off used to do nothing. No install/uninstall hook —
|
|
38
|
+
* schema is owned by migration 052.
|
|
39
|
+
*/
|
|
40
|
+
export declare const manifest: {
|
|
41
|
+
id: string;
|
|
42
|
+
name: string;
|
|
43
|
+
version: string;
|
|
44
|
+
dependencies: string[];
|
|
45
|
+
description?: string | undefined;
|
|
46
|
+
acknowledgedDependencies?: {
|
|
47
|
+
moduleId: string;
|
|
48
|
+
port: string;
|
|
49
|
+
reason: string;
|
|
50
|
+
}[] | undefined;
|
|
51
|
+
nonBindingDependencies?: {
|
|
52
|
+
moduleId: string;
|
|
53
|
+
name: string;
|
|
54
|
+
kind: "contributes-to" | "degrades-without" | "refuses-without";
|
|
55
|
+
reason: string;
|
|
56
|
+
whenAbsent?: string | undefined;
|
|
57
|
+
}[] | undefined;
|
|
58
|
+
activation?: {
|
|
59
|
+
settingCode: string;
|
|
60
|
+
default: boolean;
|
|
61
|
+
} | {
|
|
62
|
+
nonDeactivatable: true;
|
|
63
|
+
reason: string;
|
|
64
|
+
} | undefined;
|
|
65
|
+
settings?: {
|
|
66
|
+
moduleCode: string;
|
|
67
|
+
groups: {
|
|
68
|
+
code: string;
|
|
69
|
+
name: string;
|
|
70
|
+
salesChannelCodes?: string[] | undefined;
|
|
71
|
+
isSystemProtected?: boolean | undefined;
|
|
72
|
+
}[];
|
|
73
|
+
settings: {
|
|
74
|
+
code: string;
|
|
75
|
+
name: string;
|
|
76
|
+
valueType: "string" | "number" | "boolean" | "json" | "string_list" | "secret" | "credential_ref";
|
|
77
|
+
defaultValue: unknown;
|
|
78
|
+
description?: string | undefined;
|
|
79
|
+
groupCode?: string | undefined;
|
|
80
|
+
previousDefaultValues?: unknown[] | undefined;
|
|
81
|
+
salesChannelCodes?: string[] | undefined;
|
|
82
|
+
enumOptions?: string[] | undefined;
|
|
83
|
+
configurationType?: string | undefined;
|
|
84
|
+
hidden?: boolean | undefined;
|
|
85
|
+
}[];
|
|
86
|
+
} | undefined;
|
|
87
|
+
i18n?: {
|
|
88
|
+
bundlesDir: string;
|
|
89
|
+
} | undefined;
|
|
90
|
+
docs?: false | {
|
|
91
|
+
dir: string;
|
|
92
|
+
} | undefined;
|
|
93
|
+
demo?: false | {
|
|
94
|
+
summary: string;
|
|
95
|
+
seed: (context: import("@endora-commerce/contracts").ModuleDemoContext<never>) => Promise<import("@endora-commerce/contracts").DemoSeedResult>;
|
|
96
|
+
reset: (context: import("@endora-commerce/contracts").ModuleDemoContext<never>) => Promise<import("@endora-commerce/contracts").DemoResetResult>;
|
|
97
|
+
after?: readonly string[] | undefined;
|
|
98
|
+
package?: string | undefined;
|
|
99
|
+
} | undefined;
|
|
100
|
+
actions?: {
|
|
101
|
+
id: string;
|
|
102
|
+
labelKey: string;
|
|
103
|
+
icon: "Plus" | "Sparkles" | "Settings" | "Search" | "Boxes" | "Layers" | "Menu" | "PlusCircle" | "PlusSquare" | "FilePlus" | "FolderPlus" | "Upload" | "FileUp" | "CloudUpload" | "Download" | "FileDown" | "FileText" | "BookOpen" | "Rss" | "Package" | "Tag" | "ShoppingCart" | "Receipt" | "CreditCard" | "Users" | "UserPlus" | "Inbox" | "ListChecks" | "ClipboardList" | "Image" | "Video" | "LayoutDashboard" | "PanelLeft" | "KeyRound" | "ShieldCheck" | "Edit" | "Archive" | "Box" | "Truck" | "CircleDollarSign" | "Activity" | "LineChart" | "Smartphone" | "Webhook" | "Scale" | "PlugZap" | "PercentDiamond" | "Newspaper" | "Languages" | "Eraser" | "Warehouse" | "TrendingDown" | "Bell" | "PackageOpen" | "Building2" | "Store" | "ClipboardCheck";
|
|
104
|
+
targetRoute: string;
|
|
105
|
+
keywords: string[];
|
|
106
|
+
weight: number;
|
|
107
|
+
descriptionKey?: string | undefined;
|
|
108
|
+
requiredPermission?: string | undefined;
|
|
109
|
+
}[] | undefined;
|
|
110
|
+
permissions?: {
|
|
111
|
+
code: string;
|
|
112
|
+
label: string;
|
|
113
|
+
module?: string | undefined;
|
|
114
|
+
description?: string | undefined;
|
|
115
|
+
requires?: string[] | undefined;
|
|
116
|
+
}[] | undefined;
|
|
117
|
+
transactionalEmails?: {
|
|
118
|
+
code: string;
|
|
119
|
+
name: string;
|
|
120
|
+
variables: {
|
|
121
|
+
key: string;
|
|
122
|
+
label: string;
|
|
123
|
+
sampleValue?: string | undefined;
|
|
124
|
+
description?: string | undefined;
|
|
125
|
+
}[];
|
|
126
|
+
description?: string | undefined;
|
|
127
|
+
group?: string | undefined;
|
|
128
|
+
}[] | undefined;
|
|
129
|
+
capabilities?: string[] | undefined;
|
|
130
|
+
exclusiveCapabilities?: {
|
|
131
|
+
key: string;
|
|
132
|
+
errorCode: string;
|
|
133
|
+
}[] | undefined;
|
|
134
|
+
errorCodes?: {
|
|
135
|
+
code: string;
|
|
136
|
+
tokens?: string[] | undefined;
|
|
137
|
+
}[] | undefined;
|
|
138
|
+
blocks?: {
|
|
139
|
+
name: string;
|
|
140
|
+
labelKey: string;
|
|
141
|
+
category: string;
|
|
142
|
+
contexts: ("email" | "invoice" | "cms" | "newsletter")[];
|
|
143
|
+
fields: Record<string, {
|
|
144
|
+
type: "number" | "object" | "array" | "text" | "textarea" | "select" | "radio" | "external" | "uuid" | "richtext";
|
|
145
|
+
label?: string | undefined;
|
|
146
|
+
required?: boolean | undefined;
|
|
147
|
+
options?: {
|
|
148
|
+
label: string;
|
|
149
|
+
value: string | number;
|
|
150
|
+
}[] | undefined;
|
|
151
|
+
refKind?: string | undefined;
|
|
152
|
+
}>;
|
|
153
|
+
descriptionKey?: string | undefined;
|
|
154
|
+
defaultProps?: Record<string, unknown> | undefined;
|
|
155
|
+
responsiveFields?: string[] | undefined;
|
|
156
|
+
previewIcon?: string | undefined;
|
|
157
|
+
weight?: number | undefined;
|
|
158
|
+
}[] | undefined;
|
|
159
|
+
blockCategories?: {
|
|
160
|
+
key: string;
|
|
161
|
+
titleKey: string;
|
|
162
|
+
contexts: ("email" | "invoice" | "cms" | "newsletter")[];
|
|
163
|
+
weight?: number | undefined;
|
|
164
|
+
visible?: boolean | undefined;
|
|
165
|
+
}[] | undefined;
|
|
166
|
+
env?: {
|
|
167
|
+
name: string;
|
|
168
|
+
describes: {
|
|
169
|
+
en: string;
|
|
170
|
+
pl: string;
|
|
171
|
+
};
|
|
172
|
+
requirement: {
|
|
173
|
+
kind: "required";
|
|
174
|
+
} | {
|
|
175
|
+
kind: "requiredWhen";
|
|
176
|
+
input: string;
|
|
177
|
+
equals: string;
|
|
178
|
+
} | {
|
|
179
|
+
kind: "optional";
|
|
180
|
+
without: {
|
|
181
|
+
en: string;
|
|
182
|
+
pl: string;
|
|
183
|
+
};
|
|
184
|
+
};
|
|
185
|
+
secret: boolean;
|
|
186
|
+
generable: boolean;
|
|
187
|
+
owner: {
|
|
188
|
+
kind: "platform";
|
|
189
|
+
} | {
|
|
190
|
+
kind: "application";
|
|
191
|
+
application: "admin" | "backend" | "storefront";
|
|
192
|
+
} | {
|
|
193
|
+
kind: "module";
|
|
194
|
+
moduleId: string;
|
|
195
|
+
};
|
|
196
|
+
consumers: ("admin" | "backend" | "storefront")[];
|
|
197
|
+
addressOf: "admin" | "backend" | "storefront" | null;
|
|
198
|
+
}[] | undefined;
|
|
199
|
+
};
|
|
200
|
+
//# sourceMappingURL=manifest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;;;;;;;;;CAcpC,CAAC;AAEH;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6BnB,CAAC"}
|