using GB5Shared.DTO.Framework.Login; using GB5Shared.GenerateAutoNumber; using GB5Shared.Resource.GB5Solution; using Microsoft.Extensions.Logging; using MMDAL.CustomCode.FifoLayerCost; using MMDAL.DTO.StockValuation; using Newtonsoft.Json; using System.ComponentModel.DataAnnotations; using System.Data.Common; namespace MMBLL.FifoLayerCost; public class FifoLayerCostBLL : IFifoLayerCostBLL { private readonly IFifoLayerCostDAL _dal; private readonly AutoNumber _autoNumber; private readonly ILogger _logger; public FifoLayerCostBLL( IFifoLayerCostDAL dal, AutoNumber autoNumber, ILogger logger) { _dal = dal; _autoNumber = autoNumber; _logger = logger; } public async Task CreateLayerAsync( int itemId, int skuId, int storeId, int ouid, decimal quantity, decimal unitCost, int objectTypeId, int objectId, long stockLedgerId, DateOnly layerDate, LoginDTO login, DbTransaction tx, CancellationToken ct) { var autoNum = await _autoNumber.GetAutoNumber(1, "COSTLAYER", login).ConfigureAwait(false); await _dal.CreateCostLayerAsync( autoNum.StartNumber, itemId, skuId, storeId, ouid, quantity, unitCost, objectTypeId, objectId, stockLedgerId, layerDate, login, tx, ct).ConfigureAwait(false); } public async Task AllocateLayersAsync( int itemId, int skuId, int storeId, int ouid, decimal issueQty, bool isLifo, long issueStockLedgerId, LoginDTO login, DbTransaction tx, CancellationToken ct) { var slices = await _dal.AllocateLayersAsync( itemId, skuId, storeId, ouid, issueQty, isLifo, login, ct) .ConfigureAwait(false); decimal totalAllocated = slices.Sum(s => s.AllocatedQuantity); if (totalAllocated < issueQty) { _logger.LogWarning( "Insufficient layer stock for Item {ItemId} SKU {SKUId} Store {StoreId}: needed {Need} got {Got}", itemId, skuId, storeId, issueQty, totalAllocated); throw new ValidationException(MMResource.InsufficientStockForFifo); } // Consume each layer slice and write audit rows sequentially foreach (var slice in slices) { await _dal.ConsumeLayerAsync(slice.SourceId, slice.AllocatedQuantity, login, tx, ct) .ConfigureAwait(false); var autoNum = await _autoNumber.GetAutoNumber(1, "COSTLAYERDETAIL", login) .ConfigureAwait(false); await _dal.InsertLayerConsumptionAsync( autoNum.StartNumber, slice.SourceId, issueStockLedgerId, slice.AllocatedQuantity, slice.UnitCost, login, tx, ct).ConfigureAwait(false); } decimal blendedCost = totalAllocated > 0 ? slices.Sum(s => s.AllocatedQuantity * s.UnitCost) / totalAllocated : 0m; return new FifoBlendedCostDTO { ItemId = itemId, SKUId = skuId, StoreId = storeId, TotalQuantity = totalAllocated, BlendedUnitCost = Math.Round(blendedCost, 4), TotalValue = Math.Round(slices.Sum(s => s.AllocatedValue), 4), Allocations = slices }; } public async Task GetOpenCostLayersAsync( int itemId, int skuId, int storeId, int ouid, LoginDTO login, CancellationToken ct) { var layers = await _dal.GetOpenCostLayersAsync(itemId, skuId, storeId, ouid, login, ct) .ConfigureAwait(false); return JsonConvert.SerializeObject(layers); } }