using Dapr.Client; using GB5Shared.DTO.Framework.Login; using GB5Shared.GenerateAutoNumber; using GB5Shared.Resource.GB5Solution; using Microsoft.Extensions.Logging; using MMDAL.CustomCode.StockAccountPost; using MMDAL.DTO.StockValuation; using Newtonsoft.Json; namespace MMBLL.StockAccountPost; public class StockAccountPostBLL : IStockAccountPostBLL { private readonly IStockAccountPostDAL _dal; private readonly AutoNumber _autoNumber; private readonly DaprClient _dapr; private readonly ILogger _logger; public StockAccountPostBLL( IStockAccountPostDAL dal, AutoNumber autoNumber, DaprClient dapr, ILogger logger) { _dal = dal; _autoNumber = autoNumber; _dapr = dapr; _logger = logger; } public async Task PostToGLAsync( int objectTypeId, int objectId, int ouid, bool isTransfer, LoginDTO login, CancellationToken ct) { try { // 1. Fetch GL posting lines based on document type var postLines = isTransfer ? await _dal.GetTransferPostingLinesAsync(objectTypeId, objectId, ouid, login, ct) .ConfigureAwait(false) : await _dal.GetPostingLinesAsync(objectTypeId, objectId, ouid, login, ct) .ConfigureAwait(false); // 2. Fetch non-stock charge items (fixes legacy GL gap) var nonStockLines = await _dal.GetNonStockChargeItemsAsync(objectTypeId, objectId, ouid, login, ct) .ConfigureAwait(false); var allLines = postLines.Concat(nonStockLines).ToList(); if (allLines.Count == 0) return MMResource.StockAccountPostingCompleted; // 3. For each posting line: record in TSTOCKACCOUNTPOSTDETAIL then publish GL event foreach (var line in allLines) { var autoNum = await _autoNumber.GetAutoNumber(1, "STOCKACCOUNTPOSTDETAIL", login) .ConfigureAwait(false); line.StockAccountPostDetailId = autoNum.StartNumber; line.CreatedOn = DateTime.UtcNow; await _dal.InsertPostDetailAsync(line, login, ct).ConfigureAwait(false); try { // Publish GL posting event to the Accounts/Finance module await _dapr.PublishEventAsync( "pubsub", "mm.stockvaluation.gl-posting-required", new { line.StockAccountPostDetailId, line.OUID, line.ObjectTypeId, line.ObjectId, line.GLAccount, line.PostedValue, line.PostingType, line.ItemId }, ct).ConfigureAwait(false); await _dal.MarkPostedAsync(line.StockAccountPostDetailId, login, ct) .ConfigureAwait(false); // Capital items: publish separate FAM event if (line.PostingType == 1) { await _dapr.PublishEventAsync( "pubsub", "fam.asset-acquisition-required", new { line.StockAccountPostDetailId, line.ObjectTypeId, line.ObjectId, line.ObjectDetailId, line.PostedValue, line.OUID }, ct).ConfigureAwait(false); } } catch (Exception postEx) { _logger.LogError(postEx, "GL posting failed for detail {DetailId}", line.StockAccountPostDetailId); await _dal.MarkFailedAsync( line.StockAccountPostDetailId, postEx.Message, login, ct) .ConfigureAwait(false); } } _logger.LogInformation( "Stock account posting completed: {Count} lines for doc {DocId}", allLines.Count, objectId); return MMResource.StockAccountPostingCompleted; } catch (Exception ex) { _logger.LogError(ex, "PostToGLAsync failed for doc {DocId}", objectId); return MMResource.StockAccountPostingFailed; } } public async Task GetPostingReconciliationAsync( int objectTypeId, int objectId, int ouid, LoginDTO login, CancellationToken ct) { var details = await _dal.GetPostDetailsAsync(objectTypeId, objectId, ouid, login, ct) .ConfigureAwait(false); return JsonConvert.SerializeObject(details); } }