using GB5Shared.DTO.Framework.Login; using GB5Shared.GenerateAutoNumber; using GB5Shared.Resource.GB5Solution; using Microsoft.Extensions.Logging; using MMDAL.CustomCode.FifoLotCost; using MMDAL.DTO.StockValuation; using System.ComponentModel.DataAnnotations; using System.Data.Common; namespace MMBLL.FifoLotCost; public class FifoLotCostBLL : IFifoLotCostBLL { private readonly IFifoLotCostDAL _dal; private readonly AutoNumber _autoNumber; private readonly ILogger _logger; public FifoLotCostBLL( IFifoLotCostDAL dal, AutoNumber autoNumber, ILogger logger) { _dal = dal; _autoNumber = autoNumber; _logger = logger; } public async Task AllocateLotsAsync( int itemId, int skuId, int storeId, int ouid, decimal issueQty, bool isLifo, long issueStockLedgerId, LoginDTO login, DbTransaction tx, CancellationToken ct) { var slices = await _dal.AllocateLotsAsync( itemId, skuId, storeId, ouid, issueQty, isLifo, login, ct) .ConfigureAwait(false); decimal totalAllocated = slices.Sum(s => s.AllocatedQuantity); if (totalAllocated < issueQty) { _logger.LogWarning( "Insufficient lot stock for Item {ItemId} SKU {SKUId} Store {StoreId}: needed {Need} allocated {Got}", itemId, skuId, storeId, issueQty, totalAllocated); throw new ValidationException(MMResource.InsufficientStockForFifo); } // Write TLOTDETAIL rows for each consumed slice foreach (var slice in slices) { var autoNum = await _autoNumber.GetAutoNumber(1, "LOTDETAIL", login) .ConfigureAwait(false); await _dal.InsertLotDetailIssueAsync( 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 CreateLotReceiptAsync( long lotId, int itemId, int skuId, int storeId, int ouid, decimal quantity, decimal unitCost, int objectTypeId, int objectId, long stockLedgerId, DateOnly lotDate, string? lotNumber, LoginDTO login, DbTransaction tx, CancellationToken ct) { await _dal.InsertLotReceiptAsync( lotId, itemId, skuId, storeId, ouid, quantity, unitCost, objectTypeId, objectId, stockLedgerId, lotDate, lotNumber, login, tx, ct).ConfigureAwait(false); var autoNum = await _autoNumber.GetAutoNumber(1, "LOTDETAIL", login).ConfigureAwait(false); await _dal.InsertLotDetailInboundAsync( autoNum.StartNumber, lotId, stockLedgerId, quantity, unitCost, login, tx, ct).ConfigureAwait(false); } public async Task ClearLotAllocationForRerunAsync( int objectTypeId, int objectId, LoginDTO login, DbTransaction tx, CancellationToken ct) { await _dal.DeleteLotDetailsForRerunAsync(objectTypeId, objectId, login, tx, ct) .ConfigureAwait(false); } }