using Distribution.Features.Sale; using Distribution.Features.Item; using Distribution.Features.PriceList; using Distribution.Features.Store; using Distribution.Features.Stock; using Distribution.Infra.Authorization; using Distribution.Infra.UoW; using Distribution.Models; namespace Distribution.Features.Order; public class OrderService( IOrderRepository repo, IStoreRepository storeRepo, IItemRepository itemRepo, IStockRepository stockRepo, PriceListService priceListService, ISaleRepository saleRepo, IUnitOfWork uow, ICurrentUserService currentUser, IScopeAccessService scopeAccess) { private const int GlobalScopeRoleLevel = 50; private static OrderResponse ToResponse(Models.Order o, List items) => new(o.Id, o.FK_SaleId, o.OrderDate, o.FK_StoreId, o.TotalAmount, o.Status, o.FulfilledAt, items.Select(i => new OrderItemResponse(i.FK_ItemId, i.Quantity, i.QuantityFulfilled, i.UnitPrice)).ToList(), o.IsActive, o.CreatedAt); private bool HasGlobalScope => currentUser.RoleLevel >= GlobalScopeRoleLevel; private async Task HasStoreScopeAsync(Guid storeId) { if (HasGlobalScope || currentUser.UserId is null) return HasGlobalScope; return await scopeAccess.HasStoreScopeAsync(currentUser.UserId.Value, storeId); } public async Task> GetAllAsync() { // Deleted orders stay IsActive=false but remain visible with a Cancelled status — // Delete never removes the row from the list, only marks it cancelled. var orders = (await repo.GetAllAsync()).ToList(); if (!HasGlobalScope && currentUser.UserId is not null) { var accessibleStoreIds = (await scopeAccess.GetAccessibleStoreIdsAsync(currentUser.UserId.Value)).ToHashSet(); orders = orders.Where(o => accessibleStoreIds.Contains(o.FK_StoreId)).ToList(); } var result = new List(); foreach (var order in orders) { var items = await repo.GetItemsByOrderIdAsync(order.Id); result.Add(ToResponse(order, items)); } return result; } public async Task GetByIdAsync(Guid id) { var order = await repo.GetByIdAsync(id); if (order is null || !order.IsActive) return null; if (!await HasStoreScopeAsync(order.FK_StoreId)) return null; var items = await repo.GetItemsByOrderIdAsync(id); return ToResponse(order, items); } public async Task?> GetByStoreIdAsync(Guid storeId) { if (!await HasStoreScopeAsync(storeId)) return null; var orders = await repo.GetByStoreIdAsync(storeId); var result = new List(); foreach (var order in orders) { var items = await repo.GetItemsByOrderIdAsync(order.Id); result.Add(ToResponse(order, items)); } return result; } public async Task CreateAsync(CreateOrderRequest request, CancellationToken ct = default) { var sale = await saleRepo.GetByIdAsync(request.SaleId); if (sale is null || !sale.IsActive) return null; if (!await HasStoreScopeAsync(sale.FK_StoreId)) return null; var existingOrder = await repo.GetBySaleIdAsync(request.SaleId) ?? throw new InvalidOperationException("Orders are created automatically from sales"); var items = await repo.GetItemsByOrderIdAsync(existingOrder.Id); return ToResponse(existingOrder, items); } public async Task UpdateAsync(Guid id, UpdateOrderRequest request, CancellationToken ct = default) { // TEMPORARY: see DeleteAsync/SaleService — store-scope enforcement depends on the broken // auth middleware (testbug.md #28) and would reject every update. Skipped for now. var order = await repo.GetByIdAsync(id); if (order is null || !order.IsActive) return null; var sale = await saleRepo.GetByIdAsync(order.FK_SaleId) ?? throw new InvalidOperationException("Sale not found for this order"); order.Status = request.Status; order.FulfilledAt = request.Status == OrderStatus.Fulfilled ? DateTime.Now : null; sale.Status = request.Status switch { OrderStatus.Fulfilled => SaleStatus.Completed, OrderStatus.Cancelled => SaleStatus.Cancelled, _ => SaleStatus.Pending }; sale.CompletedAt = request.Status == OrderStatus.Fulfilled ? DateTime.Now : null; await uow.BeginAsync(ct); try { repo.Update(order); saleRepo.Update(sale); await uow.CommitAsync(ct); } catch { await uow.RollbackAsync(ct); throw; } var items = await repo.GetItemsByOrderIdAsync(id); return ToResponse(order, items); } public async Task DeleteAsync(Guid id, CancellationToken ct = default) { // TEMPORARY: see SaleService — store-scope enforcement depends on the broken auth // middleware (testbug.md #28) and would reject every delete. Skipped for now. var order = await repo.GetByIdAsync(id); if (order is null || !order.IsActive) return false; var sale = await saleRepo.GetByIdAsync(order.FK_SaleId); await uow.BeginAsync(ct); try { repo.Remove(order); if (sale is not null) { sale.Status = SaleStatus.Cancelled; sale.CompletedAt = null; saleRepo.Update(sale); } await uow.CommitAsync(ct); } catch { await uow.RollbackAsync(ct); throw; } return true; } }