Compare commits

..

No commits in common. "a347eebde42dc7c360f62d600aaea7ffebd5c556" and "bf787069979a225ee93b4ab246135eb0d002713b" have entirely different histories.

5 changed files with 20 additions and 27 deletions

View File

@ -26,6 +26,4 @@ This BFF exposes execution-facing waiter workflows over REST while delegating or
## Notes ## Notes
- The update route currently reuses the operations order submission contract so waiter-floor can expose update semantics without introducing a new cross-repo dependency. - The update route currently reuses the operations order submission contract so waiter-floor can expose update semantics without introducing a new cross-repo dependency.
- Both order mutations project into the shared restaurant lifecycle owned by `operations-service`, which means kitchen and POS can observe the same order/check state through the same runtime path.
- Assignment and recent-activity reads are already lifecycle-backed through `operations-service`, so this BFF does not need a separate summary-projection fallback for the Stage 49 propagation wave.
- 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,12 @@ waiter-floor-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
- Waiter-facing projection over the shared restaurant order/check lifecycle. - Order lifecycle consistency and state transitions.
- Waiter assignment visibility with recent floor activity derived from persisted restaurant state. - Waiter assignment visibility with recent floor activity context.
- Waiter order update workflows that stay aligned with service-level restaurant order orchestration. - Waiter order update workflows that stay aligned with service-level restaurant order orchestration.
- Cross-app order continuity from waiter submission to kitchen preparation and POS payment. - Kitchen queue and dispatch optimization hooks.
- 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,6 +39,5 @@ docker run --rm -p 8080:8080 --name waiter-floor-bff agilewebs/waiter-floor-bff:
- Integration artifact path: `greenfield/demo/restaurant/docker-compose.yml` - Integration artifact path: `greenfield/demo/restaurant/docker-compose.yml`
## Known Limitations ## Known Limitations
- Waiter-floor now delegates workflow snapshots to `operations-service`, which in turn projects persisted shared lifecycle state from `operations-dal`. - Waiter-floor now delegates workflow snapshots to `operations-service`, but the upstream operations adapter still serves deterministic demo data rather than database-backed state.
- Kitchen-driven progression and POS payment visibility 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

@ -57,8 +57,7 @@ public sealed class OperationsWaiterServiceClient(HttpClient httpClient) : IWait
new SubmitRestaurantOrderPayload(request.ContextId, request.OrderId, request.TableId, request.ItemCount), new SubmitRestaurantOrderPayload(request.ContextId, request.OrderId, request.TableId, request.ItemCount),
cancellationToken); cancellationToken);
// The waiter BFF deliberately reuses the shared order-write contract so both submit and update // operations-service currently accepts a full order snapshot for both submit and update semantics.
// actions stay aligned with the canonical restaurant lifecycle owned by operations-service.
var summary = payload.Accepted var summary = payload.Accepted
? $"Updated order {payload.OrderId}. {payload.Summary}" ? $"Updated order {payload.OrderId}. {payload.Summary}"
: payload.Summary; : payload.Summary;

View File

