6d8b6b3e59
Implemented DeliveryTask, Payment, and Sale entities with full CRUD, business logic, and API endpoints. Updated Order/OrderItem for sale linkage and fulfillment tracking. Registered new DbSets and DI services. Revised documentation for delivery, sale, and payment workflows.
155 lines
5.4 KiB
C#
155 lines
5.4 KiB
C#
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<Models.OrderItem> 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<bool> HasStoreScopeAsync(Guid storeId)
|
|
{
|
|
if (HasGlobalScope || currentUser.UserId is null) return HasGlobalScope;
|
|
return await scopeAccess.HasStoreScopeAsync(currentUser.UserId.Value, storeId);
|
|
}
|
|
|
|
public async Task<List<OrderResponse>> GetAllAsync()
|
|
{
|
|
var orders = (await repo.GetAllAsync()).Where(o => o.IsActive).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<OrderResponse>();
|
|
foreach (var order in orders)
|
|
{
|
|
var items = await repo.GetItemsByOrderIdAsync(order.Id);
|
|
result.Add(ToResponse(order, items));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
public async Task<OrderResponse?> 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<List<OrderResponse>?> GetByStoreIdAsync(Guid storeId)
|
|
{
|
|
if (!await HasStoreScopeAsync(storeId)) return null;
|
|
var orders = await repo.GetByStoreIdAsync(storeId);
|
|
var result = new List<OrderResponse>();
|
|
foreach (var order in orders)
|
|
{
|
|
var items = await repo.GetItemsByOrderIdAsync(order.Id);
|
|
result.Add(ToResponse(order, items));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
public async Task<OrderResponse?> 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<OrderResponse?> UpdateAsync(Guid id, UpdateOrderRequest request, CancellationToken ct = default)
|
|
{
|
|
var order = await repo.GetByIdAsync(id);
|
|
if (order is null || !order.IsActive) return null;
|
|
if (!await HasStoreScopeAsync(order.FK_StoreId)) 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<bool> DeleteAsync(Guid id, CancellationToken ct = default)
|
|
{
|
|
var order = await repo.GetByIdAsync(id);
|
|
if (order is null || !order.IsActive) return false;
|
|
if (!await HasStoreScopeAsync(order.FK_StoreId)) 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;
|
|
}
|
|
}
|