diff --git a/docs/api/internal-kitchen-workflows.md b/docs/api/internal-kitchen-workflows.md index 5849421..d336e33 100644 --- a/docs/api/internal-kitchen-workflows.md +++ b/docs/api/internal-kitchen-workflows.md @@ -18,6 +18,7 @@ This repo now orchestrates kitchen progression over persisted kitchen tickets fr That means: - board and queue reads come from persisted kitchen tickets plus lifecycle-derived ticket materialization for newly accepted restaurant orders +- derived kitchen IDs now use the full order identity so runtime projections stay collision-safe across similar order numbers - claim and priority changes update persisted kitchen ticket state - ticket transitions update both kitchen tickets and the linked restaurant order lifecycle - accepted orders can become kitchen-visible without waiting for a stack reset or a second write path diff --git a/src/Kitchen.Service.Application/Ports/DefaultKitchenWorkflowPort.cs b/src/Kitchen.Service.Application/Ports/DefaultKitchenWorkflowPort.cs index 84a3cba..41f7a75 100644 --- a/src/Kitchen.Service.Application/Ports/DefaultKitchenWorkflowPort.cs +++ b/src/Kitchen.Service.Application/Ports/DefaultKitchenWorkflowPort.cs @@ -316,10 +316,9 @@ public sealed class DefaultKitchenWorkflowPort : IKitchenWorkflowPort private static PersistedKitchenWorkItemRecord BuildDerivedWorkItem(PersistedRestaurantLifecycleRecord order) { - var numericSuffix = new string(order.OrderId.Where(char.IsDigit).ToArray()); - var workItemId = string.IsNullOrWhiteSpace(numericSuffix) - ? $"WK-{order.OrderId}" - : $"WK-{numericSuffix}"; + // Use the full order identity when deriving kitchen IDs so distinct orders + // cannot collide just because they share a numeric suffix. + var workItemId = $"WK-{order.OrderId}"; return new PersistedKitchenWorkItemRecord( order.ContextId, diff --git a/tests/Kitchen.Service.Application.UnitTests/KitchenWorkflowUseCasesTests.cs b/tests/Kitchen.Service.Application.UnitTests/KitchenWorkflowUseCasesTests.cs index 9584598..df24c48 100644 --- a/tests/Kitchen.Service.Application.UnitTests/KitchenWorkflowUseCasesTests.cs +++ b/tests/Kitchen.Service.Application.UnitTests/KitchenWorkflowUseCasesTests.cs @@ -56,6 +56,53 @@ public class KitchenWorkflowUseCasesTests Assert.Equal("hot-line", derivedItem.Station); Assert.Equal("Queued", derivedItem.State); + Assert.Equal("WK-ORD-1099", derivedItem.WorkItemId); + Assert.Equal("KT-ORD-1099", derivedItem.TicketId); + } + + [Fact] + public async Task GetKitchenBoardUseCase_DerivesCollisionSafeIdsForSimilarNumericSuffixes() + { + await restaurantLifecycle.UpsertOrderAsync( + new Kitchen.Service.Application.State.PersistedRestaurantLifecycleRecord( + "demo-context", + "ORD-STAGE49-001", + "CHK-ORD-STAGE49-001", + "T-31", + "Accepted", + "Open", + 2, + false, + 25.00m, + "USD", + "customer-orders", + new[] { "ITEM-101", "ITEM-102" }, + DateTime.UtcNow), + CancellationToken.None); + + await restaurantLifecycle.UpsertOrderAsync( + new Kitchen.Service.Application.State.PersistedRestaurantLifecycleRecord( + "demo-context", + "ORD-STAGE49-WAITER-001", + "CHK-ORD-STAGE49-WAITER-001", + "T-32", + "Accepted", + "Open", + 3, + false, + 37.50m, + "USD", + "waiter-floor", + new[] { "ITEM-201", "ITEM-202", "ITEM-203" }, + DateTime.UtcNow.AddSeconds(1)), + CancellationToken.None); + + var useCase = new GetKitchenBoardUseCase(workflowPort); + var response = await useCase.HandleAsync(new GetKitchenBoardRequest("demo-context"), CancellationToken.None); + var queuedLane = Assert.Single(response.Lanes, lane => lane.Lane == "queued"); + + Assert.Contains(queuedLane.Items, item => item.WorkItemId == "WK-ORD-STAGE49-001" && item.TicketId == "KT-ORD-STAGE49-001"); + Assert.Contains(queuedLane.Items, item => item.WorkItemId == "WK-ORD-STAGE49-WAITER-001" && item.TicketId == "KT-ORD-STAGE49-WAITER-001"); } [Fact]