docs(customer-orders-bff): align customer workflows to shared lifecycle

This commit is contained in:
José René White Enciso 2026-03-31 18:51:36 -06:00
parent 40933edb84
commit 59554a217f
5 changed files with 22 additions and 18 deletions

View File

@ -26,4 +26,5 @@ This BFF exposes customer-facing order submission, status, detail, and history w
## Notes ## Notes
- Customer order submission currently maps `ItemIds.Count` into the upstream restaurant order workflow because the internal service contract still accepts aggregate item counts. - Customer order submission currently maps `ItemIds.Count` into the upstream restaurant order workflow because the internal service contract still accepts aggregate item counts.
- Successful submission writes into the same shared restaurant lifecycle later observed by waiter-floor, kitchen-ops, and POS projections.
- Correlation IDs are preserved through Thalos session checks and operations-service calls. - Correlation IDs are preserved through Thalos session checks and operations-service calls.

View File

@ -9,11 +9,10 @@ customer-orders-bff
- Epic 3: Improve observability and operational readiness for demo compose environments. - Epic 3: Improve observability and operational readiness for demo compose environments.
## Domain-Specific Candidate Features ## Domain-Specific Candidate Features
- Order lifecycle consistency and state transitions. - Customer-facing projection over the shared restaurant lifecycle.
- Customer order detail and history views that stay aligned with operations-service workflows. - Customer order detail and history views that stay aligned with waiter, kitchen, and POS state changes.
- Kitchen queue and dispatch optimization hooks. - Cross-app order continuity from customer submission to kitchen preparation and POS payment.
- Operations control-plane policies (flags, service windows, overrides). - Operations control-plane policies (flags, service windows, overrides).
- POS closeout and settlement summary alignment.
## Documentation Contract ## Documentation Contract
Any code change in this repository must include docs updates in the same branch. Any code change in this repository must include docs updates in the same branch.

View File

@ -39,5 +39,6 @@ docker run --rm -p 8080:8080 --name customer-orders-bff agilewebs/customer-order
- Integration artifact path: `greenfield/demo/restaurant/docker-compose.yml` - Integration artifact path: `greenfield/demo/restaurant/docker-compose.yml`
## Known Limitations ## Known Limitations
- Customer-orders now delegates workflow snapshots to `operations-service`, but the upstream operations adapter still serves deterministic demo data rather than database-backed state. - Customer-orders now delegates workflow snapshots to `operations-service`, which projects persisted shared lifecycle state from `operations-dal`.
- Kitchen and POS visibility still depend on the remaining Stage 46-48 restaurant flow tasks being wired end-to-end.
- Demo PostgreSQL seeds validate integration contracts and smoke determinism, but do not yet imply full persistence implementation parity. - Demo PostgreSQL seeds validate integration contracts and smoke determinism, but do not yet imply full persistence implementation parity.

View File

