From 3c0be5eae25383a29b33972bf14ead843db7677d Mon Sep 17 00:00:00 2001 From: rowell_m_soriano Date: Thu, 25 Jun 2026 08:57:30 +0800 Subject: [PATCH] CRUD for RIS, MRS, Inventory reports are working properly --- .../Contracts/Inventory/IInventoryReports.cs | 7 +- .../Services/Inventory/InventoryReports.cs | 106 +++-- .../Inventory/IInventoryReports.cs | 8 +- .../UIServices/Inventory/InventoryReports.cs | 62 ++- .../Dto/Inventory/Reports/ReportDtos.cs | 8 +- .../Request/InventoryReportsRequest.cs | 18 + .../Inventory/InventoryReportsController.cs | 16 +- .../Inventory/InventoryReportsController.cs | 87 ++-- .../Inventory/MRSMgmtController.cs | 7 +- .../Inventory/RISMgmtController.cs | 29 +- .../Reports/InventorySummaryReport.cshtml | 394 ++++++++++++++---- .../InventorySummaryReportFinance.cshtml | 355 ++++++++++++---- .../TabPage/Reports/MRSReport.cshtml | 115 +++-- .../TabPage/Reports/RISReport.cshtml | 136 ++++-- .../Inventory/_InventoryReportHelper.cshtml | 21 +- .../wwwroot/JsFunctions/PO/CustomPO.js | 5 - CPRNIMS.WebApps/wwwroot/Reports/RIS_v2.frx | 217 ++++++++++ 17 files changed, 1206 insertions(+), 385 deletions(-) create mode 100644 CPRNIMS.Infrastructure/Dto/Inventory/Request/InventoryReportsRequest.cs create mode 100644 CPRNIMS.WebApps/wwwroot/Reports/RIS_v2.frx diff --git a/CPRNIMS.Domain/Contracts/Inventory/IInventoryReports.cs b/CPRNIMS.Domain/Contracts/Inventory/IInventoryReports.cs index 6e70503..2786abc 100644 --- a/CPRNIMS.Domain/Contracts/Inventory/IInventoryReports.cs +++ b/CPRNIMS.Domain/Contracts/Inventory/IInventoryReports.cs @@ -1,4 +1,5 @@ using CPRNIMS.Infrastructure.Dto.Inventory.Reports; +using CPRNIMS.Infrastructure.Dto.Inventory.Request; using System; using System.Collections.Generic; using System.Linq; @@ -9,8 +10,8 @@ namespace CPRNIMS.Domain.Contracts.Inventory { public interface IInventoryReports { - Task GetRISReportAsync(DateTime dateFrom, DateTime dateTo, CancellationToken ct, int? departmentId = null, string? userName = ""); - Task GetMRSReportAsync(DateTime dateFrom, DateTime dateTo, CancellationToken ct, int? departmentId = null, string? userName = ""); - Task GetInventoryReportAsync(DateTime dateFrom, DateTime dateTo, CancellationToken ct, int? departmentId = null, string? userName = ""); + Task GetRISReportAsync(InventoryReportsRequest request, string userName, int? departmentId, CancellationToken ct); + Task GetMRSReportAsync(InventoryReportsRequest request, string userName, int? departmentId, CancellationToken ct); + Task GetInventoryReportAsync(InventoryReportsRequest request,string userName, int? departmentId, CancellationToken ct); } } diff --git a/CPRNIMS.Domain/Services/Inventory/InventoryReports.cs b/CPRNIMS.Domain/Services/Inventory/InventoryReports.cs index a00245d..178fbd9 100644 --- a/CPRNIMS.Domain/Services/Inventory/InventoryReports.cs +++ b/CPRNIMS.Domain/Services/Inventory/InventoryReports.cs @@ -1,8 +1,11 @@ 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 { @@ -11,22 +14,23 @@ namespace CPRNIMS.Domain.Services.Inventory private readonly NonInventoryDbContext _db; public InventoryReports(NonInventoryDbContext db) => _db = db; - public async Task GetRISReportAsync(DateTime dateFrom, DateTime dateTo, CancellationToken ct, - int? departmentId = null, string? userName = "") + public async Task GetRISReportAsync(InventoryReportsRequest request, string userName, int? departmentId, CancellationToken ct) { - var endDate = dateTo.Date.AddDays(1); + 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 = dateTo.AddDays(1); + var dateToInclusive = request.DateTo.AddDays(1); var query = _db.RIS .Include(r => r.Discipline) .Include(r => r.Inventory) .Include(r => r.MaterialReturns) - .Where(r => r.CreatedDate >= dateFrom && r.CreatedDate < dateToInclusive); + .Skip((request.Page - 1) * request.PageSize) + .Take(request.PageSize) + .Where(r => r.CreatedDate >= request.DateFrom && r.CreatedDate < dateToInclusive); if (departmentId.HasValue && !seeAllDepartments) { @@ -94,8 +98,8 @@ namespace CPRNIMS.Domain.Services.Inventory return new RISReportDto { ReportNo = $"RPT-RIS-{DateTime.Now:yyyyMM}-{Random.Shared.Next(1, 999):D3}", - DateFrom = dateFrom, - DateTo = dateTo, + DateFrom = request.DateFrom, + DateTo = request.DateTo, Summary = summary, Rows = rows, ByDiscipline = byDiscipline, @@ -103,10 +107,9 @@ namespace CPRNIMS.Domain.Services.Inventory }; } - public async Task GetMRSReportAsync(DateTime dateFrom, DateTime dateTo, CancellationToken ct, - int? departmentId = null, string? userName = "") + public async Task GetMRSReportAsync(InventoryReportsRequest request, string userName, int? departmentId, CancellationToken ct) { - var endDate = dateTo.Date.AddDays(1); + var endDate = request.DateTo.Date.AddDays(1); var allowedAllDeptUsers = new[] { "LSKRISUR24", "LSCYNDIZ25", "LSJONTAN25", "LHRIOCAS24" }; bool seeAllDepartments = !string.IsNullOrWhiteSpace(userName) @@ -115,8 +118,10 @@ namespace CPRNIMS.Domain.Services.Inventory var query = _db.MRS .Include(m => m.RIS) .Include(m => m.Inventory) - .ThenInclude(i => i.User) - .Where(m => m.CreatedDate >= dateFrom && + .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) @@ -146,7 +151,7 @@ namespace CPRNIMS.Domain.Services.Inventory // Total RIS qty issued in the same period (for the comparison panel) var totalRISQty = await _db.RIS - .Where(r => r.CreatedDate >= dateFrom && r.CreatedDate < endDate + .Where(r => r.CreatedDate >= request.DateFrom && r.CreatedDate < endDate && r.Status != 2) .SumAsync(r => (int?)r.QtyIssued, ct) ?? 0; @@ -177,64 +182,79 @@ namespace CPRNIMS.Domain.Services.Inventory return new MRSReportDto { ReportNo = $"RPT-MRS-{DateTime.Now:yyyyMM}-{Random.Shared.Next(1, 999):D3}", - DateFrom = dateFrom, - DateTo = dateTo, + DateFrom = request.DateFrom, + DateTo = request.DateTo, Summary = summary, Rows = rows, ByCondition = byCondition }; } - public async Task GetInventoryReportAsync(DateTime dateFrom, DateTime dateTo, CancellationToken ct, - int? departmentId = null, string? userName = "") + 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 = dateTo.Date, + AsOf = endDate, Rows = new List(), ByCategory = new List(), Alerts = new List(), - Summary = new InventoryReportSummary() + 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", dateFrom)); - cmd.Parameters.Add(new SqlParameter("@DateTo", dateTo)); + 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)); - if (conn.State != System.Data.ConnectionState.Open) - await conn.OpenAsync(ct); + bool openedHere = conn.State != System.Data.ConnectionState.Open; + if (openedHere) await conn.OpenAsync(ct); try { await using var reader = await cmd.ExecuteReaderAsync(ct); - // 1: detail rows + // 0: departments while (await reader.ReadAsync(ct)) + dto.Departments.Add(reader.GetString(reader.GetOrdinal("Department"))); + + // 1: detail rows + if (await reader.NextResultAsync(ct)) { - dto.Rows.Add(new InventoryReportRow + while (await reader.ReadAsync(ct)) { - 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")), - 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")) - }); + 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 @@ -254,30 +274,26 @@ namespace CPRNIMS.Domain.Services.Inventory // 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 { diff --git a/CPRNIMS.Domain/UIContracts/Inventory/IInventoryReports.cs b/CPRNIMS.Domain/UIContracts/Inventory/IInventoryReports.cs index 516e853..10eeb30 100644 --- a/CPRNIMS.Domain/UIContracts/Inventory/IInventoryReports.cs +++ b/CPRNIMS.Domain/UIContracts/Inventory/IInventoryReports.cs @@ -1,5 +1,5 @@ using CPRNIMS.Infrastructure.Dto.Inventory.Reports; -using CPRNIMS.Infrastructure.Dto.Inventory.Reports.Response; +using CPRNIMS.Infrastructure.Dto.Inventory.Request; using System; using System.Collections.Generic; using System.Linq; @@ -11,8 +11,8 @@ namespace CPRNIMS.Domain.UIContracts.Inventory public interface IInventoryReports { - Task GetRISReportAsync(DateTime dateFrom, DateTime dateTo, CancellationToken ct); - Task GetMRSReportAsync(DateTime dateFrom, DateTime dateTo, CancellationToken ct); - Task GetInventoryReportAsync(DateTime dateFrom, DateTime dateTo, CancellationToken ct); + Task GetRISReportAsync(InventoryReportsRequest request, CancellationToken ct); + Task GetMRSReportAsync(InventoryReportsRequest request, CancellationToken ct); + Task GetInventoryReportAsync(InventoryReportsRequest request, CancellationToken ct); } } diff --git a/CPRNIMS.Domain/UIServices/Inventory/InventoryReports.cs b/CPRNIMS.Domain/UIServices/Inventory/InventoryReports.cs index 6c82c89..be7b28f 100644 --- a/CPRNIMS.Domain/UIServices/Inventory/InventoryReports.cs +++ b/CPRNIMS.Domain/UIServices/Inventory/InventoryReports.cs @@ -1,8 +1,11 @@ -using CPRNIMS.Domain.UIContracts.Common; +using Azure.Core; +using CPRNIMS.Domain.UIContracts.Common; using CPRNIMS.Domain.UIContracts.Inventory; using CPRNIMS.Infrastructure.Dto.Inventory.Reports; using CPRNIMS.Infrastructure.Dto.Inventory.Reports.Response; +using CPRNIMS.Infrastructure.Dto.Inventory.Request; using CPRNIMS.Infrastructure.Helper; +using Microsoft.AspNetCore.WebUtilities; using Microsoft.Extensions.Configuration; using System.Text; using System.Text.Json; @@ -24,7 +27,7 @@ namespace CPRNIMS.Domain.UIServices.Inventory { PropertyNameCaseInsensitive = true }; - public async Task GetInventoryReportAsync(DateTime dateFrom, DateTime dateTo, CancellationToken ct) + public async Task GetInventoryReportAsync(InventoryReportsRequest request, CancellationToken ct) { var token = await _tokenHelper.GetValidTokenAsync(); if (string.IsNullOrEmpty(token)) @@ -33,11 +36,20 @@ namespace CPRNIMS.Domain.UIServices.Inventory var baseEndpoint = _configuration["LLI:NonInvent:InventoryMgmt:GetInventoryReport"] ?? throw new InvalidOperationException("GetInventoryReport endpoint is not configured."); - var qs = new StringBuilder(baseEndpoint).Append('?'); - qs.Append($"dateFrom={dateFrom:yyyy-MM-dd}&dateTo={dateTo:yyyy-MM-dd}"); + var qs = new Dictionary + { + ["dateFrom"] = request.DateFrom.ToString("yyyy-MM-dd"), + ["dateTo"] = request.DateTo.ToString("yyyy-MM-dd"), + ["department"] = request.Department, + ["page"] = request.Page.ToString(), + ["pageSize"] = request.PageSize.ToString(), + ["paginate"] = request.Paginate.ToString().ToLowerInvariant() + }; - using var http = _apiConfigurationService.CreateHttpClientWithDefaultHeaders(token); - var response = await http.GetAsync(qs.ToString(), ct); + var url = QueryHelpers.AddQueryString(baseEndpoint, qs); + + using var httpClient = _apiConfigurationService.CreateHttpClientWithDefaultHeaders(token); + var response = await httpClient.GetAsync(url, ct); var json = await response.Content.ReadAsStringAsync(ct); if (!response.IsSuccessStatusCode) @@ -47,7 +59,7 @@ namespace CPRNIMS.Domain.UIServices.Inventory return result ?? new InventoryReportDto(); } - public async Task GetMRSReportAsync(DateTime dateFrom, DateTime dateTo, CancellationToken ct) + public async Task GetMRSReportAsync(InventoryReportsRequest request, CancellationToken ct) { var token = await _tokenHelper.GetValidTokenAsync(); if (string.IsNullOrEmpty(token)) @@ -56,11 +68,20 @@ namespace CPRNIMS.Domain.UIServices.Inventory var baseEndpoint = _configuration["LLI:NonInvent:InventoryMgmt:GetMRSReport"] ?? throw new InvalidOperationException("GetMRS endpoint is not configured."); - var qs = new StringBuilder(baseEndpoint).Append('?'); - qs.Append($"dateFrom={dateFrom:yyyy-MM-dd}&dateTo={dateTo:yyyy-MM-dd}"); + var qs = new Dictionary + { + ["dateFrom"] = request.DateFrom.ToString("yyyy-MM-dd"), + ["dateTo"] = request.DateTo.ToString("yyyy-MM-dd"), + ["department"] = request.Department, + ["page"] = request.Page.ToString(), + ["pageSize"] = request.PageSize.ToString(), + ["paginate"] = request.Paginate.ToString().ToLowerInvariant() + }; - using var http = _apiConfigurationService.CreateHttpClientWithDefaultHeaders(token); - var response = await http.GetAsync(qs.ToString(), ct); + var url = QueryHelpers.AddQueryString(baseEndpoint, qs); + + using var httpClient = _apiConfigurationService.CreateHttpClientWithDefaultHeaders(token); + var response = await httpClient.GetAsync(url, ct); var json = await response.Content.ReadAsStringAsync(ct); if (!response.IsSuccessStatusCode) @@ -70,7 +91,7 @@ namespace CPRNIMS.Domain.UIServices.Inventory return result ?? new MRSReportDto(); } - public async Task GetRISReportAsync(DateTime dateFrom, DateTime dateTo, CancellationToken ct) + public async Task GetRISReportAsync(InventoryReportsRequest request, CancellationToken ct) { var token = await _tokenHelper.GetValidTokenAsync(); if (string.IsNullOrEmpty(token)) @@ -79,11 +100,20 @@ namespace CPRNIMS.Domain.UIServices.Inventory var baseEndpoint = _configuration["LLI:NonInvent:InventoryMgmt:GetRISReport"] ?? throw new InvalidOperationException("GetMRS endpoint is not configured."); - var qs = new StringBuilder(baseEndpoint).Append('?'); - qs.Append($"dateFrom={dateFrom:yyyy-MM-dd}&dateTo={dateTo:yyyy-MM-dd}"); + var qs = new Dictionary + { + ["dateFrom"] = request.DateFrom.ToString("yyyy-MM-dd"), + ["dateTo"] = request.DateTo.ToString("yyyy-MM-dd"), + ["department"] = request.Department, + ["page"] = request.Page.ToString(), + ["pageSize"] = request.PageSize.ToString(), + ["paginate"] = request.Paginate.ToString().ToLowerInvariant() + }; - using var http = _apiConfigurationService.CreateHttpClientWithDefaultHeaders(token); - var response = await http.GetAsync(qs.ToString(), ct); + var url = QueryHelpers.AddQueryString(baseEndpoint, qs); + + using var httpClient = _apiConfigurationService.CreateHttpClientWithDefaultHeaders(token); + var response = await httpClient.GetAsync(url, ct); var json = await response.Content.ReadAsStringAsync(ct); if (!response.IsSuccessStatusCode) diff --git a/CPRNIMS.Infrastructure/Dto/Inventory/Reports/ReportDtos.cs b/CPRNIMS.Infrastructure/Dto/Inventory/Reports/ReportDtos.cs index d756fac..9f7a60e 100644 --- a/CPRNIMS.Infrastructure/Dto/Inventory/Reports/ReportDtos.cs +++ b/CPRNIMS.Infrastructure/Dto/Inventory/Reports/ReportDtos.cs @@ -108,7 +108,11 @@ namespace CPRNIMS.Infrastructure.Dto.Inventory.Reports public string PreparedBy { get; set; } = "Finance Department"; public string ReportNo { get; set; } = string.Empty; public DateTime AsOf { get; set; } - + public List Departments { get; set; } = new(); + public int Page { get; set; } + public int PageSize { get; set; } + public int TotalRows { get; set; } + public int TotalPages => PageSize > 0 ? (int)Math.Ceiling(TotalRows / (double)PageSize) : 0; public InventoryReportSummary Summary { get; set; } = new(); public List Rows { get; set; } = []; public List ByCategory { get; set; } = []; @@ -137,6 +141,8 @@ namespace CPRNIMS.Infrastructure.Dto.Inventory.Reports public decimal QtyOnHand { get; set; } public decimal UnitPrice { get; set; } public int StockPct { get; set; } + public string? Department { get; set; } + public string? CurrencyCode { get; set; } } public class CategoryStockLevel diff --git a/CPRNIMS.Infrastructure/Dto/Inventory/Request/InventoryReportsRequest.cs b/CPRNIMS.Infrastructure/Dto/Inventory/Request/InventoryReportsRequest.cs new file mode 100644 index 0000000..61ac348 --- /dev/null +++ b/CPRNIMS.Infrastructure/Dto/Inventory/Request/InventoryReportsRequest.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CPRNIMS.Infrastructure.Dto.Inventory.Request +{ + public class InventoryReportsRequest + { + public DateTime DateFrom { get; set; } + public DateTime DateTo { get; set; } + public string? Department { get; set; } + public int Page { get; set; } + public int PageSize { get; set; } + public bool Paginate { get; set; } + } +} diff --git a/CPRNIMS.WebApi/Controllers/Inventory/InventoryReportsController.cs b/CPRNIMS.WebApi/Controllers/Inventory/InventoryReportsController.cs index 241c5a9..ee97a9d 100644 --- a/CPRNIMS.WebApi/Controllers/Inventory/InventoryReportsController.cs +++ b/CPRNIMS.WebApi/Controllers/Inventory/InventoryReportsController.cs @@ -1,6 +1,8 @@ -using CPRNIMS.Domain.Contracts.Inventory; +using Azure.Core; +using CPRNIMS.Domain.Contracts.Inventory; using CPRNIMS.Domain.Contracts.Reports; using CPRNIMS.Infrastructure.Dto.Inventory.Reports; +using CPRNIMS.Infrastructure.Dto.Inventory.Request; using CPRNIMS.WebApi.Security; using Microsoft.AspNetCore.Mvc; @@ -19,35 +21,35 @@ namespace CPRNIMS.WebApi.Controllers.Inventory } [HttpGet("GetRISReport")] - public async Task GetRISReport(DateTime dateFrom, DateTime dateTo, CancellationToken ct) + public async Task GetRISReport([FromQuery] InventoryReportsRequest request, CancellationToken ct) { var currentUser = User.ToUserClaims(); if (currentUser == null) return BadRequest(); - var data = await _reports.GetRISReportAsync(dateFrom, dateTo, ct, currentUser.DepartmentId, currentUser.UserName); + var data = await _reports.GetRISReportAsync(request, currentUser.UserName,currentUser.DepartmentId, ct); return Ok(data); } [HttpGet("GetMRSReport")] - public async Task GetMRSReport(DateTime dateFrom, DateTime dateTo, CancellationToken ct) + public async Task GetMRSReport([FromQuery] InventoryReportsRequest request, CancellationToken ct) { var currentUser = User.ToUserClaims(); if (currentUser == null) return BadRequest(); - var data = await _reports.GetMRSReportAsync(dateFrom, dateTo, ct, currentUser.DepartmentId, currentUser.UserName); + var data = await _reports.GetMRSReportAsync(request, currentUser.UserName, currentUser.DepartmentId, ct); return Ok(data); } [HttpGet("GetInventoryReport")] - public async Task GetInventoryReport(DateTime dateFrom, DateTime dateTo, CancellationToken ct) + public async Task GetInventoryReport([FromQuery] InventoryReportsRequest request, CancellationToken ct) { var currentUser = User.ToUserClaims(); if (currentUser == null) return BadRequest(); - var data = await _reports.GetInventoryReportAsync(dateFrom, dateTo, ct, currentUser.DepartmentId, currentUser.UserName); + var data = await _reports.GetInventoryReportAsync(request, currentUser.UserName, currentUser.DepartmentId, ct); return Ok(data); } diff --git a/CPRNIMS.WebApps/Controllers/Inventory/InventoryReportsController.cs b/CPRNIMS.WebApps/Controllers/Inventory/InventoryReportsController.cs index 9907b9d..1028125 100644 --- a/CPRNIMS.WebApps/Controllers/Inventory/InventoryReportsController.cs +++ b/CPRNIMS.WebApps/Controllers/Inventory/InventoryReportsController.cs @@ -1,40 +1,71 @@ -using CPRNIMS.Domain.UIContracts.Inventory; +using CPRNIMS.Domain.Services.Account; +using CPRNIMS.Domain.UIContracts.Account; +using CPRNIMS.Domain.UIContracts.Inventory; +using CPRNIMS.Infrastructure.Dto.Inventory.Request; +using CPRNIMS.Infrastructure.Helper; +using CPRNIMS.WebApps.Controllers.Base; using Microsoft.AspNetCore.Mvc; +using System.Drawing.Printing; namespace CPRNIMS.WebApps.Controllers.Inventory { - public class InventoryReportsController : Controller + public class InventoryReportsController : BaseMethod { private readonly IInventoryReports _reports; - public InventoryReportsController(IInventoryReports reports) - { - _reports=reports; - } + public InventoryReportsController(ErrorLogHelper errorMessageService, + IWebHostEnvironment webHostEnvironment, TokenHelper tokenHelper, IInventoryReports reports, IAccount account) + : base(errorMessageService, webHostEnvironment, tokenHelper, account) => _reports = reports; + [HttpGet] - public async Task GetInventoryReport(DateTime dateFrom, DateTime dateTo, CancellationToken ct) + public async Task GetInventoryReport( + DateTime dateFrom, DateTime dateTo, string? department,int page = 1, int pageSize = 10, bool paginate = true, CancellationToken ct = default) { - var response = await _reports.GetInventoryReportAsync(dateFrom, dateTo, ct); - return GetResponse(response); - } - [HttpGet] - public async Task GetRISReport(DateTime dateFrom, DateTime dateTo, CancellationToken ct) - { - var response = await _reports.GetRISReportAsync(dateFrom, dateTo, ct); - return GetResponse(response); - } - [HttpGet] - public async Task GetMRSReport(DateTime dateFrom, DateTime dateTo, CancellationToken ct) - { - var response = await _reports.GetMRSReportAsync(dateFrom, dateTo, ct); - return GetResponse(response); - } - protected IActionResult GetResponse(T response) - { - return Json(new + var dto = new InventoryReportsRequest { - success = response != null, - data = response ?? Activator.CreateInstance() - }); + DateFrom = dateFrom, + DateTo = dateTo, + Department = department, + Page = page, + PageSize = pageSize, + Paginate = paginate + }; + GetUser(); + var response = await _reports.GetInventoryReportAsync(dto,ct); + return GetResponse(response); + } + [HttpGet] + public async Task GetRISReport( + DateTime dateFrom, DateTime dateTo, string? department, int page = 1, int pageSize = 10, bool paginate = true, CancellationToken ct = default) + { + var dto = new InventoryReportsRequest + { + DateFrom = dateFrom, + DateTo = dateTo, + Department = department, + Page = page, + PageSize = pageSize, + Paginate = paginate + }; + GetUser(); + var response = await _reports.GetRISReportAsync(dto, ct); + return GetResponse(response); + } + [HttpGet] + public async Task GetMRSReport( + DateTime dateFrom, DateTime dateTo, string? department, int page = 1, int pageSize = 10, bool paginate = true, CancellationToken ct = default) + { + var dto = new InventoryReportsRequest + { + DateFrom = dateFrom, + DateTo = dateTo, + Department = department, + Page = page, + PageSize = pageSize, + Paginate = paginate + }; + GetUser(); + var response = await _reports.GetMRSReportAsync(dto, ct); + return GetResponse(response); } } } diff --git a/CPRNIMS.WebApps/Controllers/Inventory/MRSMgmtController.cs b/CPRNIMS.WebApps/Controllers/Inventory/MRSMgmtController.cs index eadcdc5..25608a6 100644 --- a/CPRNIMS.WebApps/Controllers/Inventory/MRSMgmtController.cs +++ b/CPRNIMS.WebApps/Controllers/Inventory/MRSMgmtController.cs @@ -25,7 +25,7 @@ namespace CPRNIMS.WebApps.Controllers.Inventory "2" => 2, _ => null }; - + GetUser(); var result = await _mrs.GetMRSPaged(new MRSPagedRequest { SearchMRSNo = searchMRSNo, @@ -43,6 +43,7 @@ namespace CPRNIMS.WebApps.Controllers.Inventory [HttpGet] public async Task SearchRIS([FromQuery] int? searchProjectCodeId, string? searchRISNo , CancellationToken ct = default) { + GetUser(); var result = await _mrs.SearchRIS(new SearchRISProjectCodeRequest { SearchRISNo = searchRISNo, @@ -54,6 +55,7 @@ namespace CPRNIMS.WebApps.Controllers.Inventory [HttpGet] public async Task SearchProjects([FromQuery] string? searchProjectCode, CancellationToken ct = default) { + GetUser(); var result = await _mrs.SearchProjects(new SearchRISProjectCodeRequest { SearchProjectCode = searchProjectCode,}, ct); @@ -62,6 +64,7 @@ namespace CPRNIMS.WebApps.Controllers.Inventory [HttpPost] public async Task CreateMRS([FromBody] CreateMRSRequest request,CancellationToken ct) { + GetUser(); var result = await _mrs.CreateMRS(request, ct); if (!result.success) @@ -73,6 +76,7 @@ namespace CPRNIMS.WebApps.Controllers.Inventory [HttpPost] public async Task ApproveMRS([FromBody] ApproveMRSRequest request,CancellationToken ct) { + GetUser(); var result = await _mrs.ApproveMRS(request, ct); if (!result.success) @@ -84,6 +88,7 @@ namespace CPRNIMS.WebApps.Controllers.Inventory [HttpPost] public async Task CancelMRS([FromBody] CancelMRSRequest request,CancellationToken ct) { + GetUser(); var result = await _mrs.CancelMRS(request, ct); if (!result.success) diff --git a/CPRNIMS.WebApps/Controllers/Inventory/RISMgmtController.cs b/CPRNIMS.WebApps/Controllers/Inventory/RISMgmtController.cs index f7671a0..ed7305f 100644 --- a/CPRNIMS.WebApps/Controllers/Inventory/RISMgmtController.cs +++ b/CPRNIMS.WebApps/Controllers/Inventory/RISMgmtController.cs @@ -5,9 +5,6 @@ using CPRNIMS.Infrastructure.Dto.Inventory.Request; using CPRNIMS.Infrastructure.Helper; using CPRNIMS.WebApps.Controllers.Base; using Microsoft.AspNetCore.Mvc; -using FastReport; -using FastReport.Web; -using System.Threading.Tasks; namespace CPRNIMS.WebApps.Controllers.Inventory { @@ -27,30 +24,10 @@ namespace CPRNIMS.WebApps.Controllers.Inventory } - public IActionResult RISReport(DateTime? dateFrom, DateTime? dateTo, CancellationToken ct) - { - return View(); - } - public async Task RISReportPdf(DateTime? dateFrom, DateTime? dateTo, CancellationToken ct) - { - var from = dateFrom ?? DateTime.Today.AddDays(-15); - var to = dateTo ?? DateTime.Today; - - var templatePath = Path.Combine(_env.WebRootPath, "Reports", "RIS_v2.frx"); - var report = await _builder.RISBuildAsync(from, to, templatePath, ct); - report.Prepare(); - - using var pdf = new FastReport.Export.PdfSimple.PDFSimpleExport(); - using var ms = new MemoryStream(); - pdf.Export(report, ms); - - return File(ms.ToArray(), "application/pdf", - $"RIS-Report-{from:yyyyMMdd}-{to:yyyyMMdd}.pdf"); - } - [HttpPost] public async Task CreateRIS([FromBody] CreateRISRequest request,CancellationToken ct) { + GetUser(); var result = await _ris.CreateRIS(request,ct); if (!result.success) return BadRequest(new { success = false, message = result.message }); @@ -60,6 +37,7 @@ namespace CPRNIMS.WebApps.Controllers.Inventory [HttpPost] public async Task ApproveRIS([FromBody] ApproveRISRequest request,CancellationToken ct) { + GetUser(); var result = await _ris.ApproveRIS(request, ct); if (!result.success) @@ -71,6 +49,7 @@ namespace CPRNIMS.WebApps.Controllers.Inventory [HttpPost] public async Task CancelRIS([FromBody] CancelRISRequest request,CancellationToken ct) { + GetUser(); if (string.IsNullOrWhiteSpace(request.Reason)) return BadRequest(new{success = false,message = "A reason for cancellation is required."}); @@ -94,7 +73,7 @@ namespace CPRNIMS.WebApps.Controllers.Inventory "2" => 2, _ => null }; - + GetUser(); var result = await _ris.GetRISPaged(new RISPagedRequest { SearchRISNo = searchRISNo, diff --git a/CPRNIMS.WebApps/Views/Components/Inventory/TabPage/Reports/InventorySummaryReport.cshtml b/CPRNIMS.WebApps/Views/Components/Inventory/TabPage/Reports/InventorySummaryReport.cshtml index 76960c3..14a14d9 100644 --- a/CPRNIMS.WebApps/Views/Components/Inventory/TabPage/Reports/InventorySummaryReport.cshtml +++ b/CPRNIMS.WebApps/Views/Components/Inventory/TabPage/Reports/InventorySummaryReport.cshtml @@ -5,38 +5,56 @@ color: #1a2e35; padding: 24px; } + + @@media print { - body { padding: 0; margin: 0; background: #fff;} - body * { visibility: hidden;} + body > *:not(#print-mount) { + display: none !important; + } - #inv-rpt-page, #inv-rpt-page * { - visibility: visible; + #print-mount { + display: block !important; + position: static !important; + width: 100%; + } + + #print-mount .rpt-page { + box-shadow: none !important; + border: none !important; + border-radius: 0 !important; + overflow: visible !important; + } + + .rpt-section { + page-break-inside: auto; + } + + .rpt-table { + table-layout: fixed; + width: 100%; + font-size: 9px; + page-break-inside: auto; + } + + .rpt-table th, .rpt-table td { + padding: 5px 6px; + white-space: normal; + word-break: break-word; + } + + .rpt-table tr { + page-break-inside: avoid; + } + + thead { + display: table-header-group; + } + + @@page { + size: A4 landscape; + margin: 5mm; + } } - - #inv-rpt-page { - position: absolute; - left: 0; - top: 0; - width: 100%; - box-shadow: none !important; - border: none !important; - border-radius: 0 !important; - } - - .no-print { - display: none !important; - } - - .rpt-table tr { - page-break-inside: avoid; - } - - thead { - display: table-header-group; - } - - } - .rpt-page { width: 100%; margin: 0; @@ -45,6 +63,7 @@ border-radius: var(--radius-lg, 14px); overflow: hidden; } + .rpt-header { padding: 20px 24px 16px; border-bottom: 1px solid #d6eaec; @@ -117,6 +136,7 @@ background: var(--teal-pale, #e6f7f8); border-color: var(--teal-dark, #0d5c63); } + .rpt-header-top { display: flex; align-items: flex-start; @@ -387,76 +407,256 @@ const xlsxBtn = document.getElementById("inv-rpt-excel"); const container = document.getElementById("inv-rpt-container"); - if (!fromEl || !toEl || !container) { - console.error("Inventory report subtab init failed — missing elements."); - return; + // Department + const deptWrap = document.getElementById("inv-rpt-dept-wrap"); + const deptTrigger = document.getElementById("inv-rpt-dept-trigger"); + const deptDropdown= document.getElementById("inv-rpt-dept-dropdown"); + const deptList = document.getElementById("inv-rpt-dept-list"); + const deptLbl = document.getElementById("inv-rpt-dept-lbl"); + const deptSearch = document.getElementById("inv-rpt-dept-search"); + + let showAll = false; + let selectedDept = ""; // "" = All + let allDepartments = []; // [{ name }] + + // open/close + deptTrigger.addEventListener("click", () => { + const open = deptDropdown.classList.toggle("open"); + deptTrigger.classList.toggle("open", open); + if (open) { deptSearch.value = ""; renderDeptOptions(""); deptSearch.focus(); } + }); + + // close on outside click + document.addEventListener("click", (e) => { + if (!deptWrap.contains(e.target)) { + deptDropdown.classList.remove("open"); + deptTrigger.classList.remove("open"); + } + }); + + // live filter + deptSearch.addEventListener("input", () => renderDeptOptions(deptSearch.value.trim().toLowerCase())); + + function renderDeptOptions(filter) { + const items = [{ name: "All Departments", value: "" }] + .concat(allDepartments.map(d => ({ name: d, value: d }))); + + const filtered = items.filter(o => + o.value === "" || o.name.toLowerCase().includes(filter)); + + if (filtered.length === 0) { + deptList.innerHTML = `
No departments found
`; + return; + } + + deptList.innerHTML = filtered.map(o => ` +
+ + ${_esc(o.name)} +
`).join(""); + + deptList.querySelectorAll(".inv-dep-opt").forEach(el => { + el.addEventListener("click", () => { + selectedDept = el.getAttribute("data-value"); + deptLbl.textContent = el.querySelector("span").textContent; + deptDropdown.classList.remove("open"); + deptTrigger.classList.remove("open"); + currentPage = 1; // reset to first page on filter change + fetchAndRender(); + }); + }); } // default: first day of current month -> today const today = new Date(); const first = new Date(today.getFullYear(), today.getMonth(), 1); + const deptEl = document.getElementById("inv-rpt-dept"); toEl.value = today.toISOString().slice(0, 10); fromEl.value = first.toISOString().slice(0, 10); - let lastData = null; // cache for export - - function buildParams() { - return new URLSearchParams({ dateFrom: fromEl.value, dateTo: toEl.value }); - } + let currentPage = 1; + let pageSize = 10; + let lastData = null; // paged data (for display) csvBtn.addEventListener("click", exportCsv); - genBtn.addEventListener("click", fetchAndRender); + genBtn.addEventListener("click", () => { currentPage = 1; fetchAndRender(); }); async function fetchAndRender() { - container.innerHTML = `
-
Loading report…
`; - + container.innerHTML = `
Loading report…
`; try { - const res = await fetch(`/InventoryReports/GetInventoryReport?${buildParams()}`); + const res = await fetch(`/InventoryReports/GetInventoryReport?${buildParams()}`); if (!res.ok) throw new Error(`HTTP ${res.status}`); const json = await res.json(); - lastData = json.data ?? json; + lastData = json.data ?? json; + + // populate dropdown from SP's department result set (eager-loaded) + if (Array.isArray(lastData.departments)) { + allDepartments = lastData.departments; + renderDeptOptions(""); + } + renderReport(lastData); + renderPager(lastData); } catch (err) { console.error("Inventory report fetch error:", err); - container.innerHTML = ` -
- -

Failed to load report

-

Please try again.

-
`; + container.innerHTML = `

Failed to load report

Please try again.

`; } } + function renderPager(data) { + const total = data.totalRows ?? 0; + if (total === 0) return; + + const page = data.page ?? 1; + const size = showAll ? total : (data.pageSize ?? pageSize); + const pages = showAll ? 1 : Math.max(1, Math.ceil(total / size)); + + const from = showAll ? 1 : (page - 1) * size + 1; + const to = showAll ? total : Math.min(page * size, total); + + // build a compact page-number window (max 5 numbers) + let nums = []; + const win = 2; + for (let i = Math.max(1, page - win); i <= Math.min(pages, page + win); i++) nums.push(i); + + const pager = document.createElement("div"); + + pager.className = "inv-pagination no-print"; + pager.innerHTML = ` +
+ Show + + Showing ${from}–${to} of ${total.toLocaleString()} items +
+
+ + ${nums[0] > 1 ? `${nums[0] > 2 ? `` : ""}` : ""} + ${nums.map(n => ``).join("")} + ${nums[nums.length-1] < pages ? `${nums[nums.length-1] < pages-1 ? `` : ""}` : ""} + +
`; + + container.appendChild(pager); + + pager.querySelectorAll(".inv-pg-btn").forEach(btn => { + btn.addEventListener("click", () => { + const target = parseInt(btn.getAttribute("data-pg"), 10); + if (!isNaN(target) && target >= 1 && target <= pages && target !== page) { + currentPage = target; + fetchAndRender(); + container.scrollIntoView({ behavior: "smooth", block: "start" }); + } + }); + }); + + // page-size selector — must be inside renderPager so `pager` is in scope + const pgszSel = pager.querySelector("#inv-pgsz-sel"); + if (pgszSel) { + pgszSel.value = showAll ? "all" : String(pageSize); + pgszSel.addEventListener("change", () => { + if (pgszSel.value === "all") { + showAll = true; + } else { + showAll = false; + pageSize = parseInt(pgszSel.value, 10) || 50; + } + currentPage = 1; + fetchAndRender(); + }); + } + } + + function buildParams(extra = {}) { + const p = new URLSearchParams({ + dateFrom: fromEl.value, + dateTo: toEl.value, + page: currentPage, + pageSize: pageSize, + paginate: !showAll + }); + if (selectedDept) p.append("department", selectedDept); + Object.entries(extra).forEach(([k, v]) => p.set(k, v)); + return p; + } + + const printBtn = document.getElementById("inv-rpt-print"); + printBtn?.addEventListener("click", async () => { + // 1. get the full dataset (all 145 rows) + const full = await fetchFullForExport(); + renderReport(full); + + // wait for the DOM to paint the re-rendered report + setTimeout(() => { + const report = document.getElementById("inv-rpt-page"); + if (!report) { window.print(); return; } + + // 2. clone the report to a body-level mount + let mount = document.getElementById("print-mount"); + if (!mount) { + mount = document.createElement("div"); + mount.id = "print-mount"; + document.body.appendChild(mount); + } + mount.innerHTML = ""; + mount.appendChild(report.cloneNode(true)); + + // 3. print, then clean up + restore paged view + const cleanup = () => { + mount.remove(); + fetchAndRender(); + window.removeEventListener("afterprint", cleanup); + }; + window.addEventListener("afterprint", cleanup); + + window.print(); + }, 200); + }); + + async function fetchFullForExport() { + const params = buildParams({ paginate: false }); + params.delete("page"); + params.delete("pageSize"); + const res = await fetch(`/InventoryReports/GetInventoryReport?${params}`); + if (!res.ok) throw new Error(`HTTP ${res.status}`); + const json = await res.json(); + return json.data ?? json; + } // ── CSV export (client-side, no backend needed) ── - function exportCsv() { - if (!lastData) return; - const rows = lastData.rows ?? []; - const byCat = lastData.byCategory ?? []; - const alerts = lastData.alerts ?? []; - const summary = lastData.summary ?? {}; - const headers = ["Item Name","Item No.","Category","Lot No.","Qty In","Qty Out","On Hand","Stock %"]; + async function exportCsv() { + const data = await fetchFullForExport(); + if (!data) return; + const rows = data.rows ?? []; + const byCat = data.byCategory ?? []; + const alerts = data.alerts ?? []; + const summary = data.summary ?? {}; + const headers = ["Item Name / UOM","Item No.","Category","Department","Lot No","Qty In","Qty Out","On Hand","Stock %"]; const csvLines = [ `Inventory Summary Report`, - `Company,${csvCell(lastData.companyName)}`, - `Report No,${csvCell(lastData.reportNo)}`, + `Company,${csvCell(data.companyName)}`, + `Report No,${csvCell(data.reportNo)}`, `Period,${csvCell(fromEl.value)} to ${csvCell(toEl.value)}`, ``, - // ── Inventory detail ── headers.map(csvCell).join(","), ...rows.map(r => [ - r.itemName, r.itemNo, r.itemCategoryName, r.lotNo, - r.qtyIn, r.qtyOut, r.qtyOnHand, r.stockPct + r.itemName, r.itemNo, r.itemCategoryName, r.department, r.lotNo,r.qtyIn, r.qtyOut, r.qtyOnHand, r.stockPct ].map(csvCell).join(",")), - ["Total","","","", summary.totalQtyIn ?? 0, summary.totalQtyOut ?? 0, summary.totalOnHand ?? 0, ""].map(csvCell).join(","), + ["Total","","","","","", summary.totalQtyIn ?? 0, summary.totalQtyOut ?? 0, summary.totalOnHand ?? 0, ""].map(csvCell).join(","), ``, - // ── Stock level by category ── `Stock Level by Category`, ["Category","Avg Stock %"].map(csvCell).join(","), ...byCat.map(c => [c.categoryName, c.avgStockPct].map(csvCell).join(",")), ``, - // ── Items requiring attention ── `Items Requiring Attention`, ["Item","On Hand","Alert"].map(csvCell).join(","), ...alerts.map(a => [a.itemName, a.qtyOnHand, a.severity].map(csvCell).join(",")) @@ -468,10 +668,7 @@ "text/csv;charset=utf-8;" ); } - function csvCell(v) { - const s = String(v ?? ""); - return /[",\r\n]/.test(s) ? `"${s.replace(/"/g, '""')}"` : s; - } + function downloadBlob(content, filename, mime) { const blob = new Blob([content], { type: mime }); const url = URL.createObjectURL(blob); @@ -521,7 +718,7 @@ -
+
Total SKUs
${summary.totalSKUs ?? 0}
@@ -530,6 +727,7 @@
Total on hand
${(summary.totalOnHand ?? 0).toLocaleString()}
+
Low stock
${summary.lowStockCount ?? 0}
@@ -542,12 +740,17 @@
Inventory detail by item
- - +
+ - + + + + + - + + @@ -556,14 +759,15 @@ - + + `).join("")} - + @@ -638,15 +842,16 @@ return `${_esc(severity)}`; } - // ── Excel export (client-side, HTML-table .xls — Excel opens natively) ── - function exportExcel() { - if (!lastData) return; - const rows = lastData.rows ?? []; - const byCat = lastData.byCategory ?? []; - const alerts = lastData.alerts ?? []; - const summary = lastData.summary ?? {}; + // ── Excel export (client-side, HTML-table .xls — Excel opens natively) ── + async function exportExcel() { + const data = await fetchFullForExport(); + if (!data) return; + const rows = data.rows ?? []; + const byCat = data.byCategory ?? []; + const alerts = data.alerts ?? []; + const summary = data.summary ?? {}; - const headerCells = ["Item Name","Item No.","Category","Lot No.","Qty In","Qty Out","On Hand","Stock %"] + const headerCells = ["Item Name / UOM","Item No.","Category","Department","Lot No","Qty In","Qty Out","On Hand","Stock %"] .map(h => ``).join(""); const bodyRows = rows.map(r => ` @@ -654,7 +859,8 @@ - + + @@ -674,23 +880,27 @@
Item NameItem No.CategoryLot No.Item Name / UOMItem No.CategoryDepartmentLot No Qty InQty OutOn HandStock LevelOn HandStock Level
${_esc(r.itemName)} #${_esc(r.itemNo)} ${_esc(r.itemCategoryName)}${_esc(r.lotNo)}${_esc(r.department ?? "—")}${r.lotNo} ${r.qtyIn} ${r.qtyOut} ${r.qtyOnHand} ${_stockBar(r.stockPct)}
TotalTotal ${summary.totalQtyIn ?? 0} ${summary.totalQtyOut ?? 0} ${summary.totalOnHand ?? 0}${_esc(h)}${_esc(r.itemName)} ${_esc(r.itemNo)} ${_esc(r.itemCategoryName)}${_esc(r.lotNo)}${_esc(r.department ?? "")}${_esc(r.lotNo ?? "")} ${r.qtyIn} ${r.qtyOut} ${r.qtyOnHand}
- - - - + + + + ${headerCells} ${bodyRows} - + + + + + + +
Inventory Summary Report
Company${_esc(lastData.companyName)}
Report No${_esc(lastData.reportNo)}
Period${_esc(fromEl.value)} to ${_esc(toEl.value)}
Inventory Summary Report
Company${_esc(data.companyName)}
Report No${_esc(data.reportNo)}
Period${_esc(fromEl.value)} to ${_esc(toEl.value)}
Total${summary.totalQtyIn ?? 0}${summary.totalQtyOut ?? 0}${summary.totalOnHand ?? 0}
Total${summary.totalQtyIn ?? 0}${summary.totalQtyOut ?? 0}${summary.totalOnHand ?? 0}
-
${catRows}
Stock Level by Category
CategoryAvg Stock %
-
@@ -705,6 +915,6 @@ xlsxBtn.addEventListener("click", exportExcel); - fetchAndRender(); + fetchAndRender(); })(); \ No newline at end of file diff --git a/CPRNIMS.WebApps/Views/Components/Inventory/TabPage/Reports/InventorySummaryReportFinance.cshtml b/CPRNIMS.WebApps/Views/Components/Inventory/TabPage/Reports/InventorySummaryReportFinance.cshtml index b0201e6..7a8c655 100644 --- a/CPRNIMS.WebApps/Views/Components/Inventory/TabPage/Reports/InventorySummaryReportFinance.cshtml +++ b/CPRNIMS.WebApps/Views/Components/Inventory/TabPage/Reports/InventorySummaryReportFinance.cshtml @@ -5,37 +5,32 @@ color: #1a2e35; padding: 24px; } - @@media print { - body { padding: 0; margin: 0; background: #fff;} - body * { visibility: hidden;} - #inv-rpt-page, #inv-rpt-page * { - visibility: visible; + @@media print { + body > *:not(#print-mount) { + display: none !important; } - - #inv-rpt-page { - position: absolute; - left: 0; - top: 0; + #print-mount { + display: block !important; + position: static !important; width: 100%; + } + #print-mount .rpt-page { box-shadow: none !important; border: none !important; border-radius: 0 !important; + overflow: visible !important; } - - .no-print { - display: none !important; - } - - .rpt-table tr { - page-break-inside: avoid; - } - - thead { - display: table-header-group; - } - + .rpt-section { page-break-inside: auto; } + .rpt-table { table-layout: fixed; width: 100%; font-size: 9px; page-break-inside: auto; } + .rpt-table th, .rpt-table td { padding: 5px 6px; white-space: normal; word-break: break-word; } + .rpt-table tr { page-break-inside: avoid; } + thead { display: table-header-group; } + @@page { + size: A4 landscape; + margin: 5mm; } +} .rpt-page { width: 100%; @@ -387,76 +382,258 @@ const xlsxBtn = document.getElementById("inv-rpt-excel"); const container = document.getElementById("inv-rpt-container"); - if (!fromEl || !toEl || !container) { - console.error("Inventory report subtab init failed — missing elements."); - return; + // Department + const deptWrap = document.getElementById("inv-rpt-dept-wrap"); + const deptTrigger = document.getElementById("inv-rpt-dept-trigger"); + const deptDropdown= document.getElementById("inv-rpt-dept-dropdown"); + const deptList = document.getElementById("inv-rpt-dept-list"); + const deptLbl = document.getElementById("inv-rpt-dept-lbl"); + const deptSearch = document.getElementById("inv-rpt-dept-search"); + + let showAll = false; + let selectedDept = ""; // "" = All + let allDepartments = []; // [{ name }] + + // open/close + deptTrigger.addEventListener("click", () => { + const open = deptDropdown.classList.toggle("open"); + deptTrigger.classList.toggle("open", open); + if (open) { deptSearch.value = ""; renderDeptOptions(""); deptSearch.focus(); } + }); + + // close on outside click + document.addEventListener("click", (e) => { + if (!deptWrap.contains(e.target)) { + deptDropdown.classList.remove("open"); + deptTrigger.classList.remove("open"); + } + }); + + // live filter + deptSearch.addEventListener("input", () => renderDeptOptions(deptSearch.value.trim().toLowerCase())); + + function renderDeptOptions(filter) { + const items = [{ name: "All Departments", value: "" }] + .concat(allDepartments.map(d => ({ name: d, value: d }))); + + const filtered = items.filter(o => + o.value === "" || o.name.toLowerCase().includes(filter)); + + if (filtered.length === 0) { + deptList.innerHTML = `
No departments found
`; + return; + } + + deptList.innerHTML = filtered.map(o => ` +
+ + ${_esc(o.name)} +
`).join(""); + + deptList.querySelectorAll(".inv-dep-opt").forEach(el => { + el.addEventListener("click", () => { + selectedDept = el.getAttribute("data-value"); + deptLbl.textContent = el.querySelector("span").textContent; + deptDropdown.classList.remove("open"); + deptTrigger.classList.remove("open"); + currentPage = 1; // reset to first page on filter change + fetchAndRender(); + }); + }); } // default: first day of current month -> today const today = new Date(); const first = new Date(today.getFullYear(), today.getMonth(), 1); + const deptEl = document.getElementById("inv-rpt-dept"); toEl.value = today.toISOString().slice(0, 10); fromEl.value = first.toISOString().slice(0, 10); - let lastData = null; // cache for export - - function buildParams() { - return new URLSearchParams({ dateFrom: fromEl.value, dateTo: toEl.value }); - } + let currentPage = 1; + let pageSize = 10; + let lastData = null; // paged data (for display) csvBtn.addEventListener("click", exportCsv); - genBtn.addEventListener("click", fetchAndRender); + genBtn.addEventListener("click", () => { currentPage = 1; fetchAndRender(); }); async function fetchAndRender() { - container.innerHTML = `
-
Loading report…
`; - + container.innerHTML = `
Loading report…
`; try { - const res = await fetch(`/InventoryReports/GetInventoryReport?${buildParams()}`); + const res = await fetch(`/InventoryReports/GetInventoryReport?${buildParams()}`); if (!res.ok) throw new Error(`HTTP ${res.status}`); const json = await res.json(); - lastData = json.data ?? json; + lastData = json.data ?? json; + + // populate dropdown from SP's department result set (eager-loaded) + if (Array.isArray(lastData.departments)) { + allDepartments = lastData.departments; + renderDeptOptions(""); + } + renderReport(lastData); + renderPager(lastData); } catch (err) { console.error("Inventory report fetch error:", err); - container.innerHTML = ` -
- -

