207 lines
7.3 KiB
C#
207 lines
7.3 KiB
C#
using Distribution.Features.Invoice;
|
|
using Distribution.Features.PriceList;
|
|
using Distribution.Features.Stock;
|
|
using Distribution.Infra.UoW;
|
|
using Distribution.Models;
|
|
|
|
namespace Distribution.Features.ShopPO;
|
|
|
|
public class ShopPOService(
|
|
IShopPORepository repo,
|
|
IInvoiceRepository invoiceRepo,
|
|
PriceListService priceListService,
|
|
IUnitOfWork uow)
|
|
{
|
|
private static ShopPOResponse ToResponse(Models.ShopPO p) =>
|
|
new(p.Id, p.FK_StoreId, p.Store?.StoreName ?? string.Empty,
|
|
p.RaisedById, p.InvoiceId, p.TotalAmount, p.Status,
|
|
p.ConfirmedAt, p.ConfirmedBy, p.FulfilledAt,
|
|
p.Items.Where(i => i.IsActive).Select(i =>
|
|
new ShopPOItemResponse(i.Id, i.FK_ItemId, i.Item?.Name ?? string.Empty,
|
|
i.Quantity, i.QuantityFulfilled, i.UnitPrice, i.IsActive)).ToList(),
|
|
p.IsActive, p.CreatedAt);
|
|
|
|
public async Task<PagedResponse<ShopPOResponse>> GetAllAsync(Guid? agentId, Guid? routeId, Guid? regionId, Guid? shopId, bool? confirmed, ShopPOStatus? status, int page, int pageSize)
|
|
{
|
|
var total = await repo.GetCountAsync(agentId, routeId, regionId, shopId, confirmed, status);
|
|
var items = await repo.GetAllAsync(agentId, routeId, regionId, shopId, confirmed, status, page, pageSize);
|
|
return new PagedResponse<ShopPOResponse>(page, pageSize, total, items.Select(ToResponse).ToList());
|
|
}
|
|
|
|
public async Task<ShopPOResponse?> GetByIdAsync(Guid id)
|
|
{
|
|
var po = await repo.GetByIdAsync(id);
|
|
return po is null ? null : ToResponse(po);
|
|
}
|
|
|
|
public async Task<ShopPOResponse> CreateAsync(CreateShopPORequest request, Guid raisedById, CancellationToken ct = default)
|
|
{
|
|
if (request.Items.Count == 0)
|
|
throw new InvalidOperationException("At least one item is required");
|
|
|
|
var items = new List<ShopPOItem>();
|
|
decimal total = 0;
|
|
|
|
foreach (var line in request.Items)
|
|
{
|
|
var unitPrice = line.UnitPrice
|
|
?? (await priceListService.ResolveByStoreAsync(line.ItemId, request.StoreId, DateTime.Now))?.Price
|
|
?? throw new InvalidOperationException($"No resolvable price for item {line.ItemId}");
|
|
|
|
items.Add(new ShopPOItem
|
|
{
|
|
FK_ItemId = line.ItemId,
|
|
Quantity = line.Quantity,
|
|
QuantityFulfilled = 0,
|
|
UnitPrice = unitPrice
|
|
});
|
|
total += line.Quantity * unitPrice;
|
|
}
|
|
|
|
var po = new Models.ShopPO
|
|
{
|
|
FK_StoreId = request.StoreId,
|
|
RaisedById = raisedById,
|
|
TotalAmount = total,
|
|
Status = ShopPOStatus.Pending
|
|
};
|
|
|
|
await uow.BeginAsync(ct);
|
|
try
|
|
{
|
|
await repo.AddAsync(po);
|
|
foreach (var item in items) item.FK_ShopPOId = po.Id;
|
|
await repo.AddItemsAsync(items);
|
|
await uow.CommitAsync(ct);
|
|
}
|
|
catch { await uow.RollbackAsync(ct); throw; }
|
|
|
|
var saved = await repo.GetByIdAsync(po.Id);
|
|
return ToResponse(saved!);
|
|
}
|
|
|
|
public async Task<ShopPOResponse> UpdateItemsAsync(Guid id, UpdateShopPOItemsRequest request, CancellationToken ct = default)
|
|
{
|
|
var po = await repo.GetByIdAsync(id)
|
|
?? throw new KeyNotFoundException("Shop PO not found");
|
|
|
|
if (po.ConfirmedAt is not null)
|
|
throw new InvalidOperationException("Items can only be changed before the Shop PO is confirmed");
|
|
|
|
if (request.Items.Count == 0)
|
|
throw new InvalidOperationException("At least one item is required");
|
|
|
|
var newItems = new List<ShopPOItem>();
|
|
decimal total = 0;
|
|
|
|
foreach (var line in request.Items)
|
|
{
|
|
var unitPrice = line.UnitPrice
|
|
?? (await priceListService.ResolveByStoreAsync(line.ItemId, po.FK_StoreId, DateTime.Now))?.Price
|
|
?? throw new InvalidOperationException($"No resolvable price for item {line.ItemId}");
|
|
|
|
newItems.Add(new ShopPOItem
|
|
{
|
|
FK_ShopPOId = id,
|
|
FK_ItemId = line.ItemId,
|
|
Quantity = line.Quantity,
|
|
QuantityFulfilled = 0,
|
|
UnitPrice = unitPrice
|
|
});
|
|
total += line.Quantity * unitPrice;
|
|
}
|
|
|
|
po.TotalAmount = total;
|
|
|
|
await uow.BeginAsync(ct);
|
|
try
|
|
{
|
|
await repo.ReplaceItemsAsync(id, newItems);
|
|
repo.Update(po);
|
|
await uow.CommitAsync(ct);
|
|
}
|
|
catch { await uow.RollbackAsync(ct); throw; }
|
|
|
|
var saved = await repo.GetByIdAsync(id);
|
|
return ToResponse(saved!);
|
|
}
|
|
|
|
public async Task<ShopPOResponse> ConfirmAsync(Guid id, Guid confirmedBy, CancellationToken ct = default)
|
|
{
|
|
var po = await repo.GetByIdAsync(id)
|
|
?? throw new KeyNotFoundException("Shop PO not found");
|
|
|
|
if (po.ConfirmedAt is not null)
|
|
throw new InvalidOperationException("Shop PO is already confirmed");
|
|
|
|
if (po.Status == ShopPOStatus.Cancelled)
|
|
throw new InvalidOperationException("Cannot confirm a cancelled Shop PO");
|
|
|
|
po.ConfirmedAt = DateTime.Now;
|
|
po.ConfirmedBy = confirmedBy;
|
|
|
|
await uow.BeginAsync(ct);
|
|
try { repo.Update(po); await uow.CommitAsync(ct); }
|
|
catch { await uow.RollbackAsync(ct); throw; }
|
|
|
|
return ToResponse(po);
|
|
}
|
|
|
|
public async Task<ShopPOResponse> ConfirmAndCreateInvoiceAsync(Guid id, Guid userId, CancellationToken ct = default)
|
|
{
|
|
var po = await repo.GetByIdAsync(id)
|
|
?? throw new KeyNotFoundException("Shop PO not found");
|
|
|
|
if (po.ConfirmedAt is not null)
|
|
throw new InvalidOperationException("Shop PO is already confirmed");
|
|
|
|
if (po.Status == ShopPOStatus.Cancelled)
|
|
throw new InvalidOperationException("Cannot confirm a cancelled Shop PO");
|
|
|
|
po.ConfirmedAt = DateTime.Now;
|
|
po.ConfirmedBy = userId;
|
|
|
|
var invoice = new Models.Invoice
|
|
{
|
|
FK_ShopPOId = po.Id,
|
|
Status = InvoiceStatus.Draft,
|
|
PaymentStatus = PaymentStatus.Pending,
|
|
CreatedBy = userId,
|
|
TotalAmount = po.TotalAmount
|
|
};
|
|
|
|
await uow.BeginAsync(ct);
|
|
try
|
|
{
|
|
await invoiceRepo.AddAsync(invoice);
|
|
po.InvoiceId = invoice.Id;
|
|
repo.Update(po);
|
|
await uow.CommitAsync(ct);
|
|
}
|
|
catch { await uow.RollbackAsync(ct); throw; }
|
|
|
|
var saved = await repo.GetByIdAsync(id);
|
|
return ToResponse(saved!);
|
|
}
|
|
|
|
public async Task<ShopPOResponse> CancelAsync(Guid id, CancellationToken ct = default)
|
|
{
|
|
var po = await repo.GetByIdAsync(id)
|
|
?? throw new KeyNotFoundException("Shop PO not found");
|
|
|
|
if (po.Status == ShopPOStatus.Cancelled)
|
|
throw new InvalidOperationException("Shop PO is already cancelled");
|
|
|
|
if (po.Status is ShopPOStatus.PartiallyFulfilled or ShopPOStatus.Fulfilled)
|
|
throw new InvalidOperationException($"Cannot cancel a Shop PO in {po.Status} status");
|
|
|
|
po.Status = ShopPOStatus.Cancelled;
|
|
|
|
await uow.BeginAsync(ct);
|
|
try { repo.Update(po); await uow.CommitAsync(ct); }
|
|
catch { await uow.RollbackAsync(ct); throw; }
|
|
|
|
return ToResponse(po);
|
|
}
|
|
}
|