using GB5Shared.DTO.Framework.Login; using Microsoft.Extensions.Logging; using MMDAL.CustomCode.InventoryReports; using MMDAL.DTO.InventoryReports; using System.Linq; namespace MMBLL.InventoryReports { public class InventoryReportsBLL : IInventoryReportsBLL { // Default BizTransactionClassId for "Purchase Order" -- see InventoryReportsQB's own // comment for provenance. Used when the caller doesn't override it. public const int DefaultSupplyBizTransactionClassId = -1499999975; // Default staleness threshold for the Non-Moving report -- matches legacy's own default // (StockPositionDAL.GetNonMovingReport's "int Age = 15"). public const int DefaultNonMovingAgeDays = 15; public const int DefaultStockMovementPeriodDays = 30; public const int DefaultTopMoversCount = 10; public const int DefaultStockHoldingTrendPeriodDays = 90; private readonly IInventoryReportsDAL _inventoryReportsDAL; private readonly ILogger _logger; public InventoryReportsBLL(IInventoryReportsDAL inventoryReportsDAL, ILogger logger) { _inventoryReportsDAL = inventoryReportsDAL; _logger = logger; } public async Task> GetReorderBreachReport( int ouId, int? storeId, int? itemCategoryId, int supplyBizTransactionClassId, LoginDTO loginDTO, CancellationToken ct = default) { var resolvedClassId = supplyBizTransactionClassId != 0 ? supplyBizTransactionClassId : DefaultSupplyBizTransactionClassId; try { return await _inventoryReportsDAL.GetReorderBreachReport( ouId, storeId, itemCategoryId, resolvedClassId, loginDTO, ct).ConfigureAwait(false); } catch (Exception ex) { _logger.LogError(ex, "GetReorderBreachReport failed for OUId {OuId}", ouId); throw; } } public async Task> GetStockAgeingReport( int ouId, int? itemCategoryId, DateTime? asOnDate, LoginDTO loginDTO, CancellationToken ct = default) { try { return await _inventoryReportsDAL.GetStockAgeingReport( ouId, itemCategoryId, asOnDate ?? DateTime.UtcNow.Date, loginDTO, ct).ConfigureAwait(false); } catch (Exception ex) { _logger.LogError(ex, "GetStockAgeingReport failed for OUId {OuId}", ouId); throw; } } public async Task> GetNonMovingReport( int ouId, int? itemCategoryId, int? ageDays, DateTime? asOnDate, LoginDTO loginDTO, CancellationToken ct = default) { try { return await _inventoryReportsDAL.GetNonMovingReport( ouId, itemCategoryId, ageDays ?? DefaultNonMovingAgeDays, asOnDate ?? DateTime.UtcNow.Date, loginDTO, ct).ConfigureAwait(false); } catch (Exception ex) { _logger.LogError(ex, "GetNonMovingReport failed for OUId {OuId}", ouId); throw; } } public async Task GetStockMovementDashboard( int ouId, DateTime? periodFromDate, DateTime? periodToDate, int? topN, LoginDTO loginDTO, CancellationToken ct = default) { var resolvedToDate = periodToDate ?? DateTime.UtcNow.Date; var resolvedFromDate = periodFromDate ?? resolvedToDate.AddDays(-DefaultStockMovementPeriodDays); try { return await _inventoryReportsDAL.GetStockMovementDashboard( ouId, resolvedFromDate, resolvedToDate, topN ?? DefaultTopMoversCount, loginDTO, ct).ConfigureAwait(false); } catch (Exception ex) { _logger.LogError(ex, "GetStockMovementDashboard failed for OUId {OuId}", ouId); throw; } } public async Task GetStockOverviewDashboard( int ouId, LoginDTO loginDTO, CancellationToken ct = default) { try { return await _inventoryReportsDAL.GetStockOverviewDashboard(ouId, loginDTO, ct) .ConfigureAwait(false); } catch (Exception ex) { _logger.LogError(ex, "GetStockOverviewDashboard failed for OUId {OuId}", ouId); throw; } } public async Task> GetStockHoldingTrend( int ouId, int? itemId, DateTime? periodFromDate, DateTime? periodToDate, LoginDTO loginDTO, CancellationToken ct = default) { var resolvedToDate = periodToDate ?? DateTime.UtcNow.Date; var resolvedFromDate = periodFromDate ?? resolvedToDate.AddDays(-DefaultStockHoldingTrendPeriodDays); try { return await _inventoryReportsDAL.GetStockHoldingTrend( ouId, itemId, resolvedFromDate, resolvedToDate, loginDTO, ct).ConfigureAwait(false); } catch (Exception ex) { _logger.LogError(ex, "GetStockHoldingTrend failed for OUId {OuId}", ouId); throw; } } public async Task> GetSubcontractorMaterialTracker( int ouId, LoginDTO loginDTO, CancellationToken ct = default) { try { return await _inventoryReportsDAL.GetSubcontractorMaterialTracker(ouId, loginDTO, ct) .ConfigureAwait(false); } catch (Exception ex) { _logger.LogError(ex, "GetSubcontractorMaterialTracker failed for OUId {OuId}", ouId); throw; } } // Composes numbers from three different sources rather than issuing one query -- see // InventoryKPISummaryDTO.cs for the full reasoning. Reorder Breach and Obsolete Stock // reuse the already-built Stock Overview / Ageing tiles' own BLL methods rather than // recomputing the same predicate a third time. public async Task GetInventoryKPISummary( int ouId, int tenantId, DateTime? periodFromDate, DateTime? periodToDate, LoginDTO loginDTO, CancellationToken ct = default) { var resolvedToDate = periodToDate ?? DateTime.UtcNow.Date; var resolvedFromDate = periodFromDate ?? resolvedToDate.AddDays(-DefaultStockMovementPeriodDays); var fromDateId = int.Parse(resolvedFromDate.ToString("yyyyMMdd")); var toDateId = int.Parse(resolvedToDate.ToString("yyyyMMdd")); try { var totalStockValueTask = _inventoryReportsDAL.GetTotalStockValue(ouId, loginDTO, ct); var movementValueTask = _inventoryReportsDAL.GetMovementValueFromFact( ouId, tenantId, fromDateId, toDateId, loginDTO, ct); var overviewTask = GetStockOverviewDashboard(ouId, loginDTO, ct); var ageingTask = GetStockAgeingReport(ouId, null, resolvedToDate, loginDTO, ct); await Task.WhenAll(totalStockValueTask, movementValueTask, overviewTask, ageingTask) .ConfigureAwait(false); return new InventoryKPISummaryDTO { TotalStockValue = totalStockValueTask.Result, TotalMovementInValue = movementValueTask.Result.TotalMovementInValue, TotalMovementOutValue = movementValueTask.Result.TotalMovementOutValue, ReorderBreachCount = overviewTask.Result.Alerts.ReorderBreachCount, ObsoleteStockValue = ageingTask.Result.Sum(r => r.Age4Value) }; } catch (Exception ex) { _logger.LogError(ex, "GetInventoryKPISummary failed for OUId {OuId}", ouId); throw; } } } }