using CPRNIMS.Domain.Contracts.Inventory; using CPRNIMS.Infrastructure.Database; using CPRNIMS.Infrastructure.Dto.Inventory.Reports; using CPRNIMS.Infrastructure.Dto.Inventory.Request; using Microsoft.Data.SqlClient; using Microsoft.EntityFrameworkCore; using System.Drawing.Printing; using static System.Runtime.InteropServices.JavaScript.JSType; namespace CPRNIMS.Domain.Services.Inventory { public class InventoryReports : IInventoryReports { private readonly NonInventoryDbContext _db; public InventoryReports(NonInventoryDbContext db) => _db = db; public async Task GetRISReportAsync(InventoryReportsRequest request, string userName, int? departmentId, CancellationToken ct) { var endDate = request.DateTo.Date.AddDays(1); var allowedAllDeptUsers = new[] { "LSKRISUR24", "LSCYNDIZ25", "LSJONTAN25", "LHRIOCAS24" }; bool seeAllDepartments = !string.IsNullOrWhiteSpace(userName) && allowedAllDeptUsers.Contains(userName, StringComparer.OrdinalIgnoreCase); var dateToInclusive = request.DateTo.AddDays(1); var query = _db.RIS .Include(r => r.Discipline) .Include(r => r.Inventory) .Include(r => r.MaterialReturns) .Skip((request.Page - 1) * request.PageSize) .Take(request.PageSize) .Where(r => r.CreatedDate >= request.DateFrom && r.CreatedDate < dateToInclusive); if (departmentId.HasValue && !seeAllDepartments) { query = query.Where(m => m.Inventory.User != null && m.Inventory.User.DepartmentId == departmentId.Value); } var rows = await query .OrderByDescending(r => r.CreatedDate) .Select(r => new RISReportRow { RISNo = r.RISNo, CreatedDate = r.CreatedDate, ItemName = r.PRDetail != null ? r.PRDetail.ItemName : "—", ItemNo = r.Inventory.ItemNo, DisciplineName = r.Discipline.DisciplineName, ProjectName = r.ProjectCodes.ProjectName, QtyIssued = r.QtyIssued, TotalReturned = r.MaterialReturns .Where(m => m.Status != 2) .Sum(m => (int?)m.QtyReturned) ?? 0, Status = r.Status, StatusLabel = r.Status == 0 ? "Draft" : r.Status == 1 ? "Approved" : "Cancelled" }) .ToListAsync(ct); foreach (var row in rows) row.NetIssued = row.QtyIssued - row.TotalReturned; var summary = new RISReportSummary { TotalRIS = rows.Count, TotalApproved = rows.Count(r => r.Status == 1), TotalPending = rows.Count(r => r.Status == 0), TotalCancelled = rows.Count(r => r.Status == 2), TotalQtyIssued = rows.Sum(r => r.QtyIssued), TotalQtyReturned = rows.Sum(r => r.TotalReturned), TotalNetIssued = rows.Sum(r => r.NetIssued), ApprovalRatePct = rows.Count > 0 ? Math.Round(rows.Count(r => r.Status == 1) * 100m / rows.Count, 1) : 0 }; var byDiscipline = rows .GroupBy(r => r.DisciplineName) .Select(g => new DisciplineCount { DisciplineName = g.Key, Count = g.Count() }) .OrderByDescending(d => d.Count) .ToList(); var topRecipients = rows .GroupBy(r => r.ProjectName) .Select(g => new TopRecipient { IssuedTo = g.Key, SlipCount = g.Count(), TotalQty = g.Sum(r => r.QtyIssued) }) .OrderByDescending(t => t.TotalQty) .Take(5) .ToList(); return new RISReportDto { ReportNo = $"RPT-RIS-{DateTime.Now:yyyyMM}-{Random.Shared.Next(1, 999):D3}", DateFrom = request.DateFrom, DateTo = request.DateTo, Summary = summary, Rows = rows, ByDiscipline = byDiscipline, TopRecipients = topRecipients }; } public async Task GetMRSReportAsync(InventoryReportsRequest request, string userName, int? departmentId, CancellationToken ct) { var endDate = request.DateTo.Date.AddDays(1); var allowedAllDeptUsers = new[] { "LSKRISUR24", "LSCYNDIZ25", "LSJONTAN25", "LHRIOCAS24" }; bool seeAllDepartments = !string.IsNullOrWhiteSpace(userName) && allowedAllDeptUsers.Contains(userName, StringComparer.OrdinalIgnoreCase); var query = _db.MRS .Include(m => m.RIS) .Include(m => m.Inventory) .ThenInclude(i => i.User) .Skip((request.Page - 1) * request.PageSize) .Take(request.PageSize) .Where(m => m.CreatedDate >= request.DateFrom && m.CreatedDate < endDate); if (departmentId.HasValue && !seeAllDepartments) { query = query.Where(m => m.Inventory.User != null && m.Inventory.User.DepartmentId == departmentId.Value); } var rows = await query .OrderByDescending(m => m.CreatedDate) .Select(m => new MRSReportRow { MRSNo = m.MRSNo, CreatedDate = m.CreatedDate, RISNo = m.RIS.RISNo, ItemName = m.RIS.PRDetail != null ? m.RIS.PRDetail.ItemName : "—", ReturnedBy = m.ReturnedBy, QtyReturned = m.QtyReturned, Condition = m.Condition ?? "Good", Status = m.Status, StatusLabel = m.Status == 0 ? "Draft" : m.Status == 1 ? "Approved" : "Cancelled" }) .ToListAsync(ct); // Total RIS qty issued in the same period (for the comparison panel) var totalRISQty = await _db.RIS .Where(r => r.CreatedDate >= request.DateFrom && r.CreatedDate < endDate && r.Status != 2) .SumAsync(r => (int?)r.QtyIssued, ct) ?? 0; var totalReturned = rows.Where(r => r.Status != 2).Sum(r => r.QtyReturned); var goodCount = rows.Count(r => r.Condition == "Good"); var summary = new MRSReportSummary { TotalMRS = rows.Count, TotalQtyReturned = totalReturned, TotalQtyIssuedRIS = totalRISQty, NetQtyConsumed = totalRISQty - totalReturned, ReturnRatePct = totalRISQty > 0 ? Math.Round(totalReturned * 100m / totalRISQty, 1) : 0, GoodConditionPct = rows.Count > 0 ? Math.Round(goodCount * 100m / rows.Count, 1) : 0 }; var byCondition = rows .Where(r => r.Status != 2) .GroupBy(r => r.Condition) .Select(g => new ConditionTotal { Condition = g.Key, TotalQty = g.Sum(r => r.QtyReturned) }) .OrderByDescending(c => c.TotalQty) .ToList(); return new MRSReportDto { ReportNo = $"RPT-MRS-{DateTime.Now:yyyyMM}-{Random.Shared.Next(1, 999):D3}", DateFrom = request.DateFrom, DateTo = request.DateTo, Summary = summary, Rows = rows, ByCondition = byCondition }; } public async Task GetInventoryReportAsync(InventoryReportsRequest request, string userName, int? departmentId, CancellationToken ct) { var allowedAllDeptUsers = new[] { "LSKRISUR24", "LSCYNDIZ25", "LSJONTAN25", "LHRIOCAS24" }; bool seeAllDepartments = !string.IsNullOrWhiteSpace(userName) && allowedAllDeptUsers.Contains(userName, StringComparer.OrdinalIgnoreCase); var endDate = request.DateTo.Date.AddDays(1); var dto = new InventoryReportDto { ReportNo = $"RPT-INV-{DateTime.Now:yyyyMM}-{Random.Shared.Next(1, 999):D3}", AsOf = endDate, Rows = new List(), ByCategory = new List(), Alerts = new List(), Summary = new InventoryReportSummary(), Departments = new List(), Page = request.Page, PageSize = request.PageSize }; var conn = _db.Database.GetDbConnection(); await using var cmd = conn.CreateCommand(); cmd.CommandText = "dbo.GetInventoryReport"; cmd.CommandType = System.Data.CommandType.StoredProcedure; cmd.Parameters.Add(new SqlParameter("@DateFrom", (object?)request.DateFrom ?? DBNull.Value)); cmd.Parameters.Add(new SqlParameter("@DateTo", (object?)request.DateTo ?? DBNull.Value)); cmd.Parameters.Add(new SqlParameter("@Department", (object?)request.Department ?? DBNull.Value)); cmd.Parameters.Add(new SqlParameter("@DepartmentId", (object?)departmentId ?? DBNull.Value)); cmd.Parameters.Add(new SqlParameter("@SeeAllDepartments", seeAllDepartments ? 1 : 0)); cmd.Parameters.Add(new SqlParameter("@Page", request.Page)); cmd.Parameters.Add(new SqlParameter("@PageSize", request.PageSize)); cmd.Parameters.Add(new SqlParameter("@Paginate", request.Paginate ? 1 : 0)); bool openedHere = conn.State != System.Data.ConnectionState.Open; if (openedHere) await conn.OpenAsync(ct); try { await using var reader = await cmd.ExecuteReaderAsync(ct); // 0: departments while (await reader.ReadAsync(ct)) dto.Departments.Add(reader.GetString(reader.GetOrdinal("Department"))); // 1: detail rows if (await reader.NextResultAsync(ct)) { while (await reader.ReadAsync(ct)) { dto.Rows.Add(new InventoryReportRow { ItemName = reader.GetString(reader.GetOrdinal("ItemName")), ItemNo = reader.GetInt64(reader.GetOrdinal("ItemNo")), ItemCategoryName = reader.GetString(reader.GetOrdinal("ItemCategoryName")), LotNo = reader.IsDBNull(reader.GetOrdinal("LotNo")) ? null : reader.GetString(reader.GetOrdinal("LotNo")), Department = reader.IsDBNull(reader.GetOrdinal("Department")) ? null : reader.GetString(reader.GetOrdinal("Department")), CurrencyCode = reader.IsDBNull(reader.GetOrdinal("CurrencyCode")) ? null : reader.GetString(reader.GetOrdinal("CurrencyCode")), QtyIn = reader.GetDecimal(reader.GetOrdinal("QtyIn")), QtyOut = reader.GetDecimal(reader.GetOrdinal("QtyOut")), QtyOnHand = reader.GetDecimal(reader.GetOrdinal("QtyOnHand")), UnitPrice = reader.GetDecimal(reader.GetOrdinal("UnitPrice")), StockPct = reader.GetInt32(reader.GetOrdinal("StockPct")) }); } } // 2: summary if (await reader.NextResultAsync(ct) && await reader.ReadAsync(ct)) { dto.Summary = new InventoryReportSummary { TotalSKUs = reader.GetInt32(reader.GetOrdinal("TotalSKUs")), TotalOnHand = reader.GetDecimal(reader.GetOrdinal("TotalOnHand")), TotalQtyIn = reader.GetDecimal(reader.GetOrdinal("TotalQtyIn")), TotalQtyOut = reader.GetDecimal(reader.GetOrdinal("TotalQtyOut")), TotalValue = reader.GetDecimal(reader.GetOrdinal("TotalValue")), LowStockCount = reader.GetInt32(reader.GetOrdinal("LowStockCount")), OutOfStockCount = reader.GetInt32(reader.GetOrdinal("OutOfStockCount")) }; } // 3: by category if (await reader.NextResultAsync(ct)) while (await reader.ReadAsync(ct)) dto.ByCategory.Add(new CategoryStockLevel { CategoryName = reader.GetString(reader.GetOrdinal("CategoryName")), AvgStockPct = reader.GetInt32(reader.GetOrdinal("AvgStockPct")) }); // 4: alerts if (await reader.NextResultAsync(ct)) while (await reader.ReadAsync(ct)) dto.Alerts.Add(new InventoryAlert { ItemName = reader.GetString(reader.GetOrdinal("ItemName")), QtyOnHand = reader.GetDecimal(reader.GetOrdinal("QtyOnHand")), Severity = reader.GetString(reader.GetOrdinal("Severity")) }); // 5: total row count if (await reader.NextResultAsync(ct) && await reader.ReadAsync(ct)) dto.TotalRows = reader.GetInt32(reader.GetOrdinal("TotalRows")); } finally { if (conn.State == System.Data.ConnectionState.Open) await conn.CloseAsync(); } return dto; } } }