@comity/order 0.9.3 → 0.10.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/README.md +75 -18
- package/dist/contracts/customer-snapshot.d.ts +4 -4
- package/dist/contracts/line.d.ts +51 -0
- package/dist/contracts/line.js +2 -0
- package/dist/contracts/order.d.ts +46 -46
- package/dist/entities/order.d.ts +97 -74
- package/dist/entities/order.js +463 -200
- package/dist/errors/order.d.ts +7 -1
- package/dist/errors/order.js +11 -3
- package/dist/index.d.ts +2 -2
- package/dist/repositories/memory.js +11 -1
- package/package.json +4 -4
- package/dist/contracts/item.d.ts +0 -107
- package/dist/contracts/item.js +0 -2
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@ Order domain contracts for Comity commerce modules.
|
|
|
6
6
|
|
|
7
7
|
## Purpose
|
|
8
8
|
|
|
9
|
-
Defines order
|
|
9
|
+
Defines the generic order commitment contract consumed by commerce and storefront modules. Unifies cart and order into a single domain where a cart is an order in draft status. Provides the aggregate, repositories, line models and structured errors without binding to a specific commerce backend.
|
|
10
10
|
|
|
11
11
|
---
|
|
12
12
|
|
|
@@ -14,57 +14,114 @@ Defines order contracts and models consumed by commerce and storefront modules.
|
|
|
14
14
|
|
|
15
15
|
This package:
|
|
16
16
|
|
|
17
|
-
- ✅ defines the order entity with lifecycle and
|
|
18
|
-
- ✅ defines order,
|
|
17
|
+
- ✅ defines the order entity with lifecycle, validating creation, and the single atomic draft-only priced-content operation
|
|
18
|
+
- ✅ defines generic order, line and subject contracts
|
|
19
19
|
- ✅ defines order repository contracts
|
|
20
|
-
- ✅ exposes the `
|
|
20
|
+
- ✅ exposes the `errors` subpath for order-specific error types
|
|
21
21
|
|
|
22
22
|
This package does NOT:
|
|
23
23
|
|
|
24
|
+
- ❌ classify the purchased subject (product/asset/licence/service/plan)
|
|
25
|
+
- ❌ own commercial, billing, legal or fiscal buyer facts
|
|
26
|
+
- ❌ manage inventory, price composition or promotion rules
|
|
24
27
|
- ❌ implement checkout orchestration or payment processing
|
|
25
|
-
- ❌ manage inventory or pricing logic
|
|
26
|
-
- ❌ validate coupons or apply promotion rules
|
|
27
28
|
- ❌ render order UI
|
|
28
29
|
|
|
29
30
|
---
|
|
30
31
|
|
|
31
32
|
## Public API
|
|
32
33
|
|
|
33
|
-
- `Order` —
|
|
34
|
+
- `Order` — aggregate with lifecycle, `Order.create`, `setPricedContent`, `update`, `changeShippingDestination`, `attachPayment`, `submit`, `confirm`, `fulfill`, `cancel`, `snapshot`
|
|
34
35
|
- `OrderId` — order identifier value object
|
|
35
36
|
- `OrderRepository` — persistence-boundary contract (`getById`, `search`, `save`)
|
|
36
37
|
- `OrderState`, `OrderCreate`, `OrderUpdate`, `OrderSnapshot`, `OrderData`, `OrderStatus` — domain contracts
|
|
37
|
-
- `
|
|
38
|
+
- `OrderLine`, `OrderLineSubject` — generic embedded line value structures
|
|
39
|
+
- `OrderCustomerSnapshot` — generic historical buyer fact
|
|
38
40
|
- Error types (`@comity/order/errors`)
|
|
39
41
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
A line subject is an owned, immutable, generic snapshot: an opaque
|
|
43
|
+
`reference?`, a required `name` and an optional `description`. It makes the line
|
|
44
|
+
renderable and traceable without any external system, and it never classifies
|
|
45
|
+
the purchase. Commercial taxonomy, catalog identity and commercial attributes
|
|
46
|
+
belong to Commerce.
|
|
44
47
|
|
|
45
|
-
|
|
46
|
-
`
|
|
47
|
-
|
|
48
|
+
Prices are owned by `@comity/pricing`: lines and the order store the immutable
|
|
49
|
+
`PriceRecord`, and `OrderData.currency` is the structural `PriceCurrency`.
|
|
50
|
+
The order derives its own record through `PriceRecord.combine`; it never
|
|
51
|
+
performs pricing arithmetic.
|
|
48
52
|
|
|
49
53
|
No exhaustive reference; see docs for constraints.
|
|
50
54
|
|
|
51
55
|
---
|
|
52
56
|
|
|
57
|
+
## Migrating from 0.9.3
|
|
58
|
+
|
|
59
|
+
`0.9.3` is the current published release. `0.10.0` is the target of this
|
|
60
|
+
migration and is **not published yet**. The complete guide is
|
|
61
|
+
`docs/migration.md` in the repository; because the npm tarball contains the
|
|
62
|
+
README and `dist` only, this section is the npm-visible summary.
|
|
63
|
+
|
|
64
|
+
| `0.9.3` | `0.10.0` |
|
|
65
|
+
| ------------------------------------------------------------ | --------------------------------------------------------------------------------------------------- |
|
|
66
|
+
| `OrderData.items` | `OrderData.lines` |
|
|
67
|
+
| `OrderItem { id, product, ... }` | `OrderLine { lineId, subject, ... }` |
|
|
68
|
+
| `OrderItemInput` for `addItem` | no direct replacement; build `OrderLine` and call `setPricedContent` |
|
|
69
|
+
| `OrderProductSnapshot` (SKU/variant/options/attributes) | `OrderLineSubject { reference?, name, description? }`; commercial detail in Commerce or `line.meta` |
|
|
70
|
+
| `Price` on lines/order | `PriceRecord` |
|
|
71
|
+
| no order currency | required `OrderData.currency: PriceCurrency` |
|
|
72
|
+
| `new Order(data, id)` for creation | `Order.create(data, id) → Result`; constructor hydrates only |
|
|
73
|
+
| `setItems` / `addItem` / `removeItem` / `updateItemQuantity` | `setPricedContent(lines, adjustments)` |
|
|
74
|
+
| `update({ price })` | `update({ meta })` only |
|
|
75
|
+
| six error reasons | ten reasons (four additions; no `invalid_buyer`) |
|
|
76
|
+
|
|
77
|
+
Key steps:
|
|
78
|
+
|
|
79
|
+
1. Map each legacy line to `OrderLine`: `id` → `lineId`, `product` → `subject`.
|
|
80
|
+
Put SKU/variant/options/attributes in a Commerce-owned type or `line.meta`;
|
|
81
|
+
the generic subject has no commercial taxonomy.
|
|
82
|
+
2. Create every line and order price as a `PriceRecord`. Use
|
|
83
|
+
`PriceRecord.from(price)` for a computed line price and
|
|
84
|
+
`PriceRecord.combine(lineRecords, adjustments)` for the order record.
|
|
85
|
+
3. Supply `currency` from `currentPriceCurrency(code)`.
|
|
86
|
+
4. Replace all priced-content mutation with the atomic, draft-only
|
|
87
|
+
`setPricedContent`; `update` changes metadata only.
|
|
88
|
+
5. Handle `Result` values from `create`, `setPricedContent`, `submit`,
|
|
89
|
+
`update` and `attachPayment`; the four new reasons are
|
|
90
|
+
`currency_mismatch`, `price_incoherent`, `commitment_immutable` and
|
|
91
|
+
`payment_fact_conflict`.
|
|
92
|
+
6. Persist `PriceRecord.toPlain(record)` and hydrate with
|
|
93
|
+
`PriceRecord.restore(plain)`; never `JSON.stringify` a live record. Do not
|
|
94
|
+
recompute or reprice on load.
|
|
95
|
+
|
|
96
|
+
An empty order requires the canonical zero `PriceRecord` (`0` subtotal, `0`
|
|
97
|
+
total, no modifiers, current format/semantics, order currency).
|
|
98
|
+
`combine([])` is forbidden and `setPricedContent([], ...)` is rejected; clearing
|
|
99
|
+
all priced content is not supported.
|
|
100
|
+
|
|
101
|
+
The five-kind subject union, `priceBasis`, `term`, `invalid_buyer` and the
|
|
102
|
+
intermediate line mutators were never part of published `0.9.3`; consumers
|
|
103
|
+
migrating from `0.9.3` must not add them just to remove them.
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
53
107
|
## Documentation
|
|
54
108
|
|
|
55
109
|
- docs/overview.md
|
|
56
110
|
- docs/conventions.md
|
|
111
|
+
- docs/design.md
|
|
112
|
+
- docs/migration.md
|
|
57
113
|
|
|
58
114
|
---
|
|
59
115
|
|
|
60
116
|
## Related Packages
|
|
61
117
|
|
|
62
|
-
- @comity/pricing —
|
|
63
|
-
- @comity/catalog — source of
|
|
118
|
+
- @comity/pricing — `PriceRecord`/`PriceCurrency` and `combine` (`order → pricing`, registered in ADR-008)
|
|
119
|
+
- @comity/catalog — source of subject data, mapped by the application into the generic `OrderLineSubject` (no direct dependency)
|
|
64
120
|
- @comity/kernel — module lifecycle runtime
|
|
65
121
|
|
|
66
122
|
---
|
|
67
123
|
|
|
68
124
|
## Status
|
|
69
125
|
|
|
70
|
-
|
|
126
|
+
`0.9.3` is the current published release. `0.10.0` — the breaking contract in
|
|
127
|
+
this README — is in preparation and is **not published yet**.
|
|
@@ -6,11 +6,11 @@ import type { OrderContact } from "./contact.js";
|
|
|
6
6
|
* A plain, owned representation of the customer as it was at purchase time.
|
|
7
7
|
* It is NOT `Customer` from `@comity/customer` and it is not a reference to
|
|
8
8
|
* the customer module: once the order is created it MUST NOT depend on the
|
|
9
|
-
* customer for its content.
|
|
10
|
-
* invoice generation rely exclusively on this record.
|
|
9
|
+
* customer for its content.
|
|
11
10
|
*
|
|
12
|
-
* Only
|
|
13
|
-
* (
|
|
11
|
+
* Only generic counterparty facts are preserved. Commercial, billing, legal
|
|
12
|
+
* and fiscal facts (party kind, legal name, tax identifier) belong to
|
|
13
|
+
* Commerce; they are never part of the generic Order contract.
|
|
14
14
|
*/
|
|
15
15
|
export interface OrderCustomerSnapshot {
|
|
16
16
|
/** Reference to the customer identifier, if still known. */
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { PriceRecord } from "@comity/pricing";
|
|
2
|
+
/**
|
|
3
|
+
* Owned, immutable snapshot of what a line bought.
|
|
4
|
+
*
|
|
5
|
+
* Generic by construction: the Order preserves a displayable, traceable
|
|
6
|
+
* description of the purchased subject and never classifies it. Commercial
|
|
7
|
+
* taxonomy (product/asset/licence/service/plan), catalog identity, variants
|
|
8
|
+
* and options belong to Commerce.
|
|
9
|
+
*
|
|
10
|
+
* Guarantees:
|
|
11
|
+
* - `reference` is opaque and caller-owned; the Order never parses, resolves
|
|
12
|
+
* or compares it. It exists so a reader can trace the line back to its
|
|
13
|
+
* source when that is still known.
|
|
14
|
+
* - `name` (required) and optional `description` make the line renderable
|
|
15
|
+
* without any external system.
|
|
16
|
+
* - The value is an owned snapshot, defensively copied on creation and on
|
|
17
|
+
* `snapshot()`.
|
|
18
|
+
* - Only JSON-representable strings are used.
|
|
19
|
+
*/
|
|
20
|
+
export interface OrderLineSubject {
|
|
21
|
+
/** Opaque, application-owned reference to what was bought, when known. */
|
|
22
|
+
readonly reference?: string;
|
|
23
|
+
/** Human-readable label. */
|
|
24
|
+
readonly name: string;
|
|
25
|
+
/** Optional additional display text. */
|
|
26
|
+
readonly description?: string;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* A committed line: an occurrence within the order.
|
|
30
|
+
*
|
|
31
|
+
* Embedded value structure: identity only as `lineId`, no lifecycle, no
|
|
32
|
+
* repository. The full line price is a `PriceRecord` owned by
|
|
33
|
+
* `@comity/pricing`; the Order stores it and never recomputes it.
|
|
34
|
+
*/
|
|
35
|
+
export interface OrderLine {
|
|
36
|
+
/**
|
|
37
|
+
* Caller-assigned technical key for one occurrence within the order.
|
|
38
|
+
*
|
|
39
|
+
* The key identifies the occurrence, not the subject, is unique within the
|
|
40
|
+
* order, and is generated outside the Order.
|
|
41
|
+
*/
|
|
42
|
+
readonly lineId: string;
|
|
43
|
+
/** Self-contained snapshot of what was bought. */
|
|
44
|
+
readonly subject: OrderLineSubject;
|
|
45
|
+
/** Integer quantity, `>= 1`. */
|
|
46
|
+
readonly quantity: number;
|
|
47
|
+
/** Frozen committed price for the entire line. */
|
|
48
|
+
readonly price: PriceRecord;
|
|
49
|
+
/** Non-semantic metadata; the domain never reads it. */
|
|
50
|
+
readonly meta?: Record<string, unknown>;
|
|
51
|
+
}
|
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { PriceCurrency, PriceRecord } from "@comity/pricing";
|
|
2
2
|
import type { Instant } from "@comity/primitives/time";
|
|
3
3
|
import type { OrderId } from "../value-objects/order-id.js";
|
|
4
4
|
import type { ChannelId } from "@comity/organization";
|
|
5
5
|
import type { OrderAddressSnapshot } from "./address-snapshot.js";
|
|
6
6
|
import type { OrderCustomerSnapshot } from "./customer-snapshot.js";
|
|
7
|
-
import type {
|
|
7
|
+
import type { OrderLine } from "./line.js";
|
|
8
8
|
import type { OrderPaymentSnapshot } from "./payment-snapshot.js";
|
|
9
9
|
/**
|
|
10
10
|
* Order status.
|
|
11
11
|
*
|
|
12
|
-
* An order starts in
|
|
13
|
-
*
|
|
12
|
+
* An order starts in `draft` and progresses through a linear lifecycle. Each
|
|
13
|
+
* transition is explicit; the status cannot move backwards.
|
|
14
14
|
*
|
|
15
15
|
* Transitions:
|
|
16
16
|
* - `draft` → `pending` (submit)
|
|
@@ -19,16 +19,35 @@ import type { OrderPaymentSnapshot } from "./payment-snapshot.js";
|
|
|
19
19
|
* - `draft | pending | confirmed` → `cancelled` (cancel)
|
|
20
20
|
*
|
|
21
21
|
* `fulfilled` and `cancelled` are terminal.
|
|
22
|
+
*
|
|
23
|
+
* - `draft`: terms are being assembled; nothing is owed.
|
|
24
|
+
* - `pending`: submitted for acceptance.
|
|
25
|
+
* - `confirmed`: terms accepted; delivery obligations exist.
|
|
26
|
+
* - `fulfilled`: every delivery obligation has been discharged.
|
|
27
|
+
* - `cancelled`: the commitment is terminated.
|
|
22
28
|
*/
|
|
23
29
|
export type OrderStatus = "draft" | "pending" | "confirmed" | "fulfilled" | "cancelled";
|
|
24
30
|
/**
|
|
25
31
|
* Business data of an order, independent of identity and lifecycle metadata.
|
|
26
32
|
*/
|
|
27
33
|
export interface OrderData {
|
|
28
|
-
/**
|
|
29
|
-
readonly
|
|
30
|
-
/**
|
|
31
|
-
|
|
34
|
+
/** Committed lines. */
|
|
35
|
+
readonly lines: ReadonlyArray<OrderLine>;
|
|
36
|
+
/**
|
|
37
|
+
* Frozen order-level price composed by `@comity/pricing`.
|
|
38
|
+
*
|
|
39
|
+
* The aggregate derives `price.subtotal` (the sum of the line totals) and
|
|
40
|
+
* `price.total` (after order-level adjustments) through
|
|
41
|
+
* `PriceRecord.combine`; it never performs pricing arithmetic itself (O-7).
|
|
42
|
+
*/
|
|
43
|
+
readonly price: PriceRecord;
|
|
44
|
+
/**
|
|
45
|
+
* Monetary unit of the order and of every line and amount in `price`.
|
|
46
|
+
*
|
|
47
|
+
* A structural `PriceCurrency`, recorded verbatim. O-6 compares `(code,
|
|
48
|
+
* exponent)` exactly.
|
|
49
|
+
*/
|
|
50
|
+
readonly currency: PriceCurrency;
|
|
32
51
|
/** The commercial channel through which the order was placed. */
|
|
33
52
|
readonly channelId: ChannelId;
|
|
34
53
|
/** Historical buyer fact, captured at purchase time. */
|
|
@@ -52,6 +71,14 @@ export interface OrderState extends OrderData {
|
|
|
52
71
|
readonly createdAt: Instant;
|
|
53
72
|
/** Last update timestamp. */
|
|
54
73
|
readonly updatedAt: Instant;
|
|
74
|
+
/**
|
|
75
|
+
* Persisted-version token.
|
|
76
|
+
*
|
|
77
|
+
* `0` while the aggregate has never been persisted; restored verbatim on
|
|
78
|
+
* hydration. The aggregate NEVER increments it — the repository does, once
|
|
79
|
+
* per successful save. ADVISORY until ADR-029 is accepted.
|
|
80
|
+
*/
|
|
81
|
+
readonly version: number;
|
|
55
82
|
}
|
|
56
83
|
/**
|
|
57
84
|
* Immutable point-in-time snapshot of an existing order.
|
|
@@ -61,14 +88,13 @@ export type OrderSnapshot = Readonly<OrderState & {
|
|
|
61
88
|
readonly capturedAt: Instant;
|
|
62
89
|
}>;
|
|
63
90
|
/**
|
|
64
|
-
* Data required to create
|
|
91
|
+
* Data required to create or hydrate an order.
|
|
65
92
|
*
|
|
66
|
-
* The identifier is supplied separately
|
|
67
|
-
*
|
|
68
|
-
* generation
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
* hydration from persisted state (ADR-001).
|
|
93
|
+
* The identifier is supplied separately (`Order.create(data, id)` for
|
|
94
|
+
* creation, the constructor `id` argument for hydration): the caller owns ID
|
|
95
|
+
* generation. Creation ignores the lifecycle fields and always produces a
|
|
96
|
+
* `draft` order with `version: 0`; hydration restores `status`, timestamps
|
|
97
|
+
* and `version` verbatim (ADR-001).
|
|
72
98
|
*/
|
|
73
99
|
export type OrderCreate = OrderData & {
|
|
74
100
|
/** Order lifecycle status, used to restore persisted state during hydration. */
|
|
@@ -77,44 +103,18 @@ export type OrderCreate = OrderData & {
|
|
|
77
103
|
readonly createdAt?: Instant;
|
|
78
104
|
/** The timestamp when the order was last updated. */
|
|
79
105
|
readonly updatedAt?: Instant;
|
|
106
|
+
/** Persisted-version token restored during hydration. Defaults to `0`. */
|
|
107
|
+
readonly version?: number;
|
|
80
108
|
};
|
|
81
109
|
/**
|
|
82
110
|
* Partial mutation data for an order.
|
|
83
111
|
*
|
|
84
|
-
*
|
|
112
|
+
* Only non-semantic metadata is updatable here. Status transitions pass
|
|
85
113
|
* through the entity's domain methods (`submit`, `confirm`, `fulfill`,
|
|
86
|
-
* `cancel`)
|
|
87
|
-
* `
|
|
114
|
+
* `cancel`); priced content is replaced atomically through
|
|
115
|
+
* `setPricedContent`.
|
|
88
116
|
*/
|
|
89
117
|
export interface OrderUpdate {
|
|
90
|
-
/** Order price. Modifiers are updated through `price`; see `@comity/pricing`. */
|
|
91
|
-
readonly price?: Price;
|
|
92
118
|
/** Custom metadata. */
|
|
93
119
|
readonly meta?: Record<string, unknown>;
|
|
94
120
|
}
|
|
95
|
-
/**
|
|
96
|
-
* Input for adding an item to the order.
|
|
97
|
-
*
|
|
98
|
-
* The item price is supplied by the caller as a complete `Price` value:
|
|
99
|
-
* price computation belongs to `@comity/pricing` orchestration, not to the
|
|
100
|
-
* Order aggregate. The product data is an owned snapshot mapped by the caller
|
|
101
|
-
* from `ProductProjection`: the Order never depends on `@comity/catalog`.
|
|
102
|
-
*/
|
|
103
|
-
export interface OrderItemInput {
|
|
104
|
-
/**
|
|
105
|
-
* Caller-assigned technical key for the new occurrence within the order.
|
|
106
|
-
*
|
|
107
|
-
* The ID identifies one occurrence, not a product: two occurrences may
|
|
108
|
-
* carry identical product/configuration data under distinct IDs. It must be
|
|
109
|
-
* unique within the order; the aggregate rejects duplicates. The ID is
|
|
110
|
-
* generated outside the Order (application service or caller); the Order
|
|
111
|
-
* never mints occurrence IDs itself.
|
|
112
|
-
*/
|
|
113
|
-
readonly id: string;
|
|
114
|
-
/** Product snapshot to add to the order. */
|
|
115
|
-
readonly product: OrderProductSnapshot;
|
|
116
|
-
/** Quantity to add. */
|
|
117
|
-
readonly quantity: number;
|
|
118
|
-
/** Full line price (subtotal, applied modifiers, total). */
|
|
119
|
-
readonly price: Price;
|
|
120
|
-
}
|
package/dist/entities/order.d.ts
CHANGED
|
@@ -1,28 +1,58 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { PriceCurrency, PriceModifier } from "@comity/pricing";
|
|
2
2
|
import type { Result } from "@comity/primitives/result";
|
|
3
3
|
import type { OrderAddressSnapshot } from "../contracts/address-snapshot.js";
|
|
4
4
|
import type { OrderCustomerSnapshot } from "../contracts/customer-snapshot.js";
|
|
5
|
-
import type {
|
|
6
|
-
import type { OrderCreate,
|
|
5
|
+
import type { OrderLine } from "../contracts/line.js";
|
|
6
|
+
import type { OrderCreate, OrderData, OrderSnapshot, OrderStatus, OrderUpdate } from "../contracts/order.js";
|
|
7
7
|
import type { OrderPaymentSnapshot } from "../contracts/payment-snapshot.js";
|
|
8
8
|
import type { OrderId } from "../value-objects/order-id.js";
|
|
9
9
|
import type { ChannelId } from "@comity/organization";
|
|
10
|
+
import { PriceRecord } from "@comity/pricing";
|
|
10
11
|
import { Instant } from "@comity/primitives/time";
|
|
11
12
|
import { OrderError } from "../errors/order.js";
|
|
12
13
|
/**
|
|
13
14
|
* Represents an order entity in the system.
|
|
14
15
|
*
|
|
15
16
|
* @remark
|
|
16
|
-
* An order is a lifecycle aggregate: it owns its status transitions, its
|
|
17
|
-
*
|
|
18
|
-
* addresses,
|
|
19
|
-
* rules, and does not orchestrate external
|
|
20
|
-
* to `@comity/pricing`, other Core Modules,
|
|
17
|
+
* An order is a lifecycle aggregate: it owns its status transitions, its
|
|
18
|
+
* committed lines, the applied pricing result, its advisory version token, and
|
|
19
|
+
* its historical facts (buyer, addresses, payments). It does not validate
|
|
20
|
+
* coupons, does not know promotion rules, and does not orchestrate external
|
|
21
|
+
* services — those concerns belong to `@comity/pricing`, other Core Modules,
|
|
22
|
+
* and application/domain services.
|
|
23
|
+
*
|
|
24
|
+
* @remark
|
|
25
|
+
* The version token is ADVISORY: the aggregate carries it and never increments
|
|
26
|
+
* it. Optimistic concurrency belongs to the repository (Comity precondition
|
|
27
|
+
* ADR-029) and is not implemented here.
|
|
21
28
|
*/
|
|
22
29
|
export declare class Order {
|
|
23
30
|
#private;
|
|
24
31
|
/**
|
|
25
|
-
*
|
|
32
|
+
* Creates a new, validated order (ADR-001, ADR-032 AM-5a).
|
|
33
|
+
*
|
|
34
|
+
* Applies the invariants decidable from creation data — O-1 (integer
|
|
35
|
+
* quantity `>= 1`), O-2 (unique line ids), O-6 (one currency on `(code,
|
|
36
|
+
* exponent)`), O-7 (exact `PriceRecord` commitment equality against
|
|
37
|
+
* `combine`) and O-9 (supplied payment facts) — before any state exists. An
|
|
38
|
+
* empty order is accepted only with the canonical zero record. A new order
|
|
39
|
+
* is `draft` with `version: 0`.
|
|
40
|
+
*
|
|
41
|
+
* @param data - The business data of the order.
|
|
42
|
+
* @param id - The unique identifier of the order, supplied by the caller.
|
|
43
|
+
*
|
|
44
|
+
* @returns The order, or `invalid_quantity` / `duplicate_item_id` /
|
|
45
|
+
* `currency_mismatch` / `price_incoherent` / `payment_fact_conflict`.
|
|
46
|
+
*/
|
|
47
|
+
static create(data: OrderData, id: OrderId): Result<Order, OrderError>;
|
|
48
|
+
/**
|
|
49
|
+
* Hydrates an existing order (ADR-001).
|
|
50
|
+
*
|
|
51
|
+
* The constructor performs NO validation: it restores persisted state
|
|
52
|
+
* verbatim, including `version`. Use {@link Order.create} for the validating
|
|
53
|
+
* creation path.
|
|
54
|
+
*
|
|
55
|
+
* @param fields - The persisted fields of the order.
|
|
26
56
|
* @param id - The unique identifier of the order, supplied by the caller.
|
|
27
57
|
* The Order never generates its own aggregate ID; hydration preserves the
|
|
28
58
|
* persisted ID verbatim.
|
|
@@ -41,13 +71,17 @@ export declare class Order {
|
|
|
41
71
|
*/
|
|
42
72
|
get status(): OrderStatus;
|
|
43
73
|
/**
|
|
44
|
-
* @returns The
|
|
74
|
+
* @returns The committed lines.
|
|
45
75
|
*/
|
|
46
|
-
get
|
|
76
|
+
get lines(): ReadonlyArray<OrderLine>;
|
|
47
77
|
/**
|
|
48
78
|
* @returns The order price.
|
|
49
79
|
*/
|
|
50
|
-
get price():
|
|
80
|
+
get price(): PriceRecord;
|
|
81
|
+
/**
|
|
82
|
+
* @returns The monetary unit of the order and of every amount it stores.
|
|
83
|
+
*/
|
|
84
|
+
get currency(): PriceCurrency;
|
|
51
85
|
/**
|
|
52
86
|
* @returns The historical buyer fact, if captured.
|
|
53
87
|
*/
|
|
@@ -73,14 +107,24 @@ export declare class Order {
|
|
|
73
107
|
*/
|
|
74
108
|
get updatedAt(): Instant;
|
|
75
109
|
/**
|
|
76
|
-
*
|
|
110
|
+
* Returns the persisted-version token (read-only; advisory, never
|
|
111
|
+
* incremented by the aggregate).
|
|
112
|
+
*
|
|
113
|
+
* @returns The version token: `0` while unpersisted.
|
|
114
|
+
*/
|
|
115
|
+
get version(): number;
|
|
116
|
+
/**
|
|
117
|
+
* Updates non-semantic metadata.
|
|
77
118
|
*
|
|
78
|
-
* Status transitions
|
|
79
|
-
*
|
|
119
|
+
* Status transitions pass through the domain methods; priced content is
|
|
120
|
+
* replaced atomically through {@link setPricedContent}. Metadata is
|
|
121
|
+
* updatable in any status.
|
|
80
122
|
*
|
|
81
123
|
* @param changes - The changes to apply to the order.
|
|
124
|
+
*
|
|
125
|
+
* @returns Success.
|
|
82
126
|
*/
|
|
83
|
-
update(changes: OrderUpdate): void
|
|
127
|
+
update(changes: OrderUpdate): Result<void, OrderError>;
|
|
84
128
|
/**
|
|
85
129
|
* Appends a historical payment fact to the order.
|
|
86
130
|
*
|
|
@@ -89,20 +133,28 @@ export declare class Order {
|
|
|
89
133
|
* This records a historical fact only: it performs no payment logic, knows
|
|
90
134
|
* no payment module, and does not coordinate, authorize, or capture.
|
|
91
135
|
*
|
|
136
|
+
* Facts dedupe on `(paymentId, status)`: an identical repeat is an idempotent
|
|
137
|
+
* no-op that touches nothing; the same key with a different amount, and an
|
|
138
|
+
* illegal progression for a known `paymentId`, are conflicts. A fact without
|
|
139
|
+
* a `paymentId` is always appended.
|
|
140
|
+
*
|
|
92
141
|
* @param snapshot - The payment fact to record.
|
|
142
|
+
*
|
|
143
|
+
* @returns Success (including an idempotent no-op), or
|
|
144
|
+
* `payment_fact_conflict`.
|
|
93
145
|
*/
|
|
94
|
-
attachPayment(snapshot: OrderPaymentSnapshot): void
|
|
146
|
+
attachPayment(snapshot: OrderPaymentSnapshot): Result<void, OrderError>;
|
|
95
147
|
/**
|
|
96
148
|
* Replaces the shipping destination of the order.
|
|
97
149
|
*
|
|
98
150
|
* Allowed only while the order is `draft | pending`; in later statuses the
|
|
99
|
-
* shipping destination is immutable
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
151
|
+
* shipping destination is immutable. The order must hold exactly one shipping
|
|
152
|
+
* address fact; a missing or ambiguous one is an error rather than a guess. A
|
|
153
|
+
* structurally identical destination is a successful no-op that leaves
|
|
154
|
+
* timestamps untouched. The operation never reaches the generic `update()`
|
|
155
|
+
* path, never touches billing, customer, line, price, or payment facts, and
|
|
156
|
+
* records no history: the aggregate retains only the current shipping
|
|
157
|
+
* destination.
|
|
106
158
|
*
|
|
107
159
|
* @param destination - The replacement shipping destination. The address
|
|
108
160
|
* role is owned by this operation and is always stored as `"shipping"`.
|
|
@@ -111,63 +163,31 @@ export declare class Order {
|
|
|
111
163
|
*/
|
|
112
164
|
changeShippingDestination(destination: Omit<OrderAddressSnapshot, "role">): Result<void, OrderError>;
|
|
113
165
|
/**
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
* The supplied collection is the complete desired collection: items omitted
|
|
117
|
-
* from it are removed, and no merging, appending, or quantity aggregation
|
|
118
|
-
* occurs. The supplied order is the resulting order and an empty collection
|
|
119
|
-
* is valid. Every entry is validated before any state changes — quantity
|
|
120
|
-
* (integer `>= 1`) first, then ID uniqueness against the IDs already seen —
|
|
121
|
-
* and the first violation rejects the whole operation, leaving the aggregate
|
|
122
|
-
* unchanged. Every successful replacement refreshes `updatedAt`, including
|
|
123
|
-
* identical replacements. Supplied item IDs are preserved verbatim and
|
|
124
|
-
* caller-owned data is defensively copied. Item mutation is not status-gated.
|
|
125
|
-
*
|
|
126
|
-
* @param items - The complete desired item collection.
|
|
127
|
-
*
|
|
128
|
-
* @returns A result indicating the success or failure of the replacement.
|
|
129
|
-
*/
|
|
130
|
-
setItems(items: ReadonlyArray<OrderItem>): Result<void, OrderError>;
|
|
131
|
-
/**
|
|
132
|
-
* Appends one item occurrence to the order.
|
|
133
|
-
*
|
|
134
|
-
* The occurrence ID is supplied by the caller in the input: the Order never
|
|
135
|
-
* mints occurrence IDs itself. The ID must be unique within the order —
|
|
136
|
-
* appending an already-used ID is rejected so the `setItems` duplicate-ID
|
|
137
|
-
* invariant cannot be bypassed through this path. Validation runs before
|
|
138
|
-
* any mutation — quantity (integer `>= 1`) first, then ID uniqueness — and
|
|
139
|
-
* failures leave the aggregate unchanged. A successful append refreshes
|
|
140
|
-
* `updatedAt`. Occurrence IDs carry no product meaning: identical
|
|
141
|
-
* product/configuration data may coexist under distinct IDs.
|
|
142
|
-
*
|
|
143
|
-
* @param input - The item data to add, including the caller-assigned
|
|
144
|
-
* occurrence ID.
|
|
145
|
-
*
|
|
146
|
-
* @returns The created item copy, an `invalid_quantity` error when the
|
|
147
|
-
* quantity is not a positive integer, or a `duplicate_item_id` error when
|
|
148
|
-
* the ID is already used within the order.
|
|
149
|
-
*/
|
|
150
|
-
addItem(input: OrderItemInput): Result<OrderItem, OrderError>;
|
|
151
|
-
/**
|
|
152
|
-
* Removes an item from the order.
|
|
153
|
-
*
|
|
154
|
-
* @param itemId - The order item ID.
|
|
166
|
+
* Atomically replaces the priced content of the order.
|
|
155
167
|
*
|
|
156
|
-
*
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
*
|
|
168
|
+
* `lines` and `adjustments` are the complete desired priced content. The
|
|
169
|
+
* order-level `PriceRecord` is derived with `PriceRecord.combine`; line
|
|
170
|
+
* modifiers are provenance and are never reapplied or copied. All validation
|
|
171
|
+
* happens before any state changes, so a failure leaves the aggregate
|
|
172
|
+
* completely unchanged. Allowed only while the order is `draft`.
|
|
161
173
|
*
|
|
162
|
-
* @param
|
|
163
|
-
* @param
|
|
174
|
+
* @param lines - The complete desired line collection; must be non-empty.
|
|
175
|
+
* @param adjustments - The order-level adjustments, in application order.
|
|
164
176
|
*
|
|
165
|
-
* @returns
|
|
177
|
+
* @returns Success, or `commitment_immutable` outside `draft` /
|
|
178
|
+
* `currency_mismatch` / `price_incoherent` / `invalid_quantity` /
|
|
179
|
+
* `duplicate_item_id`.
|
|
166
180
|
*/
|
|
167
|
-
|
|
181
|
+
setPricedContent(lines: ReadonlyArray<OrderLine>, adjustments: ReadonlyArray<PriceModifier>): Result<void, OrderError>;
|
|
168
182
|
/**
|
|
169
183
|
* Submits the order: `draft` → `pending`.
|
|
170
184
|
*
|
|
185
|
+
* Before leaving `draft`, re-derives the order-level record from the stored
|
|
186
|
+
* line records and the stored order-level modifiers and requires exact
|
|
187
|
+
* commitment equality (ADR-032 AM-5c). An empty draft is validated against
|
|
188
|
+
* the canonical zero record. Reconciliation uses only `PriceRecord.combine`,
|
|
189
|
+
* never `verify()` and never an aggregate-side `PriceRecord.from()`.
|
|
190
|
+
*
|
|
171
191
|
* @returns A result indicating the success or failure of the transition.
|
|
172
192
|
*/
|
|
173
193
|
submit(): Result<void, OrderError>;
|
|
@@ -186,6 +206,9 @@ export declare class Order {
|
|
|
186
206
|
/**
|
|
187
207
|
* Cancels the order: `draft | pending | confirmed` → `cancelled`.
|
|
188
208
|
*
|
|
209
|
+
* Takes no argument: the Order is business-model neutral and the application
|
|
210
|
+
* records why the commitment ended.
|
|
211
|
+
*
|
|
189
212
|
* @returns A result indicating the success or failure of the transition.
|
|
190
213
|
*/
|
|
191
214
|
cancel(): Result<void, OrderError>;
|