using System.Collections.Concurrent; using Operations.Service.Application.State; namespace Operations.Service.Application.Ports; public sealed class InMemoryRestaurantLifecycleStorePort : IRestaurantLifecycleStorePort { private readonly ConcurrentDictionary orders = new(); private readonly ConcurrentDictionary> events = new(); public InMemoryRestaurantLifecycleStorePort() { foreach (var record in BuildSeedOrders()) { orders[BuildKey(record.ContextId, record.OrderId)] = record; } foreach (var record in BuildSeedEvents()) { var queue = events.GetOrAdd(BuildKey(record.ContextId, record.OrderId), static _ => new ConcurrentQueue()); queue.Enqueue(record); } } public Task GetOrderAsync(string contextId, string orderId, CancellationToken cancellationToken) { orders.TryGetValue(BuildKey(contextId, orderId), out var record); return Task.FromResult(record); } public Task> ListOrdersAsync(string contextId, CancellationToken cancellationToken) { var records = orders.Values .Where(record => string.Equals(record.ContextId, contextId, StringComparison.Ordinal)) .OrderByDescending(record => record.UpdatedAtUtc) .ToArray(); return Task.FromResult>(records); } public Task> ListPayableOrdersAsync(string contextId, CancellationToken cancellationToken) { var records = orders.Values .Where(record => string.Equals(record.ContextId, contextId, StringComparison.Ordinal)) .Where(record => string.Equals(record.OrderState, "Served", StringComparison.Ordinal)) .Where(record => string.Equals(record.CheckState, "Open", StringComparison.Ordinal) || string.Equals(record.CheckState, "AwaitingPayment", StringComparison.Ordinal)) .Where(record => record.OutstandingBalance > 0m) .OrderByDescending(record => record.UpdatedAtUtc) .ToArray(); return Task.FromResult>(records); } public Task UpsertOrderAsync(PersistedRestaurantLifecycleRecord record, CancellationToken cancellationToken) { orders[BuildKey(record.ContextId, record.OrderId)] = record; return Task.CompletedTask; } public Task> ListEventsAsync(string contextId, string orderId, CancellationToken cancellationToken) { if (!events.TryGetValue(BuildKey(contextId, orderId), out var queue)) { return Task.FromResult>(Array.Empty()); } var records = queue.OrderByDescending(record => record.OccurredAtUtc).ToArray(); return Task.FromResult>(records); } public Task AppendEventAsync(PersistedRestaurantLifecycleEvent record, CancellationToken cancellationToken) { var queue = events.GetOrAdd(BuildKey(record.ContextId, record.OrderId), static _ => new ConcurrentQueue()); queue.Enqueue(record); return Task.CompletedTask; } private static IReadOnlyCollection BuildSeedOrders() { return new[] { new PersistedRestaurantLifecycleRecord( "demo-context", "ORD-1001", "CHK-1001", "T-08", "Preparing", "Open", 2, true, 24.00m, "USD", "customer-orders", new[] { "ITEM-101", "ITEM-202" }, DateTime.UtcNow.AddMinutes(-9)), new PersistedRestaurantLifecycleRecord( "demo-context", "ORD-1002", "CHK-1002", "T-12", "Served", "Open", 4, true, 37.50m, "USD", "waiter-floor", new[] { "ITEM-301", "ITEM-404", "ITEM-405" }, DateTime.UtcNow.AddMinutes(-4)) }; } private static IReadOnlyCollection BuildSeedEvents() { return new[] { new PersistedRestaurantLifecycleEvent("demo-context", "ORD-1001", "EVT-1001", "Preparing", "Order ORD-1001 is being prepared at the hot-line station.", DateTime.UtcNow.AddMinutes(-8)), new PersistedRestaurantLifecycleEvent("demo-context", "ORD-1002", "EVT-1002", "Served", "Order ORD-1002 was served and is ready for payment.", DateTime.UtcNow.AddMinutes(-3)) }; } private static string BuildKey(string contextId, string orderId) => $"{contextId}::{orderId}"; }