using AccountsDAL.CustomCode.Warehouse; using GB5Shared.DTO.Framework.Login; using GB5Shared.Telemetry; using Microsoft.Extensions.Logging; namespace AccountsBLL.Warehouse { public class WarehouseChangeQueueBLL : IWarehouseChangeQueueBLL { private readonly IWarehouseChangeQueueDAL _queueDal; private readonly IWarehouseFactPostingBLL _postingBll; private readonly ILogger _logger; public WarehouseChangeQueueBLL( IWarehouseChangeQueueDAL queueDal, IWarehouseFactPostingBLL postingBll, ILogger logger) { _queueDal = queueDal; _postingBll = postingBll; _logger = logger; } public async Task EnqueueVoucherChangeAsync(int ouId, DateTime voucherDate, LoginDTO login, CancellationToken ct) { try { GB5Trace.Step("enqueue-voucher-change", new { ouId, voucherDate }); var deltaFactIds = await _queueDal.GetDeltaEnabledFactIdsAsync(login, ct).ConfigureAwait(false); foreach (var factId in deltaFactIds) await _queueDal.EnqueueIfNotPendingAsync(factId, ouId, voucherDate, login, ct).ConfigureAwait(false); } catch (Exception ex) { GB5Trace.MarkFailed("enqueue-voucher-change-failed", ex); _logger.LogError(ex, "EnqueueVoucherChange failed for OuId {OuId} VoucherDate {Date}", ouId, voucherDate); throw; } } public async Task ProcessDeltaForFactAsync(int factId, LoginDTO login, CancellationToken ct) { try { GB5Trace.Step("process-delta-for-fact", new { factId }); // Claim pending rows atomically — concurrent calls see disjoint sets of rows. var pending = await _queueDal.ClaimAndGetPendingAsync(factId, login, ct).ConfigureAwait(false); if (pending.Count == 0) return new WarehouseFactPostingResultDTO(); // Group by date range and OU list so PostFactAsync can be called once per // contiguous range. For simplicity in v1, post each distinct date separately; // the engine batches per-OU internally, so this is still efficient. var ouIdsByDate = pending .GroupBy(q => q.ActivityDate.Date) .ToDictionary(g => g.Key, g => g.Select(q => q.OuId).Distinct().ToList()); var combined = new WarehouseFactPostingResultDTO(); foreach (var (date, ouIds) in ouIdsByDate.OrderBy(kv => kv.Key)) { GB5Trace.Step("post-delta-date", new { factId, date, ouCount = ouIds.Count }); var result = await _postingBll .PostFactAsync(factId, date, date, ouIds, login, ct) .ConfigureAwait(false); combined.SucceededOuIds.AddRange(result.SucceededOuIds); foreach (var kv in result.FailedOuIds) combined.FailedOuIds[kv.Key] = kv.Value; } return combined; } catch (Exception ex) { GB5Trace.MarkFailed("process-delta-for-fact-failed", ex); _logger.LogError(ex, "ProcessDeltaForFact failed for FactId {FactId}", factId); throw; } } } }