using Dapr.Client; using GB5Shared.DTO.Framework.Login; using Microsoft.Extensions.Logging; using MMDAL.CustomCode.StockLedgerCost; using MMDAL.DTO.StockValuation; using System.Data.Common; namespace MMBLL.StockLedgerCost; public class StockLedgerCostBLL : IStockLedgerCostBLL { private readonly IStockLedgerCostDAL _dal; private readonly DaprClient _dapr; private readonly ILogger _logger; private static readonly DateOnly _today = DateOnly.FromDateTime(DateTime.UtcNow.Date); public StockLedgerCostBLL( IStockLedgerCostDAL dal, DaprClient dapr, ILogger logger) { _dal = dal; _dapr = dapr; _logger = logger; } public async Task UpdateCostAsync( int objectTypeId, int objectId, bool isTransfer, int fromStoreId, int toStoreId, bool isStorewise, DateOnly transactionDate, LoginDTO login, DbTransaction tx, CancellationToken ct) { // Step 1: stamp PostedCost on TSTOCKLEDGER rows from current TSTOCKPOSITION if (isTransfer) { await _dal.ApplyAverageCostTransferAsync( objectTypeId, objectId, login.WorkOUId, fromStoreId, login, tx, ct).ConfigureAwait(false); } else { await _dal.ApplyAverageCostNonTransferAsync( objectTypeId, objectId, login.WorkOUId, login, tx, ct).ConfigureAwait(false); } // Step 2: update AverageValue on TSTOCKPOSITION from this document's movement if (isTransfer) { await _dal.UpdateAverageValueTransferFromStoreAsync( objectTypeId, objectId, fromStoreId, login, tx, ct).ConfigureAwait(false); } else { await _dal.UpdateAverageValueNonTransferAsync( objectTypeId, objectId, login, tx, ct).ConfigureAwait(false); } // Step 3: recompute AverageCost = AverageValue / Quantity if (isStorewise) { await _dal.ApplyCostAffectingStorewiseAsync( objectTypeId, objectId, isTransfer, toStoreId, login, tx, ct).ConfigureAwait(false); } else { await _dal.ApplyCostAffectingConsolidatedAsync( objectTypeId, objectId, login, tx, ct).ConfigureAwait(false); } // Step 4: if backdated entry, queue a recosting job via Dapr // (published outside the open TX intentionally — Dapr subscriber is idempotent) if (transactionDate < _today) { _logger.LogInformation( "Backdated entry detected: ObjectId {ObjectId} dated {Date} — queuing recosting", objectId, transactionDate); await _dapr.PublishEventAsync( "pubsub", "mm.stockvaluation.recosting-required", new StockValuationFilterDTO { RunType = 2, // Perpetual-Rerun OUIDs = [login.WorkOUId], FromDate = transactionDate, ToDate = _today, TriggeredByUserId = login.UserId }, ct).ConfigureAwait(false); } } public async Task UpdateCostForDocumentAsync( int objectTypeId, int objectId, bool isTransfer, int fromStoreId, int toStoreId, DateOnly transactionDate, LoginDTO login, CancellationToken ct) { try { byte storewiseRaw = await _dal.GetIsStorewiseAsync(login.WorkOUId, login, ct) .ConfigureAwait(false); bool isStorewise = storewiseRaw == 0; // 0=per-store, 1=consolidated await UpdateCostAsync( objectTypeId, objectId, isTransfer, fromStoreId, toStoreId, isStorewise, transactionDate, login, tx: null, ct).ConfigureAwait(false); } catch (Exception ex) { _logger.LogError(ex, "UpdateCostForDocument failed for ObjectTypeId {TypeId} ObjectId {ObjId}. " + "Cost will be corrected on next batch valuation run.", objectTypeId, objectId); } } public async Task RunPerpetualRerunAsync( int ouid, DateOnly fromDate, DateOnly toDate, int documentId, bool isStorewise, LoginDTO login, CancellationToken ct) { _logger.LogInformation( "Perpetual rerun: OUID {OUID} doc {DocId} from {From} to {To}", ouid, documentId, fromDate, toDate); await _dal.RunPerpetualRerunBatchAsync( ouid, fromDate, toDate, documentId, isStorewise, login, ct).ConfigureAwait(false); } }