@reventlessdev/online-shop-hybrid-ordering 1.0.0-alpha.152 → 1.0.0-alpha.154
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/CHANGELOG.md +24 -0
- package/package.json +8 -8
- package/src/CatalogProduct/StateViewSliceStream/AvailableProducts.res.mjs +2 -0
- package/src/CatalogProduct/StateViewSliceStream/AvailableProducts_Projection.res +1 -1
- package/src/CatalogProduct/StateViewSliceStream/AvailableProducts_Projection.res.mjs +2 -1
- package/src/Customer/Aggregate/Customer.res +8 -0
- package/src/Customer/Aggregate/Customer.res.mjs +15 -1
- package/src/Customer/Aggregate/Customer_Behavior.res +4 -0
- package/src/Customer/Aggregate/Customer_Behavior.res.mjs +10 -0
- package/src/Customer/ReadModelStream/Customers.res +5 -0
- package/src/Customer/ReadModelStream/Customers.res.mjs +2 -0
- package/src/Customer/ReadModelStream/Customers_Projections.res +4 -2
- package/src/Customer/ReadModelStream/Customers_Projections.res.mjs +16 -0
- package/src/Order/AutomationSlice/AutoShipOrder.res +6 -2
- package/src/Order/AutomationSlice/AutoShipOrder_Automation.res +16 -2
- package/src/Order/AutomationSlice/AutoShipOrder_Automation.res.mjs +21 -7
- package/src/Order/StateChangeSlice/PlaceOrder.res +15 -1
- package/src/Order/StateChangeSlice/PlaceOrder.res.mjs +11 -2
- package/src/Order/StateChangeSlice/PlaceOrder_Behavior.res +2 -2
- package/src/Order/StateChangeSlice/PlaceOrder_Behavior.res.mjs +2 -1
- package/src/Order/StateChangeSlice/ShipOrder.res +9 -2
- package/src/Order/StateChangeSlice/ShipOrder.res.mjs +10 -2
- package/src/Order/StateChangeSlice/ShipOrder_Behavior.res +3 -1
- package/src/Order/StateChangeSlice/ShipOrder_Behavior.res.mjs +10 -9
- package/src/Order/StateViewSliceStream/Orders.res +19 -1
- package/src/Order/StateViewSliceStream/Orders.res.mjs +17 -3
- package/src/Order/StateViewSliceStream/Orders_Projection.res +9 -4
- package/src/Order/StateViewSliceStream/Orders_Projection.res.mjs +15 -4
- package/src/Plugin.res +3 -4
- package/src/Plugin.res.mjs +0 -25
- package/tests/Customer/Aggregate/Customer_GWT.res +12 -0
- package/tests/Customer/Aggregate/Customer_GWT.res.mjs +24 -0
- package/tests/Customer/ReadModelStream/Customers_GWT.res +23 -2
- package/tests/Customer/ReadModelStream/Customers_GWT.res.mjs +30 -0
- package/tests/Flow/OrderingFlow_GWT.res +33 -4
- package/tests/Flow/OrderingFlow_GWT.res.mjs +10 -4
- package/tests/Order/AutomationSlice/AutoShipOrder_GWT.res +15 -3
- package/tests/Order/AutomationSlice/AutoShipOrder_GWT.res.mjs +15 -3
- package/tests/Order/StateChangeSlice/PlaceOrder_GWT.res +36 -7
- package/tests/Order/StateChangeSlice/PlaceOrder_GWT.res.mjs +30 -7
- package/tests/Order/StateChangeSlice/ShipOrder_GWT.res +3 -3
- package/tests/Order/StateChangeSlice/ShipOrder_GWT.res.mjs +12 -3
- package/tests/Order/StateViewSliceStream/Orders_GWT.res +54 -6
- package/tests/Order/StateViewSliceStream/Orders_GWT.res.mjs +33 -6
- package/src/Order/StateChangeSlice/RefundOrder.res +0 -27
- package/src/Order/StateChangeSlice/RefundOrder.res.mjs +0 -59
- package/src/Order/StateChangeSlice/RefundOrder_Behavior.res +0 -26
- package/src/Order/StateChangeSlice/RefundOrder_Behavior.res.mjs +0 -77
- package/tests/Order/StateChangeSlice/RefundOrder_GWT.res +0 -31
- package/tests/Order/StateChangeSlice/RefundOrder_GWT.res.mjs +0 -100
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,30 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
# 1.0.0-alpha.154 (2026-07-26)
|
|
7
|
+
|
|
8
|
+
* feat(examples)!: gate hybrid auto-shipping on a shipping method ([d620e12](https://github.com/ReventlessDev/reventless-core/commit/d620e1292db1b470670654588d04cc51b34d2ab9))
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* **example:** geo-point + file command inputs and a map-bearing view ([1cd89e8](https://github.com/ReventlessDev/reventless-core/commit/1cd89e8dbf256a84a2d689df58887203ea345358))
|
|
12
|
+
* **example:** product image upload replaces the customer document demo ([467b8d3](https://github.com/ReventlessDev/reventless-core/commit/467b8d3ab729be1d4fb334939453e86fdb402015))
|
|
13
|
+
* **example:** ship an order from the Orders board (manual branch) + [@target](https://github.com/target)State by constructor ([e36e14d](https://github.com/ReventlessDev/reventless-core/commit/e36e14dd2545bf2db5413e177f8296b684468a41)), closes [#6](https://github.com/ReventlessDev/reventless-core/issues/6) [#5](https://github.com/ReventlessDev/reventless-core/issues/5) [#5](https://github.com/ReventlessDev/reventless-core/issues/5) [#4](https://github.com/ReventlessDev/reventless-core/issues/4)
|
|
14
|
+
|
|
15
|
+
### BREAKING CHANGES
|
|
16
|
+
|
|
17
|
+
* PlaceOrder and OrderPlaced gain a required shippingMethod
|
|
18
|
+
field; the RefundOrder slice and its IssueRefund command are removed.
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# 1.0.0-alpha.153 (2026-07-24)
|
|
23
|
+
|
|
24
|
+
**Note:** Version bump only for package @reventlessdev/online-shop-hybrid-ordering
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
|
|
6
30
|
# 1.0.0-alpha.152 (2026-07-23)
|
|
7
31
|
|
|
8
32
|
**Note:** Version bump only for package @reventlessdev/online-shop-hybrid-ordering
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reventlessdev/online-shop-hybrid-ordering",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.154",
|
|
4
4
|
"description": "Online Shop Hybrid example — Ordering plugin (Customer aggregate + Order/CatalogProduct DCB)",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"jest": {
|
|
@@ -21,18 +21,18 @@
|
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
23
|
"sury": "^11.0.0-alpha.4",
|
|
24
|
-
"@reventlessdev/online-shop-hybrid-
|
|
24
|
+
"@reventlessdev/online-shop-hybrid-ordering-spec": "1.0.0-alpha.74",
|
|
25
25
|
"@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.17",
|
|
26
|
-
"@reventlessdev/reventless-gwt": "1.0.0-alpha.
|
|
27
|
-
"@reventlessdev/reventless-infra": "3.0.0-alpha.
|
|
28
|
-
"@reventlessdev/
|
|
29
|
-
"@reventlessdev/
|
|
30
|
-
"@reventlessdev/reventless-spec": "3.0.0-alpha.
|
|
26
|
+
"@reventlessdev/reventless-gwt": "1.0.0-alpha.128",
|
|
27
|
+
"@reventlessdev/reventless-infra": "3.0.0-alpha.105",
|
|
28
|
+
"@reventlessdev/online-shop-hybrid-catalog-spec": "1.0.0-alpha.74",
|
|
29
|
+
"@reventlessdev/reventless-local": "3.0.0-alpha.168",
|
|
30
|
+
"@reventlessdev/reventless-spec": "3.0.0-alpha.81"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"rescript": "12.3.0",
|
|
34
34
|
"sury-ppx": "11.0.0-alpha.2",
|
|
35
|
-
"@reventlessdev/reventless-ppx": "1.0.0-alpha.
|
|
35
|
+
"@reventlessdev/reventless-ppx": "1.0.0-alpha.56"
|
|
36
36
|
},
|
|
37
37
|
"peerDependencies": {
|
|
38
38
|
"rescript": "12.3.0"
|
|
@@ -3,11 +3,18 @@
|
|
|
3
3
|
|
|
4
4
|
@@reventless.spec
|
|
5
5
|
|
|
6
|
+
// A map coordinate carried by location commands/events. Emitted as a JSON-Schema
|
|
7
|
+
// object with numeric `lat`/`lng` sub-properties, which the generated command
|
|
8
|
+
// form recognises as a geo-point picker.
|
|
9
|
+
@schema
|
|
10
|
+
type location = {lat: float, lng: float}
|
|
11
|
+
|
|
6
12
|
@schema
|
|
7
13
|
type command =
|
|
8
14
|
| Register({email: string, address: string})
|
|
9
15
|
| UpdateEmail({email: string})
|
|
10
16
|
| UpdateAddress({address: string})
|
|
17
|
+
| SetLocation({location: location})
|
|
11
18
|
| Deactivate
|
|
12
19
|
|
|
13
20
|
@schema
|
|
@@ -15,6 +22,7 @@ type event =
|
|
|
15
22
|
| Registered({email: string, address: string})
|
|
16
23
|
| EmailUpdated({email: string})
|
|
17
24
|
| AddressUpdated({address: string})
|
|
25
|
+
| LocationSet({location: location})
|
|
18
26
|
| Deactivated
|
|
19
27
|
|
|
20
28
|
@schema
|
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
import * as S from "sury/src/S.res.mjs";
|
|
4
4
|
|
|
5
|
+
let locationSchema = S.schema(s => ({
|
|
6
|
+
lat: s.m(S.float),
|
|
7
|
+
lng: s.m(S.float)
|
|
8
|
+
}));
|
|
9
|
+
|
|
5
10
|
let commandSchema = S.union([
|
|
6
11
|
S.schema(s => ({
|
|
7
12
|
TAG: "Register",
|
|
@@ -16,6 +21,10 @@ let commandSchema = S.union([
|
|
|
16
21
|
TAG: "UpdateAddress",
|
|
17
22
|
address: s.m(S.string)
|
|
18
23
|
})),
|
|
24
|
+
S.schema(s => ({
|
|
25
|
+
TAG: "SetLocation",
|
|
26
|
+
location: s.m(locationSchema)
|
|
27
|
+
})),
|
|
19
28
|
S.literal("Deactivate")
|
|
20
29
|
]);
|
|
21
30
|
|
|
@@ -33,6 +42,10 @@ let eventSchema = S.union([
|
|
|
33
42
|
TAG: "AddressUpdated",
|
|
34
43
|
address: s.m(S.string)
|
|
35
44
|
})),
|
|
45
|
+
S.schema(s => ({
|
|
46
|
+
TAG: "LocationSet",
|
|
47
|
+
location: s.m(locationSchema)
|
|
48
|
+
})),
|
|
36
49
|
S.literal("Deactivated")
|
|
37
50
|
]);
|
|
38
51
|
|
|
@@ -55,10 +68,11 @@ let moduleUrl = "@reventlessdev/online-shop-hybrid-ordering/src/Customer/Aggrega
|
|
|
55
68
|
export {
|
|
56
69
|
name,
|
|
57
70
|
Id,
|
|
71
|
+
locationSchema,
|
|
58
72
|
commandSchema,
|
|
59
73
|
eventSchema,
|
|
60
74
|
errorSchema,
|
|
61
75
|
moduleUrl,
|
|
62
76
|
commandAuthorization,
|
|
63
77
|
}
|
|
64
|
-
/*
|
|
78
|
+
/* locationSchema Not a pure module */
|
|
@@ -17,6 +17,7 @@ let evolve = (state, event) =>
|
|
|
17
17
|
| (Active(_), Registered({email, address})) => Active({email, address})
|
|
18
18
|
| (Active(s), EmailUpdated({email})) => Active({...s, email})
|
|
19
19
|
| (Active(s), AddressUpdated({address})) => Active({...s, address})
|
|
20
|
+
| (Active(_), LocationSet(_)) => state
|
|
20
21
|
| (Active(_), Customer.Deactivated) => Deactivated
|
|
21
22
|
| (Deactivated, _) => state
|
|
22
23
|
| (NotCreated, _) => state
|
|
@@ -33,9 +34,12 @@ let decide = (state, command) =>
|
|
|
33
34
|
| (Active(_), UpdateEmail({email})) => Ok([EmailUpdated({email: email})])
|
|
34
35
|
| (Active(s), UpdateAddress({address})) if address == s.address => Ok([])
|
|
35
36
|
| (Active(_), UpdateAddress({address})) => Ok([AddressUpdated({address: address})])
|
|
37
|
+
| (Active(_), SetLocation({location})) => Ok([LocationSet({location: location})])
|
|
36
38
|
| (Active(_), Deactivate) => Ok([Customer.Deactivated])
|
|
39
|
+
| (NotCreated, SetLocation(_)) => Error(CustomerNotFound)
|
|
37
40
|
| (Deactivated, Register(_)) => Error(CustomerAlreadyDeactivated)
|
|
38
41
|
| (Deactivated, UpdateEmail(_)) => Error(CustomerAlreadyDeactivated)
|
|
39
42
|
| (Deactivated, UpdateAddress(_)) => Error(CustomerAlreadyDeactivated)
|
|
43
|
+
| (Deactivated, SetLocation(_)) => Error(CustomerAlreadyDeactivated)
|
|
40
44
|
| (Deactivated, Deactivate) => Ok([]) // idempotent
|
|
41
45
|
}
|
|
@@ -46,6 +46,8 @@ function evolve(state, event) {
|
|
|
46
46
|
email: state.email,
|
|
47
47
|
address: event.address
|
|
48
48
|
};
|
|
49
|
+
case "LocationSet" :
|
|
50
|
+
return state;
|
|
49
51
|
}
|
|
50
52
|
}
|
|
51
53
|
|
|
@@ -123,6 +125,14 @@ function decide(state, command) {
|
|
|
123
125
|
}]
|
|
124
126
|
};
|
|
125
127
|
}
|
|
128
|
+
case "SetLocation" :
|
|
129
|
+
return {
|
|
130
|
+
TAG: "Ok",
|
|
131
|
+
_0: [{
|
|
132
|
+
TAG: "LocationSet",
|
|
133
|
+
location: command.location
|
|
134
|
+
}]
|
|
135
|
+
};
|
|
126
136
|
}
|
|
127
137
|
}
|
|
128
138
|
|
|
@@ -6,10 +6,15 @@
|
|
|
6
6
|
|
|
7
7
|
@@reventless.spec
|
|
8
8
|
|
|
9
|
+
// `lat`/`lng` are a numeric coordinate pair, so the generated read-model view
|
|
10
|
+
// offers a map display that drops a pin per customer. They are fed by the
|
|
11
|
+
// Customer aggregate's `LocationSet` event (see `Customers_Projections.res`).
|
|
9
12
|
@schema
|
|
10
13
|
type state = {
|
|
11
14
|
@displayName email: string,
|
|
12
15
|
address: string,
|
|
16
|
+
lat: float,
|
|
17
|
+
lng: float,
|
|
13
18
|
deactivated: bool,
|
|
14
19
|
orderCount: int,
|
|
15
20
|
}
|
|
@@ -7,6 +7,8 @@ import * as DisplayName$Reventless from "@reventlessdev/reventless-spec/src/comp
|
|
|
7
7
|
let stateSchema = S.schema(s => ({
|
|
8
8
|
email: s.m(S.string),
|
|
9
9
|
address: s.m(S.string),
|
|
10
|
+
lat: s.m(S.float),
|
|
11
|
+
lng: s.m(S.float),
|
|
10
12
|
deactivated: s.m(S.bool),
|
|
11
13
|
orderCount: s.m(S.int),
|
|
12
14
|
displayName: s.m(S.option(S.string))
|
|
@@ -44,11 +44,13 @@ module CustomerMapping = Mapping.Make(
|
|
|
44
44
|
| Registered({email, address}) =>
|
|
45
45
|
UpdateWithDefault(
|
|
46
46
|
id,
|
|
47
|
-
{Customers.email: email, address, deactivated: false, orderCount: 0},
|
|
47
|
+
{Customers.email: email, address, lat: 0.0, lng: 0.0, deactivated: false, orderCount: 0},
|
|
48
48
|
state => {...state, email, address, deactivated: false},
|
|
49
49
|
)
|
|
50
50
|
| EmailUpdated({email}) => Update(id, state => {...state, email})
|
|
51
51
|
| AddressUpdated({address}) => Update(id, state => {...state, address})
|
|
52
|
+
| LocationSet({location}) =>
|
|
53
|
+
Update(id, state => {...state, lat: location.lat, lng: location.lng})
|
|
52
54
|
| Deactivated => Update(id, state => {...state, deactivated: true})
|
|
53
55
|
}
|
|
54
56
|
},
|
|
@@ -68,7 +70,7 @@ module CustomerOrdersMapping = Mapping.Make(
|
|
|
68
70
|
| OrderPlaced({customerId}) =>
|
|
69
71
|
UpdateWithDefault(
|
|
70
72
|
customerId,
|
|
71
|
-
{Customers.email: "", address: "", deactivated: false, orderCount: 1},
|
|
73
|
+
{Customers.email: "", address: "", lat: 0.0, lng: 0.0, deactivated: false, orderCount: 1},
|
|
72
74
|
state => {...state, orderCount: state.orderCount + 1},
|
|
73
75
|
)
|
|
74
76
|
}
|
|
@@ -52,6 +52,8 @@ function project(param) {
|
|
|
52
52
|
_1: {
|
|
53
53
|
email: email,
|
|
54
54
|
address: address,
|
|
55
|
+
lat: 0.0,
|
|
56
|
+
lng: 0.0,
|
|
55
57
|
deactivated: false,
|
|
56
58
|
orderCount: 0
|
|
57
59
|
},
|
|
@@ -85,6 +87,18 @@ function project(param) {
|
|
|
85
87
|
return newrecord;
|
|
86
88
|
}
|
|
87
89
|
};
|
|
90
|
+
case "LocationSet" :
|
|
91
|
+
let location = event.location;
|
|
92
|
+
return {
|
|
93
|
+
TAG: "Update",
|
|
94
|
+
_0: id,
|
|
95
|
+
_1: state => {
|
|
96
|
+
let newrecord = {...state};
|
|
97
|
+
newrecord.lng = location.lng;
|
|
98
|
+
newrecord.lat = location.lat;
|
|
99
|
+
return newrecord;
|
|
100
|
+
}
|
|
101
|
+
};
|
|
88
102
|
}
|
|
89
103
|
}
|
|
90
104
|
|
|
@@ -108,6 +122,8 @@ function project$1(param) {
|
|
|
108
122
|
_1: {
|
|
109
123
|
email: "",
|
|
110
124
|
address: "",
|
|
125
|
+
lat: 0.0,
|
|
126
|
+
lng: 0.0,
|
|
111
127
|
deactivated: false,
|
|
112
128
|
orderCount: 1
|
|
113
129
|
},
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
// AutoShipOrder AutomationSlice.
|
|
2
|
-
//
|
|
3
|
-
// Resolved when OrderShipped arrives.
|
|
2
|
+
// Expedited dispatch: when an Express order is placed, issue ShipOrder without
|
|
3
|
+
// waiting for the batch run. Resolved when OrderShipped arrives.
|
|
4
|
+
//
|
|
5
|
+
// Standard orders ship with the batch (an explicit ShipOrder) and Pickup orders
|
|
6
|
+
// are collected in store, so neither is this slice's business — `collect` in the
|
|
7
|
+
// sibling `_Automation.res` admits only Express.
|
|
4
8
|
//
|
|
5
9
|
// Consumed events come from per-source `Mapping.Make` modules in the sibling
|
|
6
10
|
// `_Automation.res`. The framework derives the consumed-event set from each
|
|
@@ -6,9 +6,16 @@
|
|
|
6
6
|
// registers under.
|
|
7
7
|
module OrderingDcbSource = {
|
|
8
8
|
let name = "OrderingDcbEventLog"
|
|
9
|
+
|
|
10
|
+
@schema
|
|
11
|
+
type shippingMethod =
|
|
12
|
+
| Standard
|
|
13
|
+
| Express
|
|
14
|
+
| Pickup
|
|
15
|
+
|
|
9
16
|
@schema
|
|
10
17
|
type event =
|
|
11
|
-
| OrderPlaced({orderId: string})
|
|
18
|
+
| OrderPlaced({orderId: string, shippingMethod: shippingMethod})
|
|
12
19
|
| OrderShipped({orderId: string})
|
|
13
20
|
}
|
|
14
21
|
|
|
@@ -18,9 +25,16 @@ module FromOrderingDcb = Mapping.Make(
|
|
|
18
25
|
{
|
|
19
26
|
open OrderingDcbSource
|
|
20
27
|
|
|
28
|
+
// Filtering here rather than in `process` is deliberate: a todo row is a
|
|
29
|
+
// claim that this slice owes an action. Admitting Standard and Pickup and
|
|
30
|
+
// then declining them in `process` would leave rows Pending forever, so the
|
|
31
|
+
// todo view would show a backlog that is never worked off.
|
|
21
32
|
let collect = (event, _ctx) =>
|
|
22
33
|
switch event {
|
|
23
|
-
| OrderPlaced({orderId}) => [
|
|
34
|
+
| OrderPlaced({orderId, shippingMethod: Express}) => [
|
|
35
|
+
(orderId, ({orderId: orderId}: AutoShipOrder.todoItem)),
|
|
36
|
+
]
|
|
37
|
+
| OrderPlaced(_) => []
|
|
24
38
|
| OrderShipped(_) => []
|
|
25
39
|
}
|
|
26
40
|
|
|
@@ -18,10 +18,17 @@ let M = AutomationSlice$Reventless.Mappings.Make({
|
|
|
18
18
|
|
|
19
19
|
let name = "OrderingDcbEventLog";
|
|
20
20
|
|
|
21
|
+
let shippingMethodSchema = S.union([
|
|
22
|
+
S.literal("Standard"),
|
|
23
|
+
S.literal("Express"),
|
|
24
|
+
S.literal("Pickup")
|
|
25
|
+
]);
|
|
26
|
+
|
|
21
27
|
let eventSchema = S.union([
|
|
22
28
|
S.schema(s => ({
|
|
23
29
|
TAG: "OrderPlaced",
|
|
24
|
-
orderId: s.m(DcbTag$Reventless.string)
|
|
30
|
+
orderId: s.m(DcbTag$Reventless.string),
|
|
31
|
+
shippingMethod: s.m(shippingMethodSchema)
|
|
25
32
|
})),
|
|
26
33
|
S.schema(s => ({
|
|
27
34
|
TAG: "OrderShipped",
|
|
@@ -32,6 +39,7 @@ let eventSchema = S.union([
|
|
|
32
39
|
let OrderingDcbSource = {
|
|
33
40
|
Id: undefined,
|
|
34
41
|
name: name,
|
|
42
|
+
shippingMethodSchema: shippingMethodSchema,
|
|
35
43
|
eventSchema: eventSchema
|
|
36
44
|
};
|
|
37
45
|
|
|
@@ -40,12 +48,18 @@ function collect(event, _ctx) {
|
|
|
40
48
|
return [];
|
|
41
49
|
}
|
|
42
50
|
let orderId = event.orderId;
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
51
|
+
switch (event.shippingMethod) {
|
|
52
|
+
case "Express" :
|
|
53
|
+
return [[
|
|
54
|
+
orderId,
|
|
55
|
+
{
|
|
56
|
+
orderId: orderId
|
|
57
|
+
}
|
|
58
|
+
]];
|
|
59
|
+
case "Standard" :
|
|
60
|
+
case "Pickup" :
|
|
61
|
+
return [];
|
|
62
|
+
}
|
|
49
63
|
}
|
|
50
64
|
|
|
51
65
|
function resolve(event) {
|
|
@@ -9,6 +9,14 @@ type consumedEvent =
|
|
|
9
9
|
| OrderPlaced({orderId: string})
|
|
10
10
|
| CatalogProductSynced({productId: string})
|
|
11
11
|
|
|
12
|
+
// Declared in the order the UI should present them: the batched default first,
|
|
13
|
+
// then the expedited option, then in-store collection.
|
|
14
|
+
@schema
|
|
15
|
+
type shippingMethod =
|
|
16
|
+
| Standard
|
|
17
|
+
| Express
|
|
18
|
+
| Pickup
|
|
19
|
+
|
|
12
20
|
@schema
|
|
13
21
|
type command =
|
|
14
22
|
PlaceOrder({
|
|
@@ -16,6 +24,7 @@ type command =
|
|
|
16
24
|
// customerId is payload, not a query key — @noDcbTag stops it auto-tagging.
|
|
17
25
|
@noDcbTag customerId: string,
|
|
18
26
|
@ref("AvailableProducts") productIds: array<string>,
|
|
27
|
+
shippingMethod: shippingMethod,
|
|
19
28
|
})
|
|
20
29
|
|
|
21
30
|
@schema
|
|
@@ -25,4 +34,9 @@ type error =
|
|
|
25
34
|
|
|
26
35
|
@schema
|
|
27
36
|
type event =
|
|
28
|
-
OrderPlaced({
|
|
37
|
+
OrderPlaced({
|
|
38
|
+
@partitionTag orderId: string,
|
|
39
|
+
customerId: string,
|
|
40
|
+
productIds: array<string>,
|
|
41
|
+
shippingMethod: shippingMethod,
|
|
42
|
+
})
|
|
@@ -15,11 +15,18 @@ let consumedEventSchema = S.union([
|
|
|
15
15
|
}))
|
|
16
16
|
]);
|
|
17
17
|
|
|
18
|
+
let shippingMethodSchema = S.union([
|
|
19
|
+
S.literal("Standard"),
|
|
20
|
+
S.literal("Express"),
|
|
21
|
+
S.literal("Pickup")
|
|
22
|
+
]);
|
|
23
|
+
|
|
18
24
|
let commandSchema = S.schema(s => ({
|
|
19
25
|
TAG: "PlaceOrder",
|
|
20
26
|
orderId: s.m(DcbTag$Reventless.partition),
|
|
21
27
|
customerId: s.m(S.string),
|
|
22
|
-
productIds: s.m(S.array(Reference$Reventless.to_(undefined, "productId", "AvailableProducts")))
|
|
28
|
+
productIds: s.m(S.array(Reference$Reventless.to_(undefined, "productId", "AvailableProducts"))),
|
|
29
|
+
shippingMethod: s.m(shippingMethodSchema)
|
|
23
30
|
}));
|
|
24
31
|
|
|
25
32
|
let errorSchema = S.union([
|
|
@@ -34,7 +41,8 @@ let eventSchema = S.schema(s => ({
|
|
|
34
41
|
TAG: "OrderPlaced",
|
|
35
42
|
orderId: s.m(DcbTag$Reventless.partition),
|
|
36
43
|
customerId: s.m(DcbTag$Reventless.string),
|
|
37
|
-
productIds: s.m(S.array(DcbTag$Reventless.stringForKey("productId")))
|
|
44
|
+
productIds: s.m(S.array(DcbTag$Reventless.stringForKey("productId"))),
|
|
45
|
+
shippingMethod: s.m(shippingMethodSchema)
|
|
38
46
|
}));
|
|
39
47
|
|
|
40
48
|
function commandAuthorization(param) {
|
|
@@ -53,6 +61,7 @@ export {
|
|
|
53
61
|
name,
|
|
54
62
|
Id,
|
|
55
63
|
consumedEventSchema,
|
|
64
|
+
shippingMethodSchema,
|
|
56
65
|
commandSchema,
|
|
57
66
|
errorSchema,
|
|
58
67
|
eventSchema,
|
|
@@ -25,7 +25,7 @@ let evolve = (state, event: consumedEvent) =>
|
|
|
25
25
|
|
|
26
26
|
let decide = (state, command) =>
|
|
27
27
|
switch command {
|
|
28
|
-
| PlaceOrder({orderId, customerId, productIds}) =>
|
|
28
|
+
| PlaceOrder({orderId, customerId, productIds, shippingMethod}) =>
|
|
29
29
|
if state.placedOrderIds->Array.includes(orderId) {
|
|
30
30
|
Error(OrderAlreadyPlaced)
|
|
31
31
|
} else {
|
|
@@ -33,7 +33,7 @@ let decide = (state, command) =>
|
|
|
33
33
|
if missing->Array.length > 0 {
|
|
34
34
|
Error(ProductsNotAvailable({missing: missing}))
|
|
35
35
|
} else {
|
|
36
|
-
Ok([OrderPlaced({orderId, customerId, productIds})])
|
|
36
|
+
Ok([OrderPlaced({orderId, customerId, productIds, shippingMethod})])
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
39
|
}
|
|
@@ -1,17 +1,24 @@
|
|
|
1
1
|
// ShipOrder StateChangeSlice.
|
|
2
2
|
// Requires order to exist and not be cancelled; idempotent if already shipped.
|
|
3
|
+
//
|
|
4
|
+
// `OrderPlaced` carries a payload so it survives the DCB payload-less filter and
|
|
5
|
+
// lands in `consumedEventTypes`, overlapping the Orders view's consumed events.
|
|
6
|
+
// That is what links this slice to Orders (`consistencyRead=Orders`) so AutoUI's
|
|
7
|
+
// board offers it — shipping is a manual operator action on the Orders board, not
|
|
8
|
+
// automation-only. `@targetState("Shipped")` declares the transition outright, so
|
|
9
|
+
// the board resolves the drop via its DeclaredTarget tier rather than guessing.
|
|
3
10
|
|
|
4
11
|
@@reventless.spec
|
|
5
12
|
|
|
6
13
|
@schema
|
|
7
14
|
type consumedEvent =
|
|
8
|
-
| OrderPlaced
|
|
15
|
+
| OrderPlaced({productIds: array<string>})
|
|
9
16
|
| OrderShipped
|
|
10
17
|
| OrderCancelled
|
|
11
18
|
|
|
12
19
|
@schema
|
|
13
20
|
type command =
|
|
14
|
-
| @allowedStates([Orders.Placed]) ShipOrder({orderId: string})
|
|
21
|
+
| @allowedStates([Orders.Placed]) @targetState(Orders.Shipped) ShipOrder({orderId: string})
|
|
15
22
|
|
|
16
23
|
@schema
|
|
17
24
|
type error =
|
|
@@ -5,7 +5,10 @@ import * as DcbTag$Reventless from "@reventlessdev/reventless-spec/src/component
|
|
|
5
5
|
import * as Api$ReventlessInfra from "@reventlessdev/reventless-infra/src/components/Api.res.mjs";
|
|
6
6
|
|
|
7
7
|
let consumedEventSchema = S.union([
|
|
8
|
-
S.
|
|
8
|
+
S.schema(s => ({
|
|
9
|
+
TAG: "OrderPlaced",
|
|
10
|
+
productIds: s.m(S.array(DcbTag$Reventless.stringForKey("productId")))
|
|
11
|
+
})),
|
|
9
12
|
S.literal("OrderShipped"),
|
|
10
13
|
S.literal("OrderCancelled")
|
|
11
14
|
]);
|
|
@@ -30,6 +33,11 @@ let commandSchema$1 = Api$ReventlessInfra.markAllowedStates(commandSchema, [[
|
|
|
30
33
|
["Placed"]
|
|
31
34
|
]]);
|
|
32
35
|
|
|
36
|
+
let commandSchema$2 = Api$ReventlessInfra.markTargetState(commandSchema$1, [[
|
|
37
|
+
"ShipOrder",
|
|
38
|
+
"Shipped"
|
|
39
|
+
]]);
|
|
40
|
+
|
|
33
41
|
function commandAuthorization(param) {
|
|
34
42
|
return "AllowAuthenticated";
|
|
35
43
|
}
|
|
@@ -48,7 +56,7 @@ export {
|
|
|
48
56
|
consumedEventSchema,
|
|
49
57
|
errorSchema,
|
|
50
58
|
eventSchema,
|
|
51
|
-
commandSchema$
|
|
59
|
+
commandSchema$2 as commandSchema,
|
|
52
60
|
moduleUrl,
|
|
53
61
|
commandAuthorization,
|
|
54
62
|
readConsistency,
|
|
@@ -6,7 +6,9 @@ let initialState = {exists: false, shipped: false, cancelled: false}
|
|
|
6
6
|
|
|
7
7
|
let evolve = (state, event) =>
|
|
8
8
|
switch event {
|
|
9
|
-
|
|
9
|
+
// productIds is carried only to keep OrderPlaced out of the payload-less
|
|
10
|
+
// filter (so the slice links to Orders); ShipOrder's decision doesn't use it.
|
|
11
|
+
| OrderPlaced(_) => {exists: true, shipped: false, cancelled: false}
|
|
10
12
|
| OrderShipped => {...state, shipped: true}
|
|
11
13
|
| OrderCancelled => {...state, cancelled: true}
|
|
12
14
|
}
|
|
@@ -2,25 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
function evolve(state, event) {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
return {
|
|
8
|
-
exists: true,
|
|
9
|
-
shipped: false,
|
|
10
|
-
cancelled: false
|
|
11
|
-
};
|
|
12
|
-
case "OrderShipped" :
|
|
5
|
+
if (typeof event !== "object") {
|
|
6
|
+
if (event === "OrderShipped") {
|
|
13
7
|
return {
|
|
14
8
|
exists: state.exists,
|
|
15
9
|
shipped: true,
|
|
16
10
|
cancelled: state.cancelled
|
|
17
11
|
};
|
|
18
|
-
|
|
12
|
+
} else {
|
|
19
13
|
return {
|
|
20
14
|
exists: state.exists,
|
|
21
15
|
shipped: state.shipped,
|
|
22
16
|
cancelled: true
|
|
23
17
|
};
|
|
18
|
+
}
|
|
19
|
+
} else {
|
|
20
|
+
return {
|
|
21
|
+
exists: true,
|
|
22
|
+
shipped: false,
|
|
23
|
+
cancelled: false
|
|
24
|
+
};
|
|
24
25
|
}
|
|
25
26
|
}
|
|
26
27
|
|
|
@@ -3,9 +3,20 @@
|
|
|
3
3
|
|
|
4
4
|
@@reventless.spec
|
|
5
5
|
|
|
6
|
+
@schema
|
|
7
|
+
type shippingMethod =
|
|
8
|
+
| Standard
|
|
9
|
+
| Express
|
|
10
|
+
| Pickup
|
|
11
|
+
|
|
6
12
|
@schema
|
|
7
13
|
type consumedEvent =
|
|
8
|
-
| OrderPlaced({
|
|
14
|
+
| OrderPlaced({
|
|
15
|
+
orderId: string,
|
|
16
|
+
customerId: string,
|
|
17
|
+
productIds: array<string>,
|
|
18
|
+
shippingMethod: shippingMethod,
|
|
19
|
+
})
|
|
9
20
|
| OrderShipped({orderId: string})
|
|
10
21
|
| OrderCancelled({orderId: string})
|
|
11
22
|
|
|
@@ -21,4 +32,11 @@ type state = {
|
|
|
21
32
|
customerId: string,
|
|
22
33
|
productIds: array<string>,
|
|
23
34
|
@status status: status,
|
|
35
|
+
shippingMethod: shippingMethod,
|
|
36
|
+
// Producer timestamps taken from the event envelope's `meta.time` — no need to
|
|
37
|
+
// carry the time in the event payload. The `DateTime` marker surfaces
|
|
38
|
+
// `format: "date-time"` on the state's JSON Schema, which the AutoUI date
|
|
39
|
+
// views (Calendar/Timeline) key off. `shippedAt` is "" until the order ships.
|
|
40
|
+
placedAt: @s.matches(Reventless.DateTime.string) string,
|
|
41
|
+
shippedAt: @s.matches(Reventless.DateTime.string) string,
|
|
24
42
|
}
|