@ -46,7 +46,8 @@ public sealed class OperationsCustomerOrdersServiceClient(HttpClient httpClient)
public async Task<SubmitCustomerOrderResponse> SubmitOrderAsync(SubmitCustomerOrderRequest request, CancellationToken cancellationToken) public async Task<SubmitCustomerOrderResponse> SubmitOrderAsync(SubmitCustomerOrderRequest request, CancellationToken cancellationToken)
{ {
// operations-service still accepts aggregate item counts for restaurant order submission. // The customer BFF writes into the same shared restaurant lifecycle used by waiter, kitchen, and POS.
// The upstream contract still accepts aggregate counts, so the edge contract projects item IDs into count.
var payload = await SubmitOrderPayloadAsync( var payload = await SubmitOrderPayloadAsync(
new SubmitRestaurantOrderPayload(request.ContextId, request.OrderId, request.TableId, request.ItemIds.Count), new SubmitRestaurantOrderPayload(request.ContextId, request.OrderId, request.TableId, request.ItemIds.Count),
cancellationToken); cancellationToken);

View File

@ -24,10 +24,11 @@ public sealed class OperationsCustomerOrdersServiceClientTests
{ {
var adapter = new OperationsCustomerOrdersServiceClient(CreateClient(StatusPayload)); var adapter = new OperationsCustomerOrdersServiceClient(CreateClient(StatusPayload));
var response = await adapter.FetchDetailAsync(new GetCustomerOrderDetailRequest("demo-context", "CO-1001"), CancellationToken.None); var response = await adapter.FetchDetailAsync(new GetCustomerOrderDetailRequest("demo-context", "ORD-1001"), CancellationToken.None);
Assert.NotNull(response.Order); Assert.NotNull(response.Order);
Assert.Equal("CO-1001", response.Order!.OrderId); Assert.Equal("ORD-1001", response.Order!.OrderId);
Assert.Contains("preparing", response.Summary, StringComparison.OrdinalIgnoreCase);
} }
[Fact] [Fact]
@ -42,25 +43,26 @@ public sealed class OperationsCustomerOrdersServiceClientTests
} }
[Fact] [Fact]
public async Task SubmitOrderAsync_MapsSubmitPayloadUsingItemCount() public async Task SubmitOrderAsync_MapsSharedLifecycleAcceptanceUsingItemCount()
{ {
var adapter = new OperationsCustomerOrdersServiceClient(CreateClient(""" var adapter = new OperationsCustomerOrdersServiceClient(CreateClient("""
{ {
"contextId": "demo-context", "contextId": "demo-context",
"orderId": "CO-1009", "orderId": "ORD-1009",
"accepted": true, "accepted": true,
"summary": "Order CO-1009 for table T-18 was accepted with 3 items.", "summary": "Order ORD-1009 was accepted and is ready for kitchen dispatch.",
"status": "queued", "status": "accepted",
"submittedAtUtc": "2026-03-31T12:30:00Z" "submittedAtUtc": "2026-03-31T12:30:00Z"
} }
""")); """));
var response = await adapter.SubmitOrderAsync( var response = await adapter.SubmitOrderAsync(
new SubmitCustomerOrderRequest("demo-context", "CO-1009", "T-18", 4, ["ITEM-101", "ITEM-202", "ITEM-303"]), new SubmitCustomerOrderRequest("demo-context", "ORD-1009", "T-18", 4, ["ITEM-101", "ITEM-202", "ITEM-303"]),
CancellationToken.None); CancellationToken.None);
Assert.True(response.Accepted); Assert.True(response.Accepted);
Assert.Equal("queued", response.Status); Assert.Equal("accepted", response.Status);
Assert.Contains("kitchen dispatch", response.Summary);
} }
private static HttpClient CreateClient(string json) private static HttpClient CreateClient(string json)
@ -88,14 +90,14 @@ public sealed class OperationsCustomerOrdersServiceClientTests
"summary": "2 recent customer orders are visible for the active context.", "summary": "2 recent customer orders are visible for the active context.",
"orders": [ "orders": [
{ {
"orderId": "CO-1001", "orderId": "ORD-1001",
"tableId": "T-08", "tableId": "T-08",
"status": "preparing", "status": "preparing",
"guestCount": 2, "guestCount": 2,
"itemIds": [ "ITEM-101", "ITEM-202" ] "itemIds": [ "ITEM-101", "ITEM-202" ]
}, },
{ {
"orderId": "CO-1002", "orderId": "ORD-1002",
"tableId": "T-15", "tableId": "T-15",
"status": "ready", "status": "ready",
"guestCount": 4, "guestCount": 4,
@ -103,8 +105,8 @@ public sealed class OperationsCustomerOrdersServiceClientTests
} }
], ],
"recentEvents": [ "recentEvents": [
"CO-1001 moved to preparing at kitchen hot-line station.", "Order ORD-1001 moved to preparing at the kitchen hot-line station.",
"CO-1002 is ready for table pickup." "Order ORD-1002 is ready for table pickup."
] ]
} }
"""; """;