Failed to load report

-

Please try again.

-
`; + container.innerHTML = `

Failed to load report

Please try again.

`; } } + function renderPager(data) { + const total = data.totalRows ?? 0; + if (total === 0) return; + + const page = data.page ?? 1; + const size = showAll ? total : (data.pageSize ?? pageSize); + const pages = showAll ? 1 : Math.max(1, Math.ceil(total / size)); + + const from = showAll ? 1 : (page - 1) * size + 1; + const to = showAll ? total : Math.min(page * size, total); + + // build a compact page-number window (max 5 numbers) + let nums = []; + const win = 2; + for (let i = Math.max(1, page - win); i <= Math.min(pages, page + win); i++) nums.push(i); + + const pager = document.createElement("div"); + + pager.className = "inv-pagination no-print"; + pager.innerHTML = ` +
+ Show + + Showing ${from}–${to} of ${total.toLocaleString()} items +
+
+ + ${nums[0] > 1 ? `${nums[0] > 2 ? `` : ""}` : ""} + ${nums.map(n => ``).join("")} + ${nums[nums.length-1] < pages ? `${nums[nums.length-1] < pages-1 ? `` : ""}` : ""} + +
`; + + container.appendChild(pager); + + pager.querySelectorAll(".inv-pg-btn").forEach(btn => { + btn.addEventListener("click", () => { + const target = parseInt(btn.getAttribute("data-pg"), 10); + if (!isNaN(target) && target >= 1 && target <= pages && target !== page) { + currentPage = target; + fetchAndRender(); + container.scrollIntoView({ behavior: "smooth", block: "start" }); + } + }); + }); + + // page-size selector — must be inside renderPager so `pager` is in scope + const pgszSel = pager.querySelector("#inv-pgsz-sel"); + if (pgszSel) { + pgszSel.value = showAll ? "all" : String(pageSize); + pgszSel.addEventListener("change", () => { + if (pgszSel.value === "all") { + showAll = true; + } else { + showAll = false; + pageSize = parseInt(pgszSel.value, 10) || 50; + } + currentPage = 1; + fetchAndRender(); + }); + } + } + + function buildParams(extra = {}) { + const p = new URLSearchParams({ + dateFrom: fromEl.value, + dateTo: toEl.value, + page: currentPage, + pageSize: pageSize, + paginate: !showAll + }); + if (selectedDept) p.append("department", selectedDept); + Object.entries(extra).forEach(([k, v]) => p.set(k, v)); + return p; + } + + const printBtn = document.getElementById("inv-rpt-print"); + printBtn?.addEventListener("click", async () => { + // 1. get the full dataset (all 145 rows) + const full = await fetchFullForExport(); + renderReport(full); + + // wait for the DOM to paint the re-rendered report + setTimeout(() => { + const report = document.getElementById("inv-rpt-page"); + if (!report) { window.print(); return; } + + // 2. clone the report to a body-level mount + let mount = document.getElementById("print-mount"); + if (!mount) { + mount = document.createElement("div"); + mount.id = "print-mount"; + document.body.appendChild(mount); + } + mount.innerHTML = ""; + mount.appendChild(report.cloneNode(true)); + + // 3. print, then clean up + restore paged view + const cleanup = () => { + mount.remove(); + fetchAndRender(); + window.removeEventListener("afterprint", cleanup); + }; + window.addEventListener("afterprint", cleanup); + + window.print(); + }, 200); + }); + + async function fetchFullForExport() { + const params = buildParams({ paginate: false }); + params.delete("page"); + params.delete("pageSize"); + const res = await fetch(`/InventoryReports/GetInventoryReport?${params}`); + if (!res.ok) throw new Error(`HTTP ${res.status}`); + const json = await res.json(); + return json.data ?? json; + } // ── CSV export (client-side, no backend needed) ── - function exportCsv() { - if (!lastData) return; - const rows = lastData.rows ?? []; - const byCat = lastData.byCategory ?? []; - const alerts = lastData.alerts ?? []; - const summary = lastData.summary ?? {}; - const headers = ["Item Name","Item No.","Category","Unit Price","Qty In","Qty Out","On Hand","Stock %"]; + async function exportCsv() { + const data = await fetchFullForExport(); + if (!data) return; + const rows = data.rows ?? []; + const byCat = data.byCategory ?? []; + const alerts = data.alerts ?? []; + const summary = data.summary ?? {}; + const headers = ["Item Name / UOM","Item No.","Category","Department","Currency","Unit Price","Qty In","Qty Out","On Hand","Total Value","Stock %"]; const csvLines = [ `Inventory Summary Report`, - `Company,${csvCell(lastData.companyName)}`, - `Report No,${csvCell(lastData.reportNo)}`, + `Company,${csvCell(data.companyName)}`, + `Report No,${csvCell(data.reportNo)}`, `Period,${csvCell(fromEl.value)} to ${csvCell(toEl.value)}`, ``, - // ── Inventory detail ── headers.map(csvCell).join(","), ...rows.map(r => [ - r.itemName, r.itemNo, r.itemCategoryName, r.unitPrice, - r.qtyIn, r.qtyOut, r.qtyOnHand, r.stockPct + r.itemName, r.itemNo, r.itemCategoryName, r.department, r.currencyCode, + r.unitPrice, r.qtyIn, r.qtyOut, r.qtyOnHand, + (Number(r.qtyOnHand) || 0) * (Number(r.unitPrice) || 0), r.stockPct ].map(csvCell).join(",")), - ["Total","","","", summary.totalQtyIn ?? 0, summary.totalQtyOut ?? 0, summary.totalOnHand ?? 0, ""].map(csvCell).join(","), + ["Total","","","","","", summary.totalQtyIn ?? 0, summary.totalQtyOut ?? 0, summary.totalOnHand ?? 0, summary.totalValue ?? 0, ""].map(csvCell).join(","), ``, - // ── Stock level by category ── `Stock Level by Category`, ["Category","Avg Stock %"].map(csvCell).join(","), ...byCat.map(c => [c.categoryName, c.avgStockPct].map(csvCell).join(",")), ``, - // ── Items requiring attention ── `Items Requiring Attention`, ["Item","On Hand","Alert"].map(csvCell).join(","), ...alerts.map(a => [a.itemName, a.qtyOnHand, a.severity].map(csvCell).join(",")) @@ -468,10 +645,7 @@ "text/csv;charset=utf-8;" ); } - function csvCell(v) { - const s = String(v ?? ""); - return /[",\r\n]/.test(s) ? `"${s.replace(/"/g, '""')}"` : s; - } + function downloadBlob(content, filename, mime) { const blob = new Blob([content], { type: mime }); const url = URL.createObjectURL(blob); @@ -547,9 +721,11 @@
Inventory detail by item
Items Requiring Attention
- + - + + + @@ -563,15 +739,16 @@ - + + - + `).join("")} - + @@ -628,13 +805,13 @@ `; } - function _money(v) { + function _money(v, currency) { const n = Number(v) || 0; - return n.toLocaleString("en-PH", { - style: "currency", - currency: "PHP", - minimumFractionDigits: 2 + const amount = n.toLocaleString("en-PH", { + minimumFractionDigits: 2, + maximumFractionDigits: 2 }); + return currency ? `${currency} ${amount}` : amount; } function _stockBar(pct) { const cls = pct < 20 ? "fill-coral" : pct < 50 ? "fill-amber" : "fill-teal"; @@ -654,15 +831,16 @@ return `${_esc(severity)}`; } - // ── Excel export (client-side, HTML-table .xls — Excel opens natively) ── - function exportExcel() { - if (!lastData) return; - const rows = lastData.rows ?? []; - const byCat = lastData.byCategory ?? []; - const alerts = lastData.alerts ?? []; - const summary = lastData.summary ?? {}; + // ── Excel export (client-side, HTML-table .xls — Excel opens natively) ── + async function exportExcel() { + const data = await fetchFullForExport(); + if (!data) return; + const rows = data.rows ?? []; + const byCat = data.byCategory ?? []; + const alerts = data.alerts ?? []; + const summary = data.summary ?? {}; - const headerCells = ["Item Name","Item No.","Category","Unit Price","Qty In","Qty Out","On Hand","Stock %"] + const headerCells = ["Item Name / UOM","Item No.","Category","Department","Currency","Unit Price","Qty In","Qty Out","On Hand","Total Value","Stock %"] .map(h => ``).join(""); const bodyRows = rows.map(r => ` @@ -670,10 +848,13 @@ - + + + + `).join(""); @@ -690,14 +871,14 @@
Item NameItem No.CategoryItem Name / UOMItem No.CategoryDepartment Unit Price Qty InQty Out On Hand${_esc(r.itemName)} #${_esc(r.itemNo)} ${_esc(r.itemCategoryName)}${_money(r.unitPrice)}${_esc(r.department ?? "—")}${_money(r.unitPrice, r.currencyCode)} ${r.qtyIn} ${r.qtyOut} ${r.qtyOnHand}${_money(r.qtyOnHand * r.unitPrice)}${_money(r.qtyOnHand * r.unitPrice, r.currencyCode)} ${_stockBar(r.stockPct)}
TotalTotal ${summary.totalQtyIn ?? 0} ${summary.totalQtyOut ?? 0} ${summary.totalOnHand ?? 0} ${_esc(h)}${_esc(r.itemName)} ${_esc(r.itemNo)} ${_esc(r.itemCategoryName)}${_esc(r.unitPrice)}${_esc(r.department ?? "")}${_esc(r.currencyCode ?? "")}${r.unitPrice} ${r.qtyIn} ${r.qtyOut} ${r.qtyOnHand}${(Number(r.qtyOnHand)||0) * (Number(r.unitPrice)||0)} ${r.stockPct}
- - - - + + + + ${headerCells} ${bodyRows} - +
Inventory Summary Report
Company${_esc(lastData.companyName)}
Report No${_esc(lastData.reportNo)}
Period${_esc(fromEl.value)} to ${_esc(toEl.value)}
Inventory Summary Report
Company${_esc(data.companyName)}
Report No${_esc(data.reportNo)}
Period${_esc(fromEl.value)} to ${_esc(toEl.value)}
Total${summary.totalQtyIn ?? 0}${summary.totalQtyOut ?? 0}${summary.totalOnHand ?? 0}
Total${summary.totalQtyIn ?? 0}${summary.totalQtyOut ?? 0}${summary.totalOnHand ?? 0}${summary.totalValue ?? 0}

@@ -721,6 +902,6 @@ xlsxBtn.addEventListener("click", exportExcel); - fetchAndRender(); + fetchAndRender(); })(); \ No newline at end of file diff --git a/CPRNIMS.WebApps/Views/Components/Inventory/TabPage/Reports/MRSReport.cshtml b/CPRNIMS.WebApps/Views/Components/Inventory/TabPage/Reports/MRSReport.cshtml index 8445c90..a8d09d7 100644 --- a/CPRNIMS.WebApps/Views/Components/Inventory/TabPage/Reports/MRSReport.cshtml +++ b/CPRNIMS.WebApps/Views/Components/Inventory/TabPage/Reports/MRSReport.cshtml @@ -7,41 +7,52 @@ } @@media print { - body { - padding: 0; - margin: 0; - background: #fff; - } - - body * { - visibility: hidden; - } - - #mrs-rpt-page, #mrs-rpt-page * { - visibility: visible; - } - - #mrs-rpt-page { - position: absolute; - left: 0; - top: 0; - width: 100%; - box-shadow: none !important; - border: none !important; - border-radius: 0 !important; - } - - .no-print { + body > *:not(#print-mount) { display: none !important; } - .rpt-table tr { - page-break-inside: avoid; + #print-mount { + display: block !important; + position: static !important; + width: 100%; } + #print-mount .rpt-page { + box-shadow: none !important; + border: none !important; + border-radius: 0 !important; + overflow: visible !important; + } + + .rpt-section { + page-break-inside: auto; + } + + .rpt-table { + table-layout: fixed; + width: 100%; + font-size: 9px; + page-break-inside: auto; + } + + .rpt-table th, .rpt-table td { + padding: 5px 6px; + white-space: normal; + word-break: break-word; + } + + .rpt-table tr { + page-break-inside: avoid; + } + thead { display: table-header-group; } + + @@page { + size: A4 landscape; + margin: 5mm; + } } .rpt-page { @@ -197,7 +208,7 @@ .kpi-strip { display: grid; - grid-template-columns: repeat(4, 1fr); + grid-template-columns: repeat(3, 1fr); gap: 0; border-bottom: 1px solid #d6eaec; } @@ -584,9 +595,53 @@ return ` ${_esc(label)}`; } + const printBtn = document.getElementById("inv-rpt-print"); + printBtn?.addEventListener("click", async () => { + // 1. get the full dataset (all 145 rows) + const full = await fetchFullForExport(); + renderReport(full); + + // wait for the DOM to paint the re-rendered report + setTimeout(() => { + const report = document.getElementById("mrs-rpt-page"); + if (!report) { window.print(); return; } + + // 2. clone the report to a body-level mount + let mount = document.getElementById("print-mount"); + if (!mount) { + mount = document.createElement("div"); + mount.id = "print-mount"; + document.body.appendChild(mount); + } + mount.innerHTML = ""; + mount.appendChild(report.cloneNode(true)); + + // 3. print, then clean up + restore paged view + const cleanup = () => { + mount.remove(); + fetchAndRender(); + window.removeEventListener("afterprint", cleanup); + }; + window.addEventListener("afterprint", cleanup); + + window.print(); + }, 200); + }); + + async function fetchFullForExport() { + const params = buildParams({ paginate: false }); + params.delete("page"); + params.delete("pageSize"); + const res = await fetch(`/InventoryReports/GetMRSReport?${params}`); + if (!res.ok) throw new Error(`HTTP ${res.status}`); + const json = await res.json(); + return json.data ?? json; + } // ── CSV export (MRS schema) ── - function exportCsv() { + async function exportCsv() { + const data = await fetchFullForExport(); + if (!lastData) return; const rows = lastData.rows ?? []; const byCond = lastData.byCondition ?? []; @@ -626,7 +681,7 @@ } // ── Excel export (MRS schema, HTML-table .xls) ── - function exportExcel() { + async function exportExcel() { if (!lastData) return; const rows = lastData.rows ?? []; const byCond = lastData.byCondition ?? []; diff --git a/CPRNIMS.WebApps/Views/Components/Inventory/TabPage/Reports/RISReport.cshtml b/CPRNIMS.WebApps/Views/Components/Inventory/TabPage/Reports/RISReport.cshtml index 8c40e3c..fbd0583 100644 --- a/CPRNIMS.WebApps/Views/Components/Inventory/TabPage/Reports/RISReport.cshtml +++ b/CPRNIMS.WebApps/Views/Components/Inventory/TabPage/Reports/RISReport.cshtml @@ -7,41 +7,52 @@ } @@media print { - body { - padding: 0; - margin: 0; - background: #fff; - } - - body * { - visibility: hidden; - } - - #ris-rpt-page, #ris-rpt-page * { - visibility: visible; - } - - #ris-rpt-page { - position: absolute; - left: 0; - top: 0; - width: 100%; - box-shadow: none !important; - border: none !important; - border-radius: 0 !important; - } - - .no-print { + body > *:not(#print-mount) { display: none !important; } - .rpt-table tr { - page-break-inside: avoid; + #print-mount { + display: block !important; + position: static !important; + width: 100%; } + #print-mount .ris-page { + box-shadow: none !important; + border: none !important; + border-radius: 0 !important; + overflow: visible !important; + } + + .rpt-section { + page-break-inside: auto; + } + + .rpt-table { + table-layout: fixed; + width: 100%; + font-size: 9px; + page-break-inside: auto; + } + + .rpt-table th, .rpt-table td { + padding: 5px 6px; + white-space: normal; + word-break: break-word; + } + + .rpt-table tr { + page-break-inside: avoid; + } + thead { display: table-header-group; } + + @@page { + size: A4 landscape; + margin: 5mm; + } } .rpt-page { @@ -197,7 +208,7 @@ .kpi-strip { display: grid; - grid-template-columns: repeat(4, 1fr); + grid-template-columns: repeat(3, 1fr); gap: 0; border-bottom: 1px solid #d6eaec; } @@ -434,14 +445,61 @@ } csvBtn.addEventListener("click", exportCsv); genBtn.addEventListener("click", fetchAndRender); + + + + const printBtn = document.getElementById("inv-rpt-print"); + printBtn?.addEventListener("click", async () => { + // 1. get the full dataset (all 145 rows) + const full = await fetchFullForExport(); + renderReport(full); + + // wait for the DOM to paint the re-rendered report + setTimeout(() => { + const report = document.getElementById("ris-rpt-page"); + if (!report) { window.print(); return; } + + // 2. clone the report to a body-level mount + let mount = document.getElementById("print-mount"); + if (!mount) { + mount = document.createElement("div"); + mount.id = "print-mount"; + document.body.appendChild(mount); + } + mount.innerHTML = ""; + mount.appendChild(report.cloneNode(true)); + + // 3. print, then clean up + restore paged view + const cleanup = () => { + mount.remove(); + fetchAndRender(); + window.removeEventListener("afterprint", cleanup); + }; + window.addEventListener("afterprint", cleanup); + + window.print(); + }, 200); + }); + + async function fetchFullForExport() { + const params = buildParams({ paginate: false }); + params.delete("page"); + params.delete("pageSize"); + const res = await fetch(`/InventoryReports/GetRISReport?${params}`); + if (!res.ok) throw new Error(`HTTP ${res.status}`); + const json = await res.json(); + return json.data ?? json; + } + // ── CSV export (client-side, no backend needed) ── - function exportCsv() { + async function exportCsv() { + const data = await fetchFullForExport(); if (!lastData) return; const rows = lastData.rows ?? []; const byDisc = lastData.byDiscipline ?? []; const topRecv = lastData.topRecipients ?? []; const summary = lastData.summary ?? {}; - const headers = ["RIS No.","Date","Item","Item No.","Discipline","Issued To","Qty Issued","Qty Returned","Net Out","Status"]; + const headers = ["RIS No.","Date","Item","Item No.","Trade","Issued To","Qty Issued","Qty Returned","Net Out","Status"]; const csvLines = [ `Return Issuance Slip Report`, @@ -456,8 +514,8 @@ ].map(csvCell).join(",")), ["Total","","","","","", summary.totalQtyIssued ?? 0, summary.totalQtyReturned ?? 0, summary.totalNetIssued ?? 0, ""].map(csvCell).join(","), ``, - `Issuance by Discipline`, - ["Discipline","Slips"].map(csvCell).join(","), + `Issuance by Trade`, + ["Trade","Slips"].map(csvCell).join(","), ...byDisc.map(d => [d.disciplineName, d.count].map(csvCell).join(",")), ``, `Top Recipients`, @@ -501,7 +559,7 @@
${_esc(data.companyName ?? "")}
Return Issuance Slip Report
- Period: ${_fmtDate(data.dateFrom)} – ${_fmtDate(data.dateTo)} · All departments · All disciplines + Period: ${_fmtDate(data.dateFrom)} – ${_fmtDate(data.dateTo)} · All departments · All trades
@@ -544,7 +602,7 @@ RIS No.DateItemItem No. - DisciplineIssued To + TradeProject Name Qty IssuedQty Returned Net OutStatus @@ -576,7 +634,7 @@
-
Issuance by discipline
+
Issuance by trade
${byDisc.map((d, i) => `
@@ -628,14 +686,14 @@ } // ── Excel export (client-side, HTML-table .xls — Excel opens natively) ── - function exportExcel() { + async function exportExcel() { if (!lastData) return; const rows = lastData.rows ?? []; const byDisc = lastData.byDiscipline ?? []; const topRecv = lastData.topRecipients ?? []; const summary = lastData.summary ?? {}; - const headerCells = ["RIS No.","Date","Item","Item No.","Discipline","Issued To","Qty Issued","Qty Returned","Net Out","Status"] + const headerCells = ["RIS No.","Date","Item","Item No.","Trade","Issued To","Qty Issued","Qty Returned","Net Out","Status"] .map(h => `${_esc(h)}`).join(""); const bodyRows = rows.map(r => ` @@ -677,8 +735,8 @@
- - + + ${discRows}
Issuance by Discipline
DisciplineSlips
Issuance by Trade
TradeSlips
diff --git a/CPRNIMS.WebApps/Views/Shared/PagesView/Inventory/_InventoryReportHelper.cshtml b/CPRNIMS.WebApps/Views/Shared/PagesView/Inventory/_InventoryReportHelper.cshtml index 300d770..3e6ecac 100644 --- a/CPRNIMS.WebApps/Views/Shared/PagesView/Inventory/_InventoryReportHelper.cshtml +++ b/CPRNIMS.WebApps/Views/Shared/PagesView/Inventory/_InventoryReportHelper.cshtml @@ -7,10 +7,27 @@
+
+
+
+ + All Department +
+ +
+
+ +
+
+
+
-