@ -15,12 +15,12 @@ public sealed class OperationsWaiterServiceClientTests
{ {
"contextId": "demo-context", "contextId": "demo-context",
"locationId": "restaurant-demo", "locationId": "restaurant-demo",
"summary": "2 tables currently require floor attention.", "summary": "2 active waiter assignments are currently visible.",
"assignments": [ "assignments": [
{ "waiterId": "service-pool", "tableId": "T-12", "status": "Preparing", "activeOrders": 2 } { "waiterId": "waiter-01", "tableId": "T-12", "status": "serving", "activeOrders": 2 }
], ],
"recentActivity": [ "recentActivity": [
"Order ORD-1002 is currently preparing for table T-12." "demo-context: table T-12 requested dessert menus"
] ]
} }
"""); """);
@ -31,8 +31,7 @@ public sealed class OperationsWaiterServiceClientTests
Assert.Equal("restaurant-demo", response.LocationId); Assert.Equal("restaurant-demo", response.LocationId);
Assert.Single(response.Assignments); Assert.Single(response.Assignments);
Assert.Single(response.RecentActivity); Assert.Single(response.RecentActivity);
Assert.Equal("service-pool", response.Assignments.Single().WaiterId); Assert.Equal("waiter-01", response.Assignments.Single().WaiterId);
Assert.Equal("Preparing", response.Assignments.Single().Status);
} }
[Fact] [Fact]
@ -42,10 +41,10 @@ public sealed class OperationsWaiterServiceClientTests
{ {
"contextId": "demo-context", "contextId": "demo-context",
"locationId": "restaurant-demo", "locationId": "restaurant-demo",
"summary": "2 tables currently require floor attention.", "summary": "2 active waiter assignments are currently visible.",
"assignments": [], "assignments": [],
"recentActivity": [ "recentActivity": [
"Order ORD-1003 was served at table T-21 and is ready for payment capture." "demo-context: table T-08 is waiting for payment capture"
] ]
} }
"""); """);
@ -55,19 +54,18 @@ public sealed class OperationsWaiterServiceClientTests
Assert.Equal("demo-context", response.ContextId); Assert.Equal("demo-context", response.ContextId);
Assert.Single(response.RecentActivity); Assert.Single(response.RecentActivity);
Assert.Contains("floor attention", response.Summary);
} }
[Fact] [Fact]
public async Task SubmitOrderAsync_MapsSharedLifecycleAcceptanceResponse() public async Task SubmitOrderAsync_MapsOperationsPayloadToSubmitResponse()
{ {
var client = CreateClient(""" var client = CreateClient("""
{ {
"contextId": "demo-context", "contextId": "demo-context",
"orderId": "ORD-42", "orderId": "ORD-42",
"accepted": true, "accepted": true,
"summary": "Order ORD-42 was accepted and is ready for kitchen dispatch.", "summary": "Order ORD-42 for table T-12 was accepted with 3 items.",
"status": "accepted", "status": "queued",
"submittedAtUtc": "2026-03-31T10:15:00Z" "submittedAtUtc": "2026-03-31T10:15:00Z"
} }
"""); """);
@ -76,21 +74,20 @@ public sealed class OperationsWaiterServiceClientTests
var response = await adapter.SubmitOrderAsync(new SubmitFloorOrderRequest("demo-context", "T-12", "ORD-42", 3), CancellationToken.None); var response = await adapter.SubmitOrderAsync(new SubmitFloorOrderRequest("demo-context", "T-12", "ORD-42", 3), CancellationToken.None);
Assert.True(response.Accepted); Assert.True(response.Accepted);
Assert.Equal("accepted", response.Status); Assert.Equal("queued", response.Status);
Assert.Equal("demo-context", response.ContextId); Assert.Equal("demo-context", response.ContextId);
Assert.Contains("kitchen dispatch", response.Summary);
} }
[Fact] [Fact]
public async Task UpdateOrderAsync_PrefixesSharedLifecycleUpdateSummary() public async Task UpdateOrderAsync_PrefixesAcceptedUpdateSummary()
{ {
var client = CreateClient(""" var client = CreateClient("""
{ {
"contextId": "demo-context", "contextId": "demo-context",
"orderId": "ORD-42", "orderId": "ORD-42",
"accepted": true, "accepted": true,
"summary": "Order ORD-42 was accepted and is ready for kitchen dispatch.", "summary": "Order ORD-42 for table T-12 was accepted with 4 items.",
"status": "accepted", "status": "queued",
"submittedAtUtc": "2026-03-31T10:15:00Z" "submittedAtUtc": "2026-03-31T10:15:00Z"
} }
"""); """);
@ -99,8 +96,7 @@ public sealed class OperationsWaiterServiceClientTests
var response = await adapter.UpdateOrderAsync(new UpdateFloorOrderRequest("demo-context", "T-12", "ORD-42", 4), CancellationToken.None); var response = await adapter.UpdateOrderAsync(new UpdateFloorOrderRequest("demo-context", "T-12", "ORD-42", 4), CancellationToken.None);
Assert.Contains("Updated order ORD-42.", response.Summary); Assert.Contains("Updated order ORD-42.", response.Summary);
Assert.Contains("kitchen dispatch", response.Summary); Assert.Equal("queued", response.Status);
Assert.Equal("accepted", response.Status);
} }
private static HttpClient CreateClient(string json) private static HttpClient CreateClient(string json)