187 lines
6.6 KiB
C#
187 lines
6.6 KiB
C#
using Distribution.Features.Item;
|
|
using Distribution.Features.PriceList;
|
|
using Distribution.Features.Store;
|
|
using Distribution.Infra.Authorization;
|
|
using Distribution.Infra.UoW;
|
|
using Distribution.Models;
|
|
using Microsoft.Extensions.Configuration;
|
|
|
|
namespace Distribution.Features.Order;
|
|
|
|
public class OrderService(
|
|
IOrderRepository repo,
|
|
IStoreRepository storeRepo,
|
|
IItemRepository itemRepo,
|
|
PriceListService priceListService,
|
|
IUnitOfWork uow,
|
|
ICurrentUserService currentUser,
|
|
IScopeAccessService scopeAccess,
|
|
IConfiguration configuration)
|
|
{
|
|
private const int GlobalScopeRoleLevel = 50;
|
|
|
|
private static OrderResponse ToResponse(Models.Order o, List<Models.OrderItem> items) =>
|
|
new(o.Id, o.OrderDate, o.FK_StoreId, o.TotalAmount,
|
|
items.Select(i => new OrderItemResponse(i.FK_ItemId, i.Quantity, 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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// applyPricingAndDiscounts (price-list half, 14_pricing_and_suppliers.md): resolves the
|
|
/// region-scoped active price list, then global, then Item.SalePrice, and validates any
|
|
/// caller-supplied override falls within [MinPrice ?? Price, Price]. Returns null if no price
|
|
/// can be resolved, or if the requested price is outside the allowed band.
|
|
/// </summary>
|
|
private async Task<decimal?> ResolveUnitPriceAsync(Guid itemId, decimal? requestedUnitPrice, Models.Store store, DateTime orderDate)
|
|
{
|
|
var item = await itemRepo.GetByIdAsync(itemId);
|
|
if (item is null) return null;
|
|
var regionId = store.Route?.FK_RegionId;
|
|
var resolution = await priceListService.ResolvePriceAsync(itemId, regionId, item.SalePrice, orderDate);
|
|
if (resolution is null) return null;
|
|
|
|
if (!requestedUnitPrice.HasValue) return resolution.Price;
|
|
|
|
var min = resolution.MinPrice ?? resolution.Price;
|
|
var max = resolution.Price;
|
|
if (requestedUnitPrice.Value < min || requestedUnitPrice.Value > max) return null;
|
|
|
|
return requestedUnitPrice.Value;
|
|
}
|
|
|
|
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)
|
|
{
|
|
if (!await HasStoreScopeAsync(request.StoreId)) return null;
|
|
|
|
var store = await storeRepo.GetByIdWithRegionAsync(request.StoreId);
|
|
if (store is null || !store.IsActive) return null;
|
|
if (store.KycStatus != KycStatus.Verified)
|
|
{
|
|
var threshold = configuration.GetValue<decimal>("StoreManagement:UnverifiedKycOrderThreshold");
|
|
if (request.TotalAmount > threshold) return null;
|
|
}
|
|
|
|
var order = new Models.Order
|
|
{
|
|
FK_StoreId = request.StoreId,
|
|
OrderDate = request.OrderDate,
|
|
TotalAmount = request.TotalAmount
|
|
};
|
|
var orderItems = new List<Models.OrderItem>();
|
|
foreach (var i in request.Items)
|
|
{
|
|
var unitPrice = await ResolveUnitPriceAsync(i.ItemId, i.UnitPrice, store, request.OrderDate);
|
|
if (unitPrice is null) return null;
|
|
|
|
orderItems.Add(new Models.OrderItem
|
|
{
|
|
FK_OrderId = order.Id,
|
|
FK_ItemId = i.ItemId,
|
|
Quantity = i.Quantity,
|
|
UnitPrice = unitPrice.Value
|
|
});
|
|
}
|
|
|
|
await uow.BeginAsync(ct);
|
|
try
|
|
{
|
|
await repo.AddAsync(order);
|
|
await repo.AddItemsAsync(orderItems);
|
|
await uow.CommitAsync(ct);
|
|
}
|
|
catch
|
|
{
|
|
await uow.RollbackAsync(ct);
|
|
throw;
|
|
}
|
|
return ToResponse(order, orderItems);
|
|
}
|
|
|
|
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;
|
|
order.OrderDate = request.OrderDate;
|
|
order.TotalAmount = request.TotalAmount;
|
|
await uow.BeginAsync(ct);
|
|
try
|
|
{
|
|
repo.Update(order);
|
|
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;
|
|
await uow.BeginAsync(ct);
|
|
try
|
|
{
|
|
repo.Remove(order);
|
|
await uow.CommitAsync(ct);
|
|
}
|
|
catch
|
|
{
|
|
await uow.RollbackAsync(ct);
|
|
throw;
|
|
}
|
|
return true;
|
|
}
|
|
}
|