CRUD for RIS, MRS, Inventory reports are working properly
This commit is contained in:
parent
440cdfcdb7
commit
3c0be5eae2
@ -1,4 +1,5 @@
|
|||||||
using CPRNIMS.Infrastructure.Dto.Inventory.Reports;
|
using CPRNIMS.Infrastructure.Dto.Inventory.Reports;
|
||||||
|
using CPRNIMS.Infrastructure.Dto.Inventory.Request;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@ -9,8 +10,8 @@ namespace CPRNIMS.Domain.Contracts.Inventory
|
|||||||
{
|
{
|
||||||
public interface IInventoryReports
|
public interface IInventoryReports
|
||||||
{
|
{
|
||||||
Task<RISReportDto> GetRISReportAsync(DateTime dateFrom, DateTime dateTo, CancellationToken ct, int? departmentId = null, string? userName = "");
|
Task<RISReportDto> GetRISReportAsync(InventoryReportsRequest request, string userName, int? departmentId, CancellationToken ct);
|
||||||
Task<MRSReportDto> GetMRSReportAsync(DateTime dateFrom, DateTime dateTo, CancellationToken ct, int? departmentId = null, string? userName = "");
|
Task<MRSReportDto> GetMRSReportAsync(InventoryReportsRequest request, string userName, int? departmentId, CancellationToken ct);
|
||||||
Task<InventoryReportDto> GetInventoryReportAsync(DateTime dateFrom, DateTime dateTo, CancellationToken ct, int? departmentId = null, string? userName = "");
|
Task<InventoryReportDto> GetInventoryReportAsync(InventoryReportsRequest request,string userName, int? departmentId, CancellationToken ct);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +1,11 @@
|
|||||||
using CPRNIMS.Domain.Contracts.Inventory;
|
using CPRNIMS.Domain.Contracts.Inventory;
|
||||||
using CPRNIMS.Infrastructure.Database;
|
using CPRNIMS.Infrastructure.Database;
|
||||||
using CPRNIMS.Infrastructure.Dto.Inventory.Reports;
|
using CPRNIMS.Infrastructure.Dto.Inventory.Reports;
|
||||||
|
using CPRNIMS.Infrastructure.Dto.Inventory.Request;
|
||||||
using Microsoft.Data.SqlClient;
|
using Microsoft.Data.SqlClient;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using System.Drawing.Printing;
|
||||||
|
using static System.Runtime.InteropServices.JavaScript.JSType;
|
||||||
|
|
||||||
namespace CPRNIMS.Domain.Services.Inventory
|
namespace CPRNIMS.Domain.Services.Inventory
|
||||||
{
|
{
|
||||||
@ -11,22 +14,23 @@ namespace CPRNIMS.Domain.Services.Inventory
|
|||||||
private readonly NonInventoryDbContext _db;
|
private readonly NonInventoryDbContext _db;
|
||||||
public InventoryReports(NonInventoryDbContext db) => _db = db;
|
public InventoryReports(NonInventoryDbContext db) => _db = db;
|
||||||
|
|
||||||
public async Task<RISReportDto> GetRISReportAsync(DateTime dateFrom, DateTime dateTo, CancellationToken ct,
|
public async Task<RISReportDto> GetRISReportAsync(InventoryReportsRequest request, string userName, int? departmentId, CancellationToken ct)
|
||||||
int? departmentId = null, string? userName = "")
|
|
||||||
{
|
{
|
||||||
var endDate = dateTo.Date.AddDays(1);
|
var endDate = request.DateTo.Date.AddDays(1);
|
||||||
|
|
||||||
var allowedAllDeptUsers = new[] { "LSKRISUR24", "LSCYNDIZ25", "LSJONTAN25", "LHRIOCAS24" };
|
var allowedAllDeptUsers = new[] { "LSKRISUR24", "LSCYNDIZ25", "LSJONTAN25", "LHRIOCAS24" };
|
||||||
bool seeAllDepartments = !string.IsNullOrWhiteSpace(userName)
|
bool seeAllDepartments = !string.IsNullOrWhiteSpace(userName)
|
||||||
&& allowedAllDeptUsers.Contains(userName, StringComparer.OrdinalIgnoreCase);
|
&& allowedAllDeptUsers.Contains(userName, StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
var dateToInclusive = dateTo.AddDays(1);
|
var dateToInclusive = request.DateTo.AddDays(1);
|
||||||
|
|
||||||
var query = _db.RIS
|
var query = _db.RIS
|
||||||
.Include(r => r.Discipline)
|
.Include(r => r.Discipline)
|
||||||
.Include(r => r.Inventory)
|
.Include(r => r.Inventory)
|
||||||
.Include(r => r.MaterialReturns)
|
.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)
|
if (departmentId.HasValue && !seeAllDepartments)
|
||||||
{
|
{
|
||||||
@ -94,8 +98,8 @@ namespace CPRNIMS.Domain.Services.Inventory
|
|||||||
return new RISReportDto
|
return new RISReportDto
|
||||||
{
|
{
|
||||||
ReportNo = $"RPT-RIS-{DateTime.Now:yyyyMM}-{Random.Shared.Next(1, 999):D3}",
|
ReportNo = $"RPT-RIS-{DateTime.Now:yyyyMM}-{Random.Shared.Next(1, 999):D3}",
|
||||||
DateFrom = dateFrom,
|
DateFrom = request.DateFrom,
|
||||||
DateTo = dateTo,
|
DateTo = request.DateTo,
|
||||||
Summary = summary,
|
Summary = summary,
|
||||||
Rows = rows,
|
Rows = rows,
|
||||||
ByDiscipline = byDiscipline,
|
ByDiscipline = byDiscipline,
|
||||||
@ -103,10 +107,9 @@ namespace CPRNIMS.Domain.Services.Inventory
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<MRSReportDto> GetMRSReportAsync(DateTime dateFrom, DateTime dateTo, CancellationToken ct,
|
public async Task<MRSReportDto> GetMRSReportAsync(InventoryReportsRequest request, string userName, int? departmentId, CancellationToken ct)
|
||||||
int? departmentId = null, string? userName = "")
|
|
||||||
{
|
{
|
||||||
var endDate = dateTo.Date.AddDays(1);
|
var endDate = request.DateTo.Date.AddDays(1);
|
||||||
|
|
||||||
var allowedAllDeptUsers = new[] { "LSKRISUR24", "LSCYNDIZ25", "LSJONTAN25", "LHRIOCAS24" };
|
var allowedAllDeptUsers = new[] { "LSKRISUR24", "LSCYNDIZ25", "LSJONTAN25", "LHRIOCAS24" };
|
||||||
bool seeAllDepartments = !string.IsNullOrWhiteSpace(userName)
|
bool seeAllDepartments = !string.IsNullOrWhiteSpace(userName)
|
||||||
@ -116,7 +119,9 @@ namespace CPRNIMS.Domain.Services.Inventory
|
|||||||
.Include(m => m.RIS)
|
.Include(m => m.RIS)
|
||||||
.Include(m => m.Inventory)
|
.Include(m => m.Inventory)
|
||||||
.ThenInclude(i => i.User)
|
.ThenInclude(i => i.User)
|
||||||
.Where(m => m.CreatedDate >= dateFrom &&
|
.Skip((request.Page - 1) * request.PageSize)
|
||||||
|
.Take(request.PageSize)
|
||||||
|
.Where(m => m.CreatedDate >= request.DateFrom &&
|
||||||
m.CreatedDate < endDate);
|
m.CreatedDate < endDate);
|
||||||
|
|
||||||
if (departmentId.HasValue && !seeAllDepartments)
|
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)
|
// Total RIS qty issued in the same period (for the comparison panel)
|
||||||
var totalRISQty = await _db.RIS
|
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)
|
&& r.Status != 2)
|
||||||
.SumAsync(r => (int?)r.QtyIssued, ct) ?? 0;
|
.SumAsync(r => (int?)r.QtyIssued, ct) ?? 0;
|
||||||
|
|
||||||
@ -177,49 +182,62 @@ namespace CPRNIMS.Domain.Services.Inventory
|
|||||||
return new MRSReportDto
|
return new MRSReportDto
|
||||||
{
|
{
|
||||||
ReportNo = $"RPT-MRS-{DateTime.Now:yyyyMM}-{Random.Shared.Next(1, 999):D3}",
|
ReportNo = $"RPT-MRS-{DateTime.Now:yyyyMM}-{Random.Shared.Next(1, 999):D3}",
|
||||||
DateFrom = dateFrom,
|
DateFrom = request.DateFrom,
|
||||||
DateTo = dateTo,
|
DateTo = request.DateTo,
|
||||||
Summary = summary,
|
Summary = summary,
|
||||||
Rows = rows,
|
Rows = rows,
|
||||||
ByCondition = byCondition
|
ByCondition = byCondition
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<InventoryReportDto> GetInventoryReportAsync(DateTime dateFrom, DateTime dateTo, CancellationToken ct,
|
public async Task<InventoryReportDto> GetInventoryReportAsync(InventoryReportsRequest request, string userName, int? departmentId, CancellationToken ct)
|
||||||
int? departmentId = null, string? userName = "")
|
|
||||||
{
|
{
|
||||||
var allowedAllDeptUsers = new[] { "LSKRISUR24", "LSCYNDIZ25", "LSJONTAN25", "LHRIOCAS24" };
|
var allowedAllDeptUsers = new[] { "LSKRISUR24", "LSCYNDIZ25", "LSJONTAN25", "LHRIOCAS24" };
|
||||||
bool seeAllDepartments = !string.IsNullOrWhiteSpace(userName)
|
bool seeAllDepartments = !string.IsNullOrWhiteSpace(userName)
|
||||||
&& allowedAllDeptUsers.Contains(userName, StringComparer.OrdinalIgnoreCase);
|
&& allowedAllDeptUsers.Contains(userName, StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
var endDate = request.DateTo.Date.AddDays(1);
|
||||||
|
|
||||||
var dto = new InventoryReportDto
|
var dto = new InventoryReportDto
|
||||||
{
|
{
|
||||||
ReportNo = $"RPT-INV-{DateTime.Now:yyyyMM}-{Random.Shared.Next(1, 999):D3}",
|
ReportNo = $"RPT-INV-{DateTime.Now:yyyyMM}-{Random.Shared.Next(1, 999):D3}",
|
||||||
AsOf = dateTo.Date,
|
AsOf = endDate,
|
||||||
Rows = new List<InventoryReportRow>(),
|
Rows = new List<InventoryReportRow>(),
|
||||||
ByCategory = new List<CategoryStockLevel>(),
|
ByCategory = new List<CategoryStockLevel>(),
|
||||||
Alerts = new List<InventoryAlert>(),
|
Alerts = new List<InventoryAlert>(),
|
||||||
Summary = new InventoryReportSummary()
|
Summary = new InventoryReportSummary(),
|
||||||
|
Departments = new List<string>(),
|
||||||
|
Page = request.Page,
|
||||||
|
PageSize = request.PageSize
|
||||||
};
|
};
|
||||||
|
|
||||||
var conn = _db.Database.GetDbConnection();
|
var conn = _db.Database.GetDbConnection();
|
||||||
await using var cmd = conn.CreateCommand();
|
await using var cmd = conn.CreateCommand();
|
||||||
cmd.CommandText = "dbo.GetInventoryReport";
|
cmd.CommandText = "dbo.GetInventoryReport";
|
||||||
cmd.CommandType = System.Data.CommandType.StoredProcedure;
|
cmd.CommandType = System.Data.CommandType.StoredProcedure;
|
||||||
|
cmd.Parameters.Add(new SqlParameter("@DateFrom", (object?)request.DateFrom ?? DBNull.Value));
|
||||||
cmd.Parameters.Add(new SqlParameter("@DateFrom", dateFrom));
|
cmd.Parameters.Add(new SqlParameter("@DateTo", (object?)request.DateTo ?? DBNull.Value));
|
||||||
cmd.Parameters.Add(new SqlParameter("@DateTo", dateTo));
|
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("@DepartmentId", (object?)departmentId ?? DBNull.Value));
|
||||||
cmd.Parameters.Add(new SqlParameter("@SeeAllDepartments", seeAllDepartments ? 1 : 0));
|
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)
|
bool openedHere = conn.State != System.Data.ConnectionState.Open;
|
||||||
await conn.OpenAsync(ct);
|
if (openedHere) await conn.OpenAsync(ct);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await using var reader = await cmd.ExecuteReaderAsync(ct);
|
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
|
// 1: detail rows
|
||||||
|
if (await reader.NextResultAsync(ct))
|
||||||
|
{
|
||||||
while (await reader.ReadAsync(ct))
|
while (await reader.ReadAsync(ct))
|
||||||
{
|
{
|
||||||
dto.Rows.Add(new InventoryReportRow
|
dto.Rows.Add(new InventoryReportRow
|
||||||
@ -227,8 +245,9 @@ namespace CPRNIMS.Domain.Services.Inventory
|
|||||||
ItemName = reader.GetString(reader.GetOrdinal("ItemName")),
|
ItemName = reader.GetString(reader.GetOrdinal("ItemName")),
|
||||||
ItemNo = reader.GetInt64(reader.GetOrdinal("ItemNo")),
|
ItemNo = reader.GetInt64(reader.GetOrdinal("ItemNo")),
|
||||||
ItemCategoryName = reader.GetString(reader.GetOrdinal("ItemCategoryName")),
|
ItemCategoryName = reader.GetString(reader.GetOrdinal("ItemCategoryName")),
|
||||||
LotNo = reader.IsDBNull(reader.GetOrdinal("LotNo"))
|
LotNo = reader.IsDBNull(reader.GetOrdinal("LotNo")) ? null : reader.GetString(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")),
|
QtyIn = reader.GetDecimal(reader.GetOrdinal("QtyIn")),
|
||||||
QtyOut = reader.GetDecimal(reader.GetOrdinal("QtyOut")),
|
QtyOut = reader.GetDecimal(reader.GetOrdinal("QtyOut")),
|
||||||
QtyOnHand = reader.GetDecimal(reader.GetOrdinal("QtyOnHand")),
|
QtyOnHand = reader.GetDecimal(reader.GetOrdinal("QtyOnHand")),
|
||||||
@ -236,6 +255,7 @@ namespace CPRNIMS.Domain.Services.Inventory
|
|||||||
StockPct = reader.GetInt32(reader.GetOrdinal("StockPct"))
|
StockPct = reader.GetInt32(reader.GetOrdinal("StockPct"))
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 2: summary
|
// 2: summary
|
||||||
if (await reader.NextResultAsync(ct) && await reader.ReadAsync(ct))
|
if (await reader.NextResultAsync(ct) && await reader.ReadAsync(ct))
|
||||||
@ -254,30 +274,26 @@ namespace CPRNIMS.Domain.Services.Inventory
|
|||||||
|
|
||||||
// 3: by category
|
// 3: by category
|
||||||
if (await reader.NextResultAsync(ct))
|
if (await reader.NextResultAsync(ct))
|
||||||
{
|
|
||||||
while (await reader.ReadAsync(ct))
|
while (await reader.ReadAsync(ct))
|
||||||
{
|
|
||||||
dto.ByCategory.Add(new CategoryStockLevel
|
dto.ByCategory.Add(new CategoryStockLevel
|
||||||
{
|
{
|
||||||
CategoryName = reader.GetString(reader.GetOrdinal("CategoryName")),
|
CategoryName = reader.GetString(reader.GetOrdinal("CategoryName")),
|
||||||
AvgStockPct = reader.GetInt32(reader.GetOrdinal("AvgStockPct"))
|
AvgStockPct = reader.GetInt32(reader.GetOrdinal("AvgStockPct"))
|
||||||
});
|
});
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4: alerts
|
// 4: alerts
|
||||||
if (await reader.NextResultAsync(ct))
|
if (await reader.NextResultAsync(ct))
|
||||||
{
|
|
||||||
while (await reader.ReadAsync(ct))
|
while (await reader.ReadAsync(ct))
|
||||||
{
|
|
||||||
dto.Alerts.Add(new InventoryAlert
|
dto.Alerts.Add(new InventoryAlert
|
||||||
{
|
{
|
||||||
ItemName = reader.GetString(reader.GetOrdinal("ItemName")),
|
ItemName = reader.GetString(reader.GetOrdinal("ItemName")),
|
||||||
QtyOnHand = reader.GetDecimal(reader.GetOrdinal("QtyOnHand")),
|
QtyOnHand = reader.GetDecimal(reader.GetOrdinal("QtyOnHand")),
|
||||||
Severity = reader.GetString(reader.GetOrdinal("Severity"))
|
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
|
finally
|
||||||
{
|
{
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
using CPRNIMS.Infrastructure.Dto.Inventory.Reports;
|
using CPRNIMS.Infrastructure.Dto.Inventory.Reports;
|
||||||
using CPRNIMS.Infrastructure.Dto.Inventory.Reports.Response;
|
using CPRNIMS.Infrastructure.Dto.Inventory.Request;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@ -11,8 +11,8 @@ namespace CPRNIMS.Domain.UIContracts.Inventory
|
|||||||
public interface IInventoryReports
|
public interface IInventoryReports
|
||||||
{
|
{
|
||||||
|
|
||||||
Task<RISReportDto> GetRISReportAsync(DateTime dateFrom, DateTime dateTo, CancellationToken ct);
|
Task<RISReportDto> GetRISReportAsync(InventoryReportsRequest request, CancellationToken ct);
|
||||||
Task<MRSReportDto> GetMRSReportAsync(DateTime dateFrom, DateTime dateTo, CancellationToken ct);
|
Task<MRSReportDto> GetMRSReportAsync(InventoryReportsRequest request, CancellationToken ct);
|
||||||
Task<InventoryReportDto> GetInventoryReportAsync(DateTime dateFrom, DateTime dateTo, CancellationToken ct);
|
Task<InventoryReportDto> GetInventoryReportAsync(InventoryReportsRequest request, CancellationToken ct);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +1,11 @@
|
|||||||
using CPRNIMS.Domain.UIContracts.Common;
|
using Azure.Core;
|
||||||
|
using CPRNIMS.Domain.UIContracts.Common;
|
||||||
using CPRNIMS.Domain.UIContracts.Inventory;
|
using CPRNIMS.Domain.UIContracts.Inventory;
|
||||||
using CPRNIMS.Infrastructure.Dto.Inventory.Reports;
|
using CPRNIMS.Infrastructure.Dto.Inventory.Reports;
|
||||||
using CPRNIMS.Infrastructure.Dto.Inventory.Reports.Response;
|
using CPRNIMS.Infrastructure.Dto.Inventory.Reports.Response;
|
||||||
|
using CPRNIMS.Infrastructure.Dto.Inventory.Request;
|
||||||
using CPRNIMS.Infrastructure.Helper;
|
using CPRNIMS.Infrastructure.Helper;
|
||||||
|
using Microsoft.AspNetCore.WebUtilities;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
@ -24,7 +27,7 @@ namespace CPRNIMS.Domain.UIServices.Inventory
|
|||||||
{
|
{
|
||||||
PropertyNameCaseInsensitive = true
|
PropertyNameCaseInsensitive = true
|
||||||
};
|
};
|
||||||
public async Task<InventoryReportDto> GetInventoryReportAsync(DateTime dateFrom, DateTime dateTo, CancellationToken ct)
|
public async Task<InventoryReportDto> GetInventoryReportAsync(InventoryReportsRequest request, CancellationToken ct)
|
||||||
{
|
{
|
||||||
var token = await _tokenHelper.GetValidTokenAsync();
|
var token = await _tokenHelper.GetValidTokenAsync();
|
||||||
if (string.IsNullOrEmpty(token))
|
if (string.IsNullOrEmpty(token))
|
||||||
@ -33,11 +36,20 @@ namespace CPRNIMS.Domain.UIServices.Inventory
|
|||||||
var baseEndpoint = _configuration["LLI:NonInvent:InventoryMgmt:GetInventoryReport"]
|
var baseEndpoint = _configuration["LLI:NonInvent:InventoryMgmt:GetInventoryReport"]
|
||||||
?? throw new InvalidOperationException("GetInventoryReport endpoint is not configured.");
|
?? throw new InvalidOperationException("GetInventoryReport endpoint is not configured.");
|
||||||
|
|
||||||
var qs = new StringBuilder(baseEndpoint).Append('?');
|
var qs = new Dictionary<string, string?>
|
||||||
qs.Append($"dateFrom={dateFrom:yyyy-MM-dd}&dateTo={dateTo:yyyy-MM-dd}");
|
{
|
||||||
|
["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 url = QueryHelpers.AddQueryString(baseEndpoint, qs);
|
||||||
var response = await http.GetAsync(qs.ToString(), ct);
|
|
||||||
|
using var httpClient = _apiConfigurationService.CreateHttpClientWithDefaultHeaders(token);
|
||||||
|
var response = await httpClient.GetAsync(url, ct);
|
||||||
var json = await response.Content.ReadAsStringAsync(ct);
|
var json = await response.Content.ReadAsStringAsync(ct);
|
||||||
|
|
||||||
if (!response.IsSuccessStatusCode)
|
if (!response.IsSuccessStatusCode)
|
||||||
@ -47,7 +59,7 @@ namespace CPRNIMS.Domain.UIServices.Inventory
|
|||||||
return result ?? new InventoryReportDto();
|
return result ?? new InventoryReportDto();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<MRSReportDto> GetMRSReportAsync(DateTime dateFrom, DateTime dateTo, CancellationToken ct)
|
public async Task<MRSReportDto> GetMRSReportAsync(InventoryReportsRequest request, CancellationToken ct)
|
||||||
{
|
{
|
||||||
var token = await _tokenHelper.GetValidTokenAsync();
|
var token = await _tokenHelper.GetValidTokenAsync();
|
||||||
if (string.IsNullOrEmpty(token))
|
if (string.IsNullOrEmpty(token))
|
||||||
@ -56,11 +68,20 @@ namespace CPRNIMS.Domain.UIServices.Inventory
|
|||||||
var baseEndpoint = _configuration["LLI:NonInvent:InventoryMgmt:GetMRSReport"]
|
var baseEndpoint = _configuration["LLI:NonInvent:InventoryMgmt:GetMRSReport"]
|
||||||
?? throw new InvalidOperationException("GetMRS endpoint is not configured.");
|
?? throw new InvalidOperationException("GetMRS endpoint is not configured.");
|
||||||
|
|
||||||
var qs = new StringBuilder(baseEndpoint).Append('?');
|
var qs = new Dictionary<string, string?>
|
||||||
qs.Append($"dateFrom={dateFrom:yyyy-MM-dd}&dateTo={dateTo:yyyy-MM-dd}");
|
{
|
||||||
|
["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 url = QueryHelpers.AddQueryString(baseEndpoint, qs);
|
||||||
var response = await http.GetAsync(qs.ToString(), ct);
|
|
||||||
|
using var httpClient = _apiConfigurationService.CreateHttpClientWithDefaultHeaders(token);
|
||||||
|
var response = await httpClient.GetAsync(url, ct);
|
||||||
var json = await response.Content.ReadAsStringAsync(ct);
|
var json = await response.Content.ReadAsStringAsync(ct);
|
||||||
|
|
||||||
if (!response.IsSuccessStatusCode)
|
if (!response.IsSuccessStatusCode)
|
||||||
@ -70,7 +91,7 @@ namespace CPRNIMS.Domain.UIServices.Inventory
|
|||||||
return result ?? new MRSReportDto();
|
return result ?? new MRSReportDto();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<RISReportDto> GetRISReportAsync(DateTime dateFrom, DateTime dateTo, CancellationToken ct)
|
public async Task<RISReportDto> GetRISReportAsync(InventoryReportsRequest request, CancellationToken ct)
|
||||||
{
|
{
|
||||||
var token = await _tokenHelper.GetValidTokenAsync();
|
var token = await _tokenHelper.GetValidTokenAsync();
|
||||||
if (string.IsNullOrEmpty(token))
|
if (string.IsNullOrEmpty(token))
|
||||||
@ -79,11 +100,20 @@ namespace CPRNIMS.Domain.UIServices.Inventory
|
|||||||
var baseEndpoint = _configuration["LLI:NonInvent:InventoryMgmt:GetRISReport"]
|
var baseEndpoint = _configuration["LLI:NonInvent:InventoryMgmt:GetRISReport"]
|
||||||
?? throw new InvalidOperationException("GetMRS endpoint is not configured.");
|
?? throw new InvalidOperationException("GetMRS endpoint is not configured.");
|
||||||
|
|
||||||
var qs = new StringBuilder(baseEndpoint).Append('?');
|
var qs = new Dictionary<string, string?>
|
||||||
qs.Append($"dateFrom={dateFrom:yyyy-MM-dd}&dateTo={dateTo:yyyy-MM-dd}");
|
{
|
||||||
|
["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 url = QueryHelpers.AddQueryString(baseEndpoint, qs);
|
||||||
var response = await http.GetAsync(qs.ToString(), ct);
|
|
||||||
|
using var httpClient = _apiConfigurationService.CreateHttpClientWithDefaultHeaders(token);
|
||||||
|
var response = await httpClient.GetAsync(url, ct);
|
||||||
var json = await response.Content.ReadAsStringAsync(ct);
|
var json = await response.Content.ReadAsStringAsync(ct);
|
||||||
|
|
||||||
if (!response.IsSuccessStatusCode)
|
if (!response.IsSuccessStatusCode)
|
||||||
|
|||||||
@ -108,7 +108,11 @@ namespace CPRNIMS.Infrastructure.Dto.Inventory.Reports
|
|||||||
public string PreparedBy { get; set; } = "Finance Department";
|
public string PreparedBy { get; set; } = "Finance Department";
|
||||||
public string ReportNo { get; set; } = string.Empty;
|
public string ReportNo { get; set; } = string.Empty;
|
||||||
public DateTime AsOf { get; set; }
|
public DateTime AsOf { get; set; }
|
||||||
|
public List<string> 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 InventoryReportSummary Summary { get; set; } = new();
|
||||||
public List<InventoryReportRow> Rows { get; set; } = [];
|
public List<InventoryReportRow> Rows { get; set; } = [];
|
||||||
public List<CategoryStockLevel> ByCategory { get; set; } = [];
|
public List<CategoryStockLevel> ByCategory { get; set; } = [];
|
||||||
@ -137,6 +141,8 @@ namespace CPRNIMS.Infrastructure.Dto.Inventory.Reports
|
|||||||
public decimal QtyOnHand { get; set; }
|
public decimal QtyOnHand { get; set; }
|
||||||
public decimal UnitPrice { get; set; }
|
public decimal UnitPrice { get; set; }
|
||||||
public int StockPct { get; set; }
|
public int StockPct { get; set; }
|
||||||
|
public string? Department { get; set; }
|
||||||
|
public string? CurrencyCode { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class CategoryStockLevel
|
public class CategoryStockLevel
|
||||||
|
|||||||
@ -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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,6 +1,8 @@
|
|||||||
using CPRNIMS.Domain.Contracts.Inventory;
|
using Azure.Core;
|
||||||
|
using CPRNIMS.Domain.Contracts.Inventory;
|
||||||
using CPRNIMS.Domain.Contracts.Reports;
|
using CPRNIMS.Domain.Contracts.Reports;
|
||||||
using CPRNIMS.Infrastructure.Dto.Inventory.Reports;
|
using CPRNIMS.Infrastructure.Dto.Inventory.Reports;
|
||||||
|
using CPRNIMS.Infrastructure.Dto.Inventory.Request;
|
||||||
using CPRNIMS.WebApi.Security;
|
using CPRNIMS.WebApi.Security;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
@ -19,35 +21,35 @@ namespace CPRNIMS.WebApi.Controllers.Inventory
|
|||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("GetRISReport")]
|
[HttpGet("GetRISReport")]
|
||||||
public async Task<IActionResult> GetRISReport(DateTime dateFrom, DateTime dateTo, CancellationToken ct)
|
public async Task<IActionResult> GetRISReport([FromQuery] InventoryReportsRequest request, CancellationToken ct)
|
||||||
{
|
{
|
||||||
var currentUser = User.ToUserClaims();
|
var currentUser = User.ToUserClaims();
|
||||||
if (currentUser == null)
|
if (currentUser == null)
|
||||||
return BadRequest();
|
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);
|
return Ok(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("GetMRSReport")]
|
[HttpGet("GetMRSReport")]
|
||||||
public async Task<IActionResult> GetMRSReport(DateTime dateFrom, DateTime dateTo, CancellationToken ct)
|
public async Task<IActionResult> GetMRSReport([FromQuery] InventoryReportsRequest request, CancellationToken ct)
|
||||||
{
|
{
|
||||||
var currentUser = User.ToUserClaims();
|
var currentUser = User.ToUserClaims();
|
||||||
if (currentUser == null)
|
if (currentUser == null)
|
||||||
return BadRequest();
|
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);
|
return Ok(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("GetInventoryReport")]
|
[HttpGet("GetInventoryReport")]
|
||||||
public async Task<IActionResult> GetInventoryReport(DateTime dateFrom, DateTime dateTo, CancellationToken ct)
|
public async Task<IActionResult> GetInventoryReport([FromQuery] InventoryReportsRequest request, CancellationToken ct)
|
||||||
{
|
{
|
||||||
var currentUser = User.ToUserClaims();
|
var currentUser = User.ToUserClaims();
|
||||||
if (currentUser == null)
|
if (currentUser == null)
|
||||||
return BadRequest();
|
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);
|
return Ok(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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 Microsoft.AspNetCore.Mvc;
|
||||||
|
using System.Drawing.Printing;
|
||||||
|
|
||||||
namespace CPRNIMS.WebApps.Controllers.Inventory
|
namespace CPRNIMS.WebApps.Controllers.Inventory
|
||||||
{
|
{
|
||||||
public class InventoryReportsController : Controller
|
public class InventoryReportsController : BaseMethod
|
||||||
{
|
{
|
||||||
private readonly IInventoryReports _reports;
|
private readonly IInventoryReports _reports;
|
||||||
public InventoryReportsController(IInventoryReports reports)
|
public InventoryReportsController(ErrorLogHelper errorMessageService,
|
||||||
{
|
IWebHostEnvironment webHostEnvironment, TokenHelper tokenHelper, IInventoryReports reports, IAccount account)
|
||||||
_reports=reports;
|
: base(errorMessageService, webHostEnvironment, tokenHelper, account) => _reports = reports;
|
||||||
}
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
public async Task<IActionResult> GetInventoryReport(DateTime dateFrom, DateTime dateTo, CancellationToken ct)
|
public async Task<IActionResult> 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);
|
var dto = new InventoryReportsRequest
|
||||||
|
{
|
||||||
|
DateFrom = dateFrom,
|
||||||
|
DateTo = dateTo,
|
||||||
|
Department = department,
|
||||||
|
Page = page,
|
||||||
|
PageSize = pageSize,
|
||||||
|
Paginate = paginate
|
||||||
|
};
|
||||||
|
GetUser();
|
||||||
|
var response = await _reports.GetInventoryReportAsync(dto,ct);
|
||||||
return GetResponse(response);
|
return GetResponse(response);
|
||||||
}
|
}
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
public async Task<IActionResult> GetRISReport(DateTime dateFrom, DateTime dateTo, CancellationToken ct)
|
public async Task<IActionResult> GetRISReport(
|
||||||
|
DateTime dateFrom, DateTime dateTo, string? department, int page = 1, int pageSize = 10, bool paginate = true, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
var response = await _reports.GetRISReportAsync(dateFrom, dateTo, ct);
|
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);
|
return GetResponse(response);
|
||||||
}
|
}
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
public async Task<IActionResult> GetMRSReport(DateTime dateFrom, DateTime dateTo, CancellationToken ct)
|
public async Task<IActionResult> GetMRSReport(
|
||||||
|
DateTime dateFrom, DateTime dateTo, string? department, int page = 1, int pageSize = 10, bool paginate = true, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
var response = await _reports.GetMRSReportAsync(dateFrom, dateTo, ct);
|
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);
|
return GetResponse(response);
|
||||||
}
|
}
|
||||||
protected IActionResult GetResponse<T>(T response)
|
|
||||||
{
|
|
||||||
return Json(new
|
|
||||||
{
|
|
||||||
success = response != null,
|
|
||||||
data = response ?? Activator.CreateInstance<T>()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -25,7 +25,7 @@ namespace CPRNIMS.WebApps.Controllers.Inventory
|
|||||||
"2" => 2,
|
"2" => 2,
|
||||||
_ => null
|
_ => null
|
||||||
};
|
};
|
||||||
|
GetUser();
|
||||||
var result = await _mrs.GetMRSPaged(new MRSPagedRequest
|
var result = await _mrs.GetMRSPaged(new MRSPagedRequest
|
||||||
{
|
{
|
||||||
SearchMRSNo = searchMRSNo,
|
SearchMRSNo = searchMRSNo,
|
||||||
@ -43,6 +43,7 @@ namespace CPRNIMS.WebApps.Controllers.Inventory
|
|||||||
[HttpGet]
|
[HttpGet]
|
||||||
public async Task<IActionResult> SearchRIS([FromQuery] int? searchProjectCodeId, string? searchRISNo , CancellationToken ct = default)
|
public async Task<IActionResult> SearchRIS([FromQuery] int? searchProjectCodeId, string? searchRISNo , CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
|
GetUser();
|
||||||
var result = await _mrs.SearchRIS(new SearchRISProjectCodeRequest
|
var result = await _mrs.SearchRIS(new SearchRISProjectCodeRequest
|
||||||
{
|
{
|
||||||
SearchRISNo = searchRISNo,
|
SearchRISNo = searchRISNo,
|
||||||
@ -54,6 +55,7 @@ namespace CPRNIMS.WebApps.Controllers.Inventory
|
|||||||
[HttpGet]
|
[HttpGet]
|
||||||
public async Task<IActionResult> SearchProjects([FromQuery] string? searchProjectCode, CancellationToken ct = default)
|
public async Task<IActionResult> SearchProjects([FromQuery] string? searchProjectCode, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
|
GetUser();
|
||||||
var result = await _mrs.SearchProjects(new SearchRISProjectCodeRequest
|
var result = await _mrs.SearchProjects(new SearchRISProjectCodeRequest
|
||||||
{ SearchProjectCode = searchProjectCode,}, ct);
|
{ SearchProjectCode = searchProjectCode,}, ct);
|
||||||
|
|
||||||
@ -62,6 +64,7 @@ namespace CPRNIMS.WebApps.Controllers.Inventory
|
|||||||
[HttpPost]
|
[HttpPost]
|
||||||
public async Task<IActionResult> CreateMRS([FromBody] CreateMRSRequest request,CancellationToken ct)
|
public async Task<IActionResult> CreateMRS([FromBody] CreateMRSRequest request,CancellationToken ct)
|
||||||
{
|
{
|
||||||
|
GetUser();
|
||||||
var result = await _mrs.CreateMRS(request, ct);
|
var result = await _mrs.CreateMRS(request, ct);
|
||||||
|
|
||||||
if (!result.success)
|
if (!result.success)
|
||||||
@ -73,6 +76,7 @@ namespace CPRNIMS.WebApps.Controllers.Inventory
|
|||||||
[HttpPost]
|
[HttpPost]
|
||||||
public async Task<IActionResult> ApproveMRS([FromBody] ApproveMRSRequest request,CancellationToken ct)
|
public async Task<IActionResult> ApproveMRS([FromBody] ApproveMRSRequest request,CancellationToken ct)
|
||||||
{
|
{
|
||||||
|
GetUser();
|
||||||
var result = await _mrs.ApproveMRS(request, ct);
|
var result = await _mrs.ApproveMRS(request, ct);
|
||||||
|
|
||||||
if (!result.success)
|
if (!result.success)
|
||||||
@ -84,6 +88,7 @@ namespace CPRNIMS.WebApps.Controllers.Inventory
|
|||||||
[HttpPost]
|
[HttpPost]
|
||||||
public async Task<IActionResult> CancelMRS([FromBody] CancelMRSRequest request,CancellationToken ct)
|
public async Task<IActionResult> CancelMRS([FromBody] CancelMRSRequest request,CancellationToken ct)
|
||||||
{
|
{
|
||||||
|
GetUser();
|
||||||
var result = await _mrs.CancelMRS(request, ct);
|
var result = await _mrs.CancelMRS(request, ct);
|
||||||
|
|
||||||
if (!result.success)
|
if (!result.success)
|
||||||
|
|||||||
@ -5,9 +5,6 @@ using CPRNIMS.Infrastructure.Dto.Inventory.Request;
|
|||||||
using CPRNIMS.Infrastructure.Helper;
|
using CPRNIMS.Infrastructure.Helper;
|
||||||
using CPRNIMS.WebApps.Controllers.Base;
|
using CPRNIMS.WebApps.Controllers.Base;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using FastReport;
|
|
||||||
using FastReport.Web;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace CPRNIMS.WebApps.Controllers.Inventory
|
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<IActionResult> 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]
|
[HttpPost]
|
||||||
public async Task<IActionResult> CreateRIS([FromBody] CreateRISRequest request,CancellationToken ct)
|
public async Task<IActionResult> CreateRIS([FromBody] CreateRISRequest request,CancellationToken ct)
|
||||||
{
|
{
|
||||||
|
GetUser();
|
||||||
var result = await _ris.CreateRIS(request,ct);
|
var result = await _ris.CreateRIS(request,ct);
|
||||||
if (!result.success)
|
if (!result.success)
|
||||||
return BadRequest(new { success = false, message = result.message });
|
return BadRequest(new { success = false, message = result.message });
|
||||||
@ -60,6 +37,7 @@ namespace CPRNIMS.WebApps.Controllers.Inventory
|
|||||||
[HttpPost]
|
[HttpPost]
|
||||||
public async Task<IActionResult> ApproveRIS([FromBody] ApproveRISRequest request,CancellationToken ct)
|
public async Task<IActionResult> ApproveRIS([FromBody] ApproveRISRequest request,CancellationToken ct)
|
||||||
{
|
{
|
||||||
|
GetUser();
|
||||||
var result = await _ris.ApproveRIS(request, ct);
|
var result = await _ris.ApproveRIS(request, ct);
|
||||||
|
|
||||||
if (!result.success)
|
if (!result.success)
|
||||||
@ -71,6 +49,7 @@ namespace CPRNIMS.WebApps.Controllers.Inventory
|
|||||||
[HttpPost]
|
[HttpPost]
|
||||||
public async Task<IActionResult> CancelRIS([FromBody] CancelRISRequest request,CancellationToken ct)
|
public async Task<IActionResult> CancelRIS([FromBody] CancelRISRequest request,CancellationToken ct)
|
||||||
{
|
{
|
||||||
|
GetUser();
|
||||||
if (string.IsNullOrWhiteSpace(request.Reason))
|
if (string.IsNullOrWhiteSpace(request.Reason))
|
||||||
return BadRequest(new{success = false,message = "A reason for cancellation is required."});
|
return BadRequest(new{success = false,message = "A reason for cancellation is required."});
|
||||||
|
|
||||||
@ -94,7 +73,7 @@ namespace CPRNIMS.WebApps.Controllers.Inventory
|
|||||||
"2" => 2,
|
"2" => 2,
|
||||||
_ => null
|
_ => null
|
||||||
};
|
};
|
||||||
|
GetUser();
|
||||||
var result = await _ris.GetRISPaged(new RISPagedRequest
|
var result = await _ris.GetRISPaged(new RISPagedRequest
|
||||||
{
|
{
|
||||||
SearchRISNo = searchRISNo,
|
SearchRISNo = searchRISNo,
|
||||||
|
|||||||
@ -5,26 +5,41 @@
|
|||||||
color: #1a2e35;
|
color: #1a2e35;
|
||||||
padding: 24px;
|
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 {
|
#print-mount {
|
||||||
position: absolute;
|
display: block !important;
|
||||||
left: 0;
|
position: static !important;
|
||||||
top: 0;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#print-mount .rpt-page {
|
||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
border: none !important;
|
border: none !important;
|
||||||
border-radius: 0 !important;
|
border-radius: 0 !important;
|
||||||
|
overflow: visible !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.no-print {
|
.rpt-section {
|
||||||
display: none !important;
|
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 {
|
.rpt-table tr {
|
||||||
@ -35,8 +50,11 @@
|
|||||||
display: table-header-group;
|
display: table-header-group;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@page {
|
||||||
|
size: A4 landscape;
|
||||||
|
margin: 5mm;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.rpt-page {
|
.rpt-page {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@ -45,6 +63,7 @@
|
|||||||
border-radius: var(--radius-lg, 14px);
|
border-radius: var(--radius-lg, 14px);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.rpt-header {
|
.rpt-header {
|
||||||
padding: 20px 24px 16px;
|
padding: 20px 24px 16px;
|
||||||
border-bottom: 1px solid #d6eaec;
|
border-bottom: 1px solid #d6eaec;
|
||||||
@ -117,6 +136,7 @@
|
|||||||
background: var(--teal-pale, #e6f7f8);
|
background: var(--teal-pale, #e6f7f8);
|
||||||
border-color: var(--teal-dark, #0d5c63);
|
border-color: var(--teal-dark, #0d5c63);
|
||||||
}
|
}
|
||||||
|
|
||||||
.rpt-header-top {
|
.rpt-header-top {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
@ -387,76 +407,256 @@
|
|||||||
const xlsxBtn = document.getElementById("inv-rpt-excel");
|
const xlsxBtn = document.getElementById("inv-rpt-excel");
|
||||||
const container = document.getElementById("inv-rpt-container");
|
const container = document.getElementById("inv-rpt-container");
|
||||||
|
|
||||||
if (!fromEl || !toEl || !container) {
|
// Department
|
||||||
console.error("Inventory report subtab init failed — missing elements.");
|
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 = `<div class="inv-dep-empty">No departments found</div>`;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deptList.innerHTML = filtered.map(o => `
|
||||||
|
<div class="inv-dep-opt ${o.value === selectedDept ? "active" : ""}" data-value="${_esc(o.value)}">
|
||||||
|
<i class="fas ${o.value === "" ? "fa-th" : "fa-building"}"></i>
|
||||||
|
<span>${_esc(o.name)}</span>
|
||||||
|
</div>`).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
|
// default: first day of current month -> today
|
||||||
const today = new Date();
|
const today = new Date();
|
||||||
const first = new Date(today.getFullYear(), today.getMonth(), 1);
|
const first = new Date(today.getFullYear(), today.getMonth(), 1);
|
||||||
|
const deptEl = document.getElementById("inv-rpt-dept");
|
||||||
toEl.value = today.toISOString().slice(0, 10);
|
toEl.value = today.toISOString().slice(0, 10);
|
||||||
fromEl.value = first.toISOString().slice(0, 10);
|
fromEl.value = first.toISOString().slice(0, 10);
|
||||||
|
|
||||||
let lastData = null; // cache for export
|
let currentPage = 1;
|
||||||
|
let pageSize = 10;
|
||||||
function buildParams() {
|
let lastData = null; // paged data (for display)
|
||||||
return new URLSearchParams({ dateFrom: fromEl.value, dateTo: toEl.value });
|
|
||||||
}
|
|
||||||
|
|
||||||
csvBtn.addEventListener("click", exportCsv);
|
csvBtn.addEventListener("click", exportCsv);
|
||||||
genBtn.addEventListener("click", fetchAndRender);
|
genBtn.addEventListener("click", () => { currentPage = 1; fetchAndRender(); });
|
||||||
|
|
||||||
async function fetchAndRender() {
|
async function fetchAndRender() {
|
||||||
container.innerHTML = `<div class="inv-tab-loading">
|
container.innerHTML = `<div class="inv-tab-loading"><div class="inv-spinner"></div><span>Loading report…</span></div>`;
|
||||||
<div class="inv-spinner"></div><span>Loading report…</span></div>`;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`/InventoryReports/GetInventoryReport?${buildParams()}`);
|
const res = await fetch(`/InventoryReports/GetInventoryReport?${buildParams()}`);
|
||||||
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||||
const json = await res.json();
|
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);
|
renderReport(lastData);
|
||||||
|
renderPager(lastData);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Inventory report fetch error:", err);
|
console.error("Inventory report fetch error:", err);
|
||||||
container.innerHTML = `
|
container.innerHTML = `<div class="inv-placeholder"><i class="fas fa-exclamation-triangle" style="color:#ff5c5c"></i><h3>Failed to load report</h3><p>Please try again.</p></div>`;
|
||||||
<div class="inv-placeholder">
|
|
||||||
<i class="fas fa-exclamation-triangle" style="color:#ff5c5c"></i>
|
|
||||||
<h3>Failed to load report</h3>
|
|
||||||
<p>Please try again.</p>
|
|
||||||
</div>`;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 = `
|
||||||
|
<div class="inv-pg-left">
|
||||||
|
<span class="inv-pgsz-lbl">Show</span>
|
||||||
|
<select class="inv-pgsz-sel" id="inv-pgsz-sel">
|
||||||
|
<option value="10">10</option>
|
||||||
|
<option value="25">25</option>
|
||||||
|
<option value="50">50</option>
|
||||||
|
<option value="100">100</option>
|
||||||
|
<option value="all">All</option>
|
||||||
|
</select>
|
||||||
|
<span class="inv-pg-info">Showing <b>${from}–${to}</b> of <b>${total.toLocaleString()}</b> items</span>
|
||||||
|
</div>
|
||||||
|
<div class="inv-pg-btns">
|
||||||
|
<button class="inv-pg-btn" data-pg="${page - 1}" ${page <= 1 ? "disabled" : ""}>
|
||||||
|
<i class="fas fa-chevron-left"></i>
|
||||||
|
</button>
|
||||||
|
${nums[0] > 1 ? `<button class="inv-pg-btn" data-pg="1">1</button>${nums[0] > 2 ? `<span class="inv-pg-ellipsis">…</span>` : ""}` : ""}
|
||||||
|
${nums.map(n => `<button class="inv-pg-btn ${n === page ? "active" : ""}" data-pg="${n}">${n}</button>`).join("")}
|
||||||
|
${nums[nums.length-1] < pages ? `${nums[nums.length-1] < pages-1 ? `<span class="inv-pg-ellipsis">…</span>` : ""}<button class="inv-pg-btn" data-pg="${pages}">${pages}</button>` : ""}
|
||||||
|
<button class="inv-pg-btn" data-pg="${page + 1}" ${page >= pages ? "disabled" : ""}>
|
||||||
|
<i class="fas fa-chevron-right"></i>
|
||||||
|
</button>
|
||||||
|
</div>`;
|
||||||
|
|
||||||
|
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) ──
|
// ── CSV export (client-side, no backend needed) ──
|
||||||
function exportCsv() {
|
async function exportCsv() {
|
||||||
if (!lastData) return;
|
const data = await fetchFullForExport();
|
||||||
const rows = lastData.rows ?? [];
|
if (!data) return;
|
||||||
const byCat = lastData.byCategory ?? [];
|
const rows = data.rows ?? [];
|
||||||
const alerts = lastData.alerts ?? [];
|
const byCat = data.byCategory ?? [];
|
||||||
const summary = lastData.summary ?? {};
|
const alerts = data.alerts ?? [];
|
||||||
const headers = ["Item Name","Item No.","Category","Lot No.","Qty In","Qty Out","On Hand","Stock %"];
|
const summary = data.summary ?? {};
|
||||||
|
const headers = ["Item Name / UOM","Item No.","Category","Department","Lot No","Qty In","Qty Out","On Hand","Stock %"];
|
||||||
|
|
||||||
const csvLines = [
|
const csvLines = [
|
||||||
`Inventory Summary Report`,
|
`Inventory Summary Report`,
|
||||||
`Company,${csvCell(lastData.companyName)}`,
|
`Company,${csvCell(data.companyName)}`,
|
||||||
`Report No,${csvCell(lastData.reportNo)}`,
|
`Report No,${csvCell(data.reportNo)}`,
|
||||||
`Period,${csvCell(fromEl.value)} to ${csvCell(toEl.value)}`,
|
`Period,${csvCell(fromEl.value)} to ${csvCell(toEl.value)}`,
|
||||||
``,
|
``,
|
||||||
// ── Inventory detail ──
|
|
||||||
headers.map(csvCell).join(","),
|
headers.map(csvCell).join(","),
|
||||||
...rows.map(r => [
|
...rows.map(r => [
|
||||||
r.itemName, r.itemNo, r.itemCategoryName, r.lotNo,
|
r.itemName, r.itemNo, r.itemCategoryName, r.department, r.lotNo,r.qtyIn, r.qtyOut, r.qtyOnHand, r.stockPct
|
||||||
r.qtyIn, r.qtyOut, r.qtyOnHand, r.stockPct
|
|
||||||
].map(csvCell).join(",")),
|
].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`,
|
`Stock Level by Category`,
|
||||||
["Category","Avg Stock %"].map(csvCell).join(","),
|
["Category","Avg Stock %"].map(csvCell).join(","),
|
||||||
...byCat.map(c => [c.categoryName, c.avgStockPct].map(csvCell).join(",")),
|
...byCat.map(c => [c.categoryName, c.avgStockPct].map(csvCell).join(",")),
|
||||||
``,
|
``,
|
||||||
// ── Items requiring attention ──
|
|
||||||
`Items Requiring Attention`,
|
`Items Requiring Attention`,
|
||||||
["Item","On Hand","Alert"].map(csvCell).join(","),
|
["Item","On Hand","Alert"].map(csvCell).join(","),
|
||||||
...alerts.map(a => [a.itemName, a.qtyOnHand, a.severity].map(csvCell).join(","))
|
...alerts.map(a => [a.itemName, a.qtyOnHand, a.severity].map(csvCell).join(","))
|
||||||
@ -468,10 +668,7 @@
|
|||||||
"text/csv;charset=utf-8;"
|
"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) {
|
function downloadBlob(content, filename, mime) {
|
||||||
const blob = new Blob([content], { type: mime });
|
const blob = new Blob([content], { type: mime });
|
||||||
const url = URL.createObjectURL(blob);
|
const url = URL.createObjectURL(blob);
|
||||||
@ -530,6 +727,7 @@
|
|||||||
<div class="kpi-lbl"><i class="fas fa-layer-group"></i> Total on hand</div>
|
<div class="kpi-lbl"><i class="fas fa-layer-group"></i> Total on hand</div>
|
||||||
<div class="kpi-val">${(summary.totalOnHand ?? 0).toLocaleString()}</div>
|
<div class="kpi-val">${(summary.totalOnHand ?? 0).toLocaleString()}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="kpi-cell">
|
<div class="kpi-cell">
|
||||||
<div class="kpi-lbl"><i class="fas fa-exclamation-triangle"></i> Low stock</div>
|
<div class="kpi-lbl"><i class="fas fa-exclamation-triangle"></i> Low stock</div>
|
||||||
<div class="kpi-val" style="color:#A32D2D">${summary.lowStockCount ?? 0}</div>
|
<div class="kpi-val" style="color:#A32D2D">${summary.lowStockCount ?? 0}</div>
|
||||||
@ -545,9 +743,14 @@
|
|||||||
<table class="rpt-table">
|
<table class="rpt-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Item Name</th><th>Item No.</th><th>Category</th><th>Lot No.</th>
|
<th>Item Name / UOM</th>
|
||||||
|
<th>Item No.</th>
|
||||||
|
<th>Category</th>
|
||||||
|
<th>Department</th>
|
||||||
|
<th class="num">Lot No</th>
|
||||||
<th class="num">Qty In</th><th class="num">Qty Out</th>
|
<th class="num">Qty In</th><th class="num">Qty Out</th>
|
||||||
<th class="num">On Hand</th><th>Stock Level</th>
|
<th class="num">On Hand</th>
|
||||||
|
<th>Stock Level</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@ -556,14 +759,15 @@
|
|||||||
<td style="font-weight:600">${_esc(r.itemName)}</td>
|
<td style="font-weight:600">${_esc(r.itemName)}</td>
|
||||||
<td style="color:var(--text-muted,#6b8890)">#${_esc(r.itemNo)}</td>
|
<td style="color:var(--text-muted,#6b8890)">#${_esc(r.itemNo)}</td>
|
||||||
<td>${_esc(r.itemCategoryName)}</td>
|
<td>${_esc(r.itemCategoryName)}</td>
|
||||||
<td style="color:var(--text-muted,#6b8890)">${_esc(r.lotNo)}</td>
|
<td>${_esc(r.department ?? "—")}</td>
|
||||||
|
<td class="num">${r.lotNo}</td>
|
||||||
<td class="num">${r.qtyIn}</td>
|
<td class="num">${r.qtyIn}</td>
|
||||||
<td class="num">${r.qtyOut}</td>
|
<td class="num">${r.qtyOut}</td>
|
||||||
<td class="num" style="font-weight:600">${r.qtyOnHand}</td>
|
<td class="num" style="font-weight:600">${r.qtyOnHand}</td>
|
||||||
<td>${_stockBar(r.stockPct)}</td>
|
<td>${_stockBar(r.stockPct)}</td>
|
||||||
</tr>`).join("")}
|
</tr>`).join("")}
|
||||||
<tr class="total-row">
|
<tr class="total-row">
|
||||||
<td colspan="4">Total</td>
|
<td colspan="5">Total</td>
|
||||||
<td class="num">${summary.totalQtyIn ?? 0}</td>
|
<td class="num">${summary.totalQtyIn ?? 0}</td>
|
||||||
<td class="num">${summary.totalQtyOut ?? 0}</td>
|
<td class="num">${summary.totalQtyOut ?? 0}</td>
|
||||||
<td class="num">${summary.totalOnHand ?? 0}</td>
|
<td class="num">${summary.totalOnHand ?? 0}</td>
|
||||||
@ -639,14 +843,15 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ── Excel export (client-side, HTML-table .xls — Excel opens natively) ──
|
// ── Excel export (client-side, HTML-table .xls — Excel opens natively) ──
|
||||||
function exportExcel() {
|
async function exportExcel() {
|
||||||
if (!lastData) return;
|
const data = await fetchFullForExport();
|
||||||
const rows = lastData.rows ?? [];
|
if (!data) return;
|
||||||
const byCat = lastData.byCategory ?? [];
|
const rows = data.rows ?? [];
|
||||||
const alerts = lastData.alerts ?? [];
|
const byCat = data.byCategory ?? [];
|
||||||
const summary = lastData.summary ?? {};
|
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 => `<th>${_esc(h)}</th>`).join("");
|
.map(h => `<th>${_esc(h)}</th>`).join("");
|
||||||
|
|
||||||
const bodyRows = rows.map(r => `
|
const bodyRows = rows.map(r => `
|
||||||
@ -654,7 +859,8 @@
|
|||||||
<td>${_esc(r.itemName)}</td>
|
<td>${_esc(r.itemName)}</td>
|
||||||
<td>${_esc(r.itemNo)}</td>
|
<td>${_esc(r.itemNo)}</td>
|
||||||
<td>${_esc(r.itemCategoryName)}</td>
|
<td>${_esc(r.itemCategoryName)}</td>
|
||||||
<td>${_esc(r.lotNo)}</td>
|
<td>${_esc(r.department ?? "")}</td>
|
||||||
|
<td>${_esc(r.lotNo ?? "")}</td>
|
||||||
<td>${r.qtyIn}</td>
|
<td>${r.qtyIn}</td>
|
||||||
<td>${r.qtyOut}</td>
|
<td>${r.qtyOut}</td>
|
||||||
<td>${r.qtyOnHand}</td>
|
<td>${r.qtyOnHand}</td>
|
||||||
@ -674,23 +880,27 @@
|
|||||||
<head><meta charset="utf-8"></head>
|
<head><meta charset="utf-8"></head>
|
||||||
<body>
|
<body>
|
||||||
<table border="1">
|
<table border="1">
|
||||||
<tr><td colspan="8"><b>Inventory Summary Report</b></td></tr>
|
<tr><td colspan="9"><b>Inventory Summary Report</b></td></tr>
|
||||||
<tr><td>Company</td><td colspan="7">${_esc(lastData.companyName)}</td></tr>
|
<tr><td>Company</td><td colspan="8">${_esc(data.companyName)}</td></tr>
|
||||||
<tr><td>Report No</td><td colspan="7">${_esc(lastData.reportNo)}</td></tr>
|
<tr><td>Report No</td><td colspan="8">${_esc(data.reportNo)}</td></tr>
|
||||||
<tr><td>Period</td><td colspan="7">${_esc(fromEl.value)} to ${_esc(toEl.value)}</td></tr>
|
<tr><td>Period</td><td colspan="8">${_esc(fromEl.value)} to ${_esc(toEl.value)}</td></tr>
|
||||||
<tr></tr>
|
<tr></tr>
|
||||||
<tr>${headerCells}</tr>
|
<tr>${headerCells}</tr>
|
||||||
${bodyRows}
|
${bodyRows}
|
||||||
<tr><td colspan="4"><b>Total</b></td><td>${summary.totalQtyIn ?? 0}</td><td>${summary.totalQtyOut ?? 0}</td><td>${summary.totalOnHand ?? 0}</td><td></td></tr>
|
<tr>
|
||||||
|
<td colspan="5"><b>Total</b></td>
|
||||||
|
<td>${summary.totalQtyIn ?? 0}</td>
|
||||||
|
<td>${summary.totalQtyOut ?? 0}</td>
|
||||||
|
<td>${summary.totalOnHand ?? 0}</td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<br/>
|
<br/>
|
||||||
<table border="1">
|
<table border="1">
|
||||||
<tr><td colspan="2"><b>Stock Level by Category</b></td></tr>
|
<tr><td colspan="2"><b>Stock Level by Category</b></td></tr>
|
||||||
<tr><th>Category</th><th>Avg Stock %</th></tr>
|
<tr><th>Category</th><th>Avg Stock %</th></tr>
|
||||||
${catRows}
|
${catRows}
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<br/>
|
<br/>
|
||||||
<table border="1">
|
<table border="1">
|
||||||
<tr><td colspan="3"><b>Items Requiring Attention</b></td></tr>
|
<tr><td colspan="3"><b>Items Requiring Attention</b></td></tr>
|
||||||
|
|||||||
@ -5,36 +5,31 @@
|
|||||||
color: #1a2e35;
|
color: #1a2e35;
|
||||||
padding: 24px;
|
padding: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@media print {
|
@@media print {
|
||||||
body { padding: 0; margin: 0; background: #fff;}
|
body > *:not(#print-mount) {
|
||||||
body * { visibility: hidden;}
|
display: none !important;
|
||||||
|
|
||||||
#inv-rpt-page, #inv-rpt-page * {
|
|
||||||
visibility: visible;
|
|
||||||
}
|
}
|
||||||
|
#print-mount {
|
||||||
#inv-rpt-page {
|
display: block !important;
|
||||||
position: absolute;
|
position: static !important;
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
}
|
||||||
|
#print-mount .rpt-page {
|
||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
border: none !important;
|
border: none !important;
|
||||||
border-radius: 0 !important;
|
border-radius: 0 !important;
|
||||||
|
overflow: visible !important;
|
||||||
}
|
}
|
||||||
|
.rpt-section { page-break-inside: auto; }
|
||||||
.no-print {
|
.rpt-table { table-layout: fixed; width: 100%; font-size: 9px; page-break-inside: auto; }
|
||||||
display: none !important;
|
.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-table tr {
|
|
||||||
page-break-inside: avoid;
|
|
||||||
}
|
|
||||||
|
|
||||||
thead {
|
|
||||||
display: table-header-group;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.rpt-page {
|
.rpt-page {
|
||||||
@ -387,76 +382,258 @@
|
|||||||
const xlsxBtn = document.getElementById("inv-rpt-excel");
|
const xlsxBtn = document.getElementById("inv-rpt-excel");
|
||||||
const container = document.getElementById("inv-rpt-container");
|
const container = document.getElementById("inv-rpt-container");
|
||||||
|
|
||||||
if (!fromEl || !toEl || !container) {
|
// Department
|
||||||
console.error("Inventory report subtab init failed — missing elements.");
|
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 = `<div class="inv-dep-empty">No departments found</div>`;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deptList.innerHTML = filtered.map(o => `
|
||||||
|
<div class="inv-dep-opt ${o.value === selectedDept ? "active" : ""}" data-value="${_esc(o.value)}">
|
||||||
|
<i class="fas ${o.value === "" ? "fa-th" : "fa-building"}"></i>
|
||||||
|
<span>${_esc(o.name)}</span>
|
||||||
|
</div>`).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
|
// default: first day of current month -> today
|
||||||
const today = new Date();
|
const today = new Date();
|
||||||
const first = new Date(today.getFullYear(), today.getMonth(), 1);
|
const first = new Date(today.getFullYear(), today.getMonth(), 1);
|
||||||
|
const deptEl = document.getElementById("inv-rpt-dept");
|
||||||
toEl.value = today.toISOString().slice(0, 10);
|
toEl.value = today.toISOString().slice(0, 10);
|
||||||
fromEl.value = first.toISOString().slice(0, 10);
|
fromEl.value = first.toISOString().slice(0, 10);
|
||||||
|
|
||||||
let lastData = null; // cache for export
|
let currentPage = 1;
|
||||||
|
let pageSize = 10;
|
||||||
function buildParams() {
|
let lastData = null; // paged data (for display)
|
||||||
return new URLSearchParams({ dateFrom: fromEl.value, dateTo: toEl.value });
|
|
||||||
}
|
|
||||||
|
|
||||||
csvBtn.addEventListener("click", exportCsv);
|
csvBtn.addEventListener("click", exportCsv);
|
||||||
genBtn.addEventListener("click", fetchAndRender);
|
genBtn.addEventListener("click", () => { currentPage = 1; fetchAndRender(); });
|
||||||
|
|
||||||
async function fetchAndRender() {
|
async function fetchAndRender() {
|
||||||
container.innerHTML = `<div class="inv-tab-loading">
|
container.innerHTML = `<div class="inv-tab-loading"><div class="inv-spinner"></div><span>Loading report…</span></div>`;
|
||||||
<div class="inv-spinner"></div><span>Loading report…</span></div>`;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`/InventoryReports/GetInventoryReport?${buildParams()}`);
|
const res = await fetch(`/InventoryReports/GetInventoryReport?${buildParams()}`);
|
||||||
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||||
const json = await res.json();
|
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);
|
renderReport(lastData);
|
||||||
|
renderPager(lastData);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Inventory report fetch error:", err);
|
console.error("Inventory report fetch error:", err);
|
||||||
container.innerHTML = `
|
container.innerHTML = `<div class="inv-placeholder"><i class="fas fa-exclamation-triangle" style="color:#ff5c5c"></i><h3>Failed to load report</h3><p>Please try again.</p></div>`;
|
||||||
<div class="inv-placeholder">
|
|
||||||
<i class="fas fa-exclamation-triangle" style="color:#ff5c5c"></i>
|
|
||||||
<h3>Failed to load report</h3>
|
|
||||||
<p>Please try again.</p>
|
|
||||||
</div>`;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 = `
|
||||||
|
<div class="inv-pg-left">
|
||||||
|
<span class="inv-pgsz-lbl">Show</span>
|
||||||
|
<select class="inv-pgsz-sel" id="inv-pgsz-sel">
|
||||||
|
<option value="10">10</option>
|
||||||
|
<option value="25">25</option>
|
||||||
|
<option value="50">50</option>
|
||||||
|
<option value="100">100</option>
|
||||||
|
<option value="all">All</option>
|
||||||
|
</select>
|
||||||
|
<span class="inv-pg-info">Showing <b>${from}–${to}</b> of <b>${total.toLocaleString()}</b> items</span>
|
||||||
|
</div>
|
||||||
|
<div class="inv-pg-btns">
|
||||||
|
<button class="inv-pg-btn" data-pg="${page - 1}" ${page <= 1 ? "disabled" : ""}>
|
||||||
|
<i class="fas fa-chevron-left"></i>
|
||||||
|
</button>
|
||||||
|
${nums[0] > 1 ? `<button class="inv-pg-btn" data-pg="1">1</button>${nums[0] > 2 ? `<span class="inv-pg-ellipsis">…</span>` : ""}` : ""}
|
||||||
|
${nums.map(n => `<button class="inv-pg-btn ${n === page ? "active" : ""}" data-pg="${n}">${n}</button>`).join("")}
|
||||||
|
${nums[nums.length-1] < pages ? `${nums[nums.length-1] < pages-1 ? `<span class="inv-pg-ellipsis">…</span>` : ""}<button class="inv-pg-btn" data-pg="${pages}">${pages}</button>` : ""}
|
||||||
|
<button class="inv-pg-btn" data-pg="${page + 1}" ${page >= pages ? "disabled" : ""}>
|
||||||
|
<i class="fas fa-chevron-right"></i>
|
||||||
|
</button>
|
||||||
|
</div>`;
|
||||||
|
|
||||||
|
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) ──
|
// ── CSV export (client-side, no backend needed) ──
|
||||||
function exportCsv() {
|
async function exportCsv() {
|
||||||
if (!lastData) return;
|
const data = await fetchFullForExport();
|
||||||
const rows = lastData.rows ?? [];
|
if (!data) return;
|
||||||
const byCat = lastData.byCategory ?? [];
|
const rows = data.rows ?? [];
|
||||||
const alerts = lastData.alerts ?? [];
|
const byCat = data.byCategory ?? [];
|
||||||
const summary = lastData.summary ?? {};
|
const alerts = data.alerts ?? [];
|
||||||
const headers = ["Item Name","Item No.","Category","Unit Price","Qty In","Qty Out","On Hand","Stock %"];
|
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 = [
|
const csvLines = [
|
||||||
`Inventory Summary Report`,
|
`Inventory Summary Report`,
|
||||||
`Company,${csvCell(lastData.companyName)}`,
|
`Company,${csvCell(data.companyName)}`,
|
||||||
`Report No,${csvCell(lastData.reportNo)}`,
|
`Report No,${csvCell(data.reportNo)}`,
|
||||||
`Period,${csvCell(fromEl.value)} to ${csvCell(toEl.value)}`,
|
`Period,${csvCell(fromEl.value)} to ${csvCell(toEl.value)}`,
|
||||||
``,
|
``,
|
||||||
// ── Inventory detail ──
|
|
||||||
headers.map(csvCell).join(","),
|
headers.map(csvCell).join(","),
|
||||||
...rows.map(r => [
|
...rows.map(r => [
|
||||||
r.itemName, r.itemNo, r.itemCategoryName, r.unitPrice,
|
r.itemName, r.itemNo, r.itemCategoryName, r.department, r.currencyCode,
|
||||||
r.qtyIn, r.qtyOut, r.qtyOnHand, r.stockPct
|
r.unitPrice, r.qtyIn, r.qtyOut, r.qtyOnHand,
|
||||||
|
(Number(r.qtyOnHand) || 0) * (Number(r.unitPrice) || 0), r.stockPct
|
||||||
].map(csvCell).join(",")),
|
].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`,
|
`Stock Level by Category`,
|
||||||
["Category","Avg Stock %"].map(csvCell).join(","),
|
["Category","Avg Stock %"].map(csvCell).join(","),
|
||||||
...byCat.map(c => [c.categoryName, c.avgStockPct].map(csvCell).join(",")),
|
...byCat.map(c => [c.categoryName, c.avgStockPct].map(csvCell).join(",")),
|
||||||
``,
|
``,
|
||||||
// ── Items requiring attention ──
|
|
||||||
`Items Requiring Attention`,
|
`Items Requiring Attention`,
|
||||||
["Item","On Hand","Alert"].map(csvCell).join(","),
|
["Item","On Hand","Alert"].map(csvCell).join(","),
|
||||||
...alerts.map(a => [a.itemName, a.qtyOnHand, a.severity].map(csvCell).join(","))
|
...alerts.map(a => [a.itemName, a.qtyOnHand, a.severity].map(csvCell).join(","))
|
||||||
@ -468,10 +645,7 @@
|
|||||||
"text/csv;charset=utf-8;"
|
"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) {
|
function downloadBlob(content, filename, mime) {
|
||||||
const blob = new Blob([content], { type: mime });
|
const blob = new Blob([content], { type: mime });
|
||||||
const url = URL.createObjectURL(blob);
|
const url = URL.createObjectURL(blob);
|
||||||
@ -549,7 +723,9 @@
|
|||||||
<table class="rpt-table">
|
<table class="rpt-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Item Name</th><th>Item No.</th><th>Category</th>
|
<th>Item Name / UOM</th>
|
||||||
|
<th>Item No.</th><th>Category</th>
|
||||||
|
<th>Department</th>
|
||||||
<th class="num">Unit Price</th>
|
<th class="num">Unit Price</th>
|
||||||
<th class="num">Qty In</th><th class="num">Qty Out</th>
|
<th class="num">Qty In</th><th class="num">Qty Out</th>
|
||||||
<th class="num">On Hand</th>
|
<th class="num">On Hand</th>
|
||||||
@ -563,15 +739,16 @@
|
|||||||
<td style="font-weight:600">${_esc(r.itemName)}</td>
|
<td style="font-weight:600">${_esc(r.itemName)}</td>
|
||||||
<td style="color:var(--text-muted,#6b8890)">#${_esc(r.itemNo)}</td>
|
<td style="color:var(--text-muted,#6b8890)">#${_esc(r.itemNo)}</td>
|
||||||
<td>${_esc(r.itemCategoryName)}</td>
|
<td>${_esc(r.itemCategoryName)}</td>
|
||||||
<td class="num">${_money(r.unitPrice)}</td>
|
<td>${_esc(r.department ?? "—")}</td>
|
||||||
|
<td class="num">${_money(r.unitPrice, r.currencyCode)}</td>
|
||||||
<td class="num">${r.qtyIn}</td>
|
<td class="num">${r.qtyIn}</td>
|
||||||
<td class="num">${r.qtyOut}</td>
|
<td class="num">${r.qtyOut}</td>
|
||||||
<td class="num" style="font-weight:600">${r.qtyOnHand}</td>
|
<td class="num" style="font-weight:600">${r.qtyOnHand}</td>
|
||||||
<td class="num">${_money(r.qtyOnHand * r.unitPrice)}</td>
|
<td class="num">${_money(r.qtyOnHand * r.unitPrice, r.currencyCode)}</td>
|
||||||
<td>${_stockBar(r.stockPct)}</td>
|
<td>${_stockBar(r.stockPct)}</td>
|
||||||
</tr>`).join("")}
|
</tr>`).join("")}
|
||||||
<tr class="total-row">
|
<tr class="total-row">
|
||||||
<td colspan="4">Total</td>
|
<td colspan="5">Total</td>
|
||||||
<td class="num">${summary.totalQtyIn ?? 0}</td>
|
<td class="num">${summary.totalQtyIn ?? 0}</td>
|
||||||
<td class="num">${summary.totalQtyOut ?? 0}</td>
|
<td class="num">${summary.totalQtyOut ?? 0}</td>
|
||||||
<td class="num">${summary.totalOnHand ?? 0}</td>
|
<td class="num">${summary.totalOnHand ?? 0}</td>
|
||||||
@ -628,13 +805,13 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
function _money(v) {
|
function _money(v, currency) {
|
||||||
const n = Number(v) || 0;
|
const n = Number(v) || 0;
|
||||||
return n.toLocaleString("en-PH", {
|
const amount = n.toLocaleString("en-PH", {
|
||||||
style: "currency",
|
minimumFractionDigits: 2,
|
||||||
currency: "PHP",
|
maximumFractionDigits: 2
|
||||||
minimumFractionDigits: 2
|
|
||||||
});
|
});
|
||||||
|
return currency ? `${currency} ${amount}` : amount;
|
||||||
}
|
}
|
||||||
function _stockBar(pct) {
|
function _stockBar(pct) {
|
||||||
const cls = pct < 20 ? "fill-coral" : pct < 50 ? "fill-amber" : "fill-teal";
|
const cls = pct < 20 ? "fill-coral" : pct < 50 ? "fill-amber" : "fill-teal";
|
||||||
@ -655,14 +832,15 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ── Excel export (client-side, HTML-table .xls — Excel opens natively) ──
|
// ── Excel export (client-side, HTML-table .xls — Excel opens natively) ──
|
||||||
function exportExcel() {
|
async function exportExcel() {
|
||||||
if (!lastData) return;
|
const data = await fetchFullForExport();
|
||||||
const rows = lastData.rows ?? [];
|
if (!data) return;
|
||||||
const byCat = lastData.byCategory ?? [];
|
const rows = data.rows ?? [];
|
||||||
const alerts = lastData.alerts ?? [];
|
const byCat = data.byCategory ?? [];
|
||||||
const summary = lastData.summary ?? {};
|
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 => `<th>${_esc(h)}</th>`).join("");
|
.map(h => `<th>${_esc(h)}</th>`).join("");
|
||||||
|
|
||||||
const bodyRows = rows.map(r => `
|
const bodyRows = rows.map(r => `
|
||||||
@ -670,10 +848,13 @@
|
|||||||
<td>${_esc(r.itemName)}</td>
|
<td>${_esc(r.itemName)}</td>
|
||||||
<td>${_esc(r.itemNo)}</td>
|
<td>${_esc(r.itemNo)}</td>
|
||||||
<td>${_esc(r.itemCategoryName)}</td>
|
<td>${_esc(r.itemCategoryName)}</td>
|
||||||
<td>${_esc(r.unitPrice)}</td>
|
<td>${_esc(r.department ?? "")}</td>
|
||||||
|
<td>${_esc(r.currencyCode ?? "")}</td>
|
||||||
|
<td>${r.unitPrice}</td>
|
||||||
<td>${r.qtyIn}</td>
|
<td>${r.qtyIn}</td>
|
||||||
<td>${r.qtyOut}</td>
|
<td>${r.qtyOut}</td>
|
||||||
<td>${r.qtyOnHand}</td>
|
<td>${r.qtyOnHand}</td>
|
||||||
|
<td>${(Number(r.qtyOnHand)||0) * (Number(r.unitPrice)||0)}</td>
|
||||||
<td>${r.stockPct}</td>
|
<td>${r.stockPct}</td>
|
||||||
</tr>`).join("");
|
</tr>`).join("");
|
||||||
|
|
||||||
@ -690,14 +871,14 @@
|
|||||||
<head><meta charset="utf-8"></head>
|
<head><meta charset="utf-8"></head>
|
||||||
<body>
|
<body>
|
||||||
<table border="1">
|
<table border="1">
|
||||||
<tr><td colspan="8"><b>Inventory Summary Report</b></td></tr>
|
<tr><td colspan="11"><b>Inventory Summary Report</b></td></tr>
|
||||||
<tr><td>Company</td><td colspan="7">${_esc(lastData.companyName)}</td></tr>
|
<tr><td>Company</td><td colspan="10">${_esc(data.companyName)}</td></tr>
|
||||||
<tr><td>Report No</td><td colspan="7">${_esc(lastData.reportNo)}</td></tr>
|
<tr><td>Report No</td><td colspan="10">${_esc(data.reportNo)}</td></tr>
|
||||||
<tr><td>Period</td><td colspan="7">${_esc(fromEl.value)} to ${_esc(toEl.value)}</td></tr>
|
<tr><td>Period</td><td colspan="10">${_esc(fromEl.value)} to ${_esc(toEl.value)}</td></tr>
|
||||||
<tr></tr>
|
<tr></tr>
|
||||||
<tr>${headerCells}</tr>
|
<tr>${headerCells}</tr>
|
||||||
${bodyRows}
|
${bodyRows}
|
||||||
<tr><td colspan="4"><b>Total</b></td><td>${summary.totalQtyIn ?? 0}</td><td>${summary.totalQtyOut ?? 0}</td><td>${summary.totalOnHand ?? 0}</td><td></td></tr>
|
<tr><td colspan="6"><b>Total</b></td><td>${summary.totalQtyIn ?? 0}</td><td>${summary.totalQtyOut ?? 0}</td><td>${summary.totalOnHand ?? 0}</td><td>${summary.totalValue ?? 0}</td><td></td></tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<br/>
|
<br/>
|
||||||
|
|||||||
@ -7,32 +7,38 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@@media print {
|
@@media print {
|
||||||
body {
|
body > *:not(#print-mount) {
|
||||||
padding: 0;
|
display: none !important;
|
||||||
margin: 0;
|
|
||||||
background: #fff;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
body * {
|
#print-mount {
|
||||||
visibility: hidden;
|
display: block !important;
|
||||||
}
|
position: static !important;
|
||||||
|
|
||||||
#mrs-rpt-page, #mrs-rpt-page * {
|
|
||||||
visibility: visible;
|
|
||||||
}
|
|
||||||
|
|
||||||
#mrs-rpt-page {
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#print-mount .rpt-page {
|
||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
border: none !important;
|
border: none !important;
|
||||||
border-radius: 0 !important;
|
border-radius: 0 !important;
|
||||||
|
overflow: visible !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.no-print {
|
.rpt-section {
|
||||||
display: none !important;
|
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 {
|
.rpt-table tr {
|
||||||
@ -42,6 +48,11 @@
|
|||||||
thead {
|
thead {
|
||||||
display: table-header-group;
|
display: table-header-group;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@page {
|
||||||
|
size: A4 landscape;
|
||||||
|
margin: 5mm;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.rpt-page {
|
.rpt-page {
|
||||||
@ -197,7 +208,7 @@
|
|||||||
|
|
||||||
.kpi-strip {
|
.kpi-strip {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(4, 1fr);
|
grid-template-columns: repeat(3, 1fr);
|
||||||
gap: 0;
|
gap: 0;
|
||||||
border-bottom: 1px solid #d6eaec;
|
border-bottom: 1px solid #d6eaec;
|
||||||
}
|
}
|
||||||
@ -584,9 +595,53 @@
|
|||||||
return `<span class="pill ${cls}"><i class="fas ${icon}"></i> ${_esc(label)}</span>`;
|
return `<span class="pill ${cls}"><i class="fas ${icon}"></i> ${_esc(label)}</span>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) ──
|
// ── CSV export (MRS schema) ──
|
||||||
function exportCsv() {
|
async function exportCsv() {
|
||||||
|
const data = await fetchFullForExport();
|
||||||
|
|
||||||
if (!lastData) return;
|
if (!lastData) return;
|
||||||
const rows = lastData.rows ?? [];
|
const rows = lastData.rows ?? [];
|
||||||
const byCond = lastData.byCondition ?? [];
|
const byCond = lastData.byCondition ?? [];
|
||||||
@ -626,7 +681,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ── Excel export (MRS schema, HTML-table .xls) ──
|
// ── Excel export (MRS schema, HTML-table .xls) ──
|
||||||
function exportExcel() {
|
async function exportExcel() {
|
||||||
if (!lastData) return;
|
if (!lastData) return;
|
||||||
const rows = lastData.rows ?? [];
|
const rows = lastData.rows ?? [];
|
||||||
const byCond = lastData.byCondition ?? [];
|
const byCond = lastData.byCondition ?? [];
|
||||||
|
|||||||
@ -7,32 +7,38 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@@media print {
|
@@media print {
|
||||||
body {
|
body > *:not(#print-mount) {
|
||||||
padding: 0;
|
display: none !important;
|
||||||
margin: 0;
|
|
||||||
background: #fff;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
body * {
|
#print-mount {
|
||||||
visibility: hidden;
|
display: block !important;
|
||||||
}
|
position: static !important;
|
||||||
|
|
||||||
#ris-rpt-page, #ris-rpt-page * {
|
|
||||||
visibility: visible;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ris-rpt-page {
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#print-mount .ris-page {
|
||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
border: none !important;
|
border: none !important;
|
||||||
border-radius: 0 !important;
|
border-radius: 0 !important;
|
||||||
|
overflow: visible !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.no-print {
|
.rpt-section {
|
||||||
display: none !important;
|
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 {
|
.rpt-table tr {
|
||||||
@ -42,6 +48,11 @@
|
|||||||
thead {
|
thead {
|
||||||
display: table-header-group;
|
display: table-header-group;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@page {
|
||||||
|
size: A4 landscape;
|
||||||
|
margin: 5mm;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.rpt-page {
|
.rpt-page {
|
||||||
@ -197,7 +208,7 @@
|
|||||||
|
|
||||||
.kpi-strip {
|
.kpi-strip {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(4, 1fr);
|
grid-template-columns: repeat(3, 1fr);
|
||||||
gap: 0;
|
gap: 0;
|
||||||
border-bottom: 1px solid #d6eaec;
|
border-bottom: 1px solid #d6eaec;
|
||||||
}
|
}
|
||||||
@ -434,14 +445,61 @@
|
|||||||
}
|
}
|
||||||
csvBtn.addEventListener("click", exportCsv);
|
csvBtn.addEventListener("click", exportCsv);
|
||||||
genBtn.addEventListener("click", fetchAndRender);
|
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) ──
|
// ── CSV export (client-side, no backend needed) ──
|
||||||
function exportCsv() {
|
async function exportCsv() {
|
||||||
|
const data = await fetchFullForExport();
|
||||||
if (!lastData) return;
|
if (!lastData) return;
|
||||||
const rows = lastData.rows ?? [];
|
const rows = lastData.rows ?? [];
|
||||||
const byDisc = lastData.byDiscipline ?? [];
|
const byDisc = lastData.byDiscipline ?? [];
|
||||||
const topRecv = lastData.topRecipients ?? [];
|
const topRecv = lastData.topRecipients ?? [];
|
||||||
const summary = lastData.summary ?? {};
|
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 = [
|
const csvLines = [
|
||||||
`Return Issuance Slip Report`,
|
`Return Issuance Slip Report`,
|
||||||
@ -456,8 +514,8 @@
|
|||||||
].map(csvCell).join(",")),
|
].map(csvCell).join(",")),
|
||||||
["Total","","","","","", summary.totalQtyIssued ?? 0, summary.totalQtyReturned ?? 0, summary.totalNetIssued ?? 0, ""].map(csvCell).join(","),
|
["Total","","","","","", summary.totalQtyIssued ?? 0, summary.totalQtyReturned ?? 0, summary.totalNetIssued ?? 0, ""].map(csvCell).join(","),
|
||||||
``,
|
``,
|
||||||
`Issuance by Discipline`,
|
`Issuance by Trade`,
|
||||||
["Discipline","Slips"].map(csvCell).join(","),
|
["Trade","Slips"].map(csvCell).join(","),
|
||||||
...byDisc.map(d => [d.disciplineName, d.count].map(csvCell).join(",")),
|
...byDisc.map(d => [d.disciplineName, d.count].map(csvCell).join(",")),
|
||||||
``,
|
``,
|
||||||
`Top Recipients`,
|
`Top Recipients`,
|
||||||
@ -501,7 +559,7 @@
|
|||||||
<div class="rpt-company">${_esc(data.companyName ?? "")}</div>
|
<div class="rpt-company">${_esc(data.companyName ?? "")}</div>
|
||||||
<div class="rpt-title">Return Issuance Slip Report</div>
|
<div class="rpt-title">Return Issuance Slip Report</div>
|
||||||
<div class="rpt-subtitle">
|
<div class="rpt-subtitle">
|
||||||
Period: ${_fmtDate(data.dateFrom)} – ${_fmtDate(data.dateTo)} · All departments · All disciplines
|
Period: ${_fmtDate(data.dateFrom)} – ${_fmtDate(data.dateTo)} · All departments · All trades
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="rpt-logo"><i class="fas fa-file-export"></i></div>
|
<div class="rpt-logo"><i class="fas fa-file-export"></i></div>
|
||||||
@ -544,7 +602,7 @@
|
|||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>RIS No.</th><th>Date</th><th>Item</th><th>Item No.</th>
|
<th>RIS No.</th><th>Date</th><th>Item</th><th>Item No.</th>
|
||||||
<th>Discipline</th><th>Issued To</th>
|
<th>Trade</th><th>Project Name</th>
|
||||||
<th class="num">Qty Issued</th><th class="num">Qty Returned</th>
|
<th class="num">Qty Issued</th><th class="num">Qty Returned</th>
|
||||||
<th class="num">Net Out</th><th>Status</th>
|
<th class="num">Net Out</th><th>Status</th>
|
||||||
</tr>
|
</tr>
|
||||||
@ -576,7 +634,7 @@
|
|||||||
|
|
||||||
<div class="two-col">
|
<div class="two-col">
|
||||||
<div class="col-left">
|
<div class="col-left">
|
||||||
<div class="rpt-section-title"><i class="fas fa-chart-bar"></i> Issuance by discipline</div>
|
<div class="rpt-section-title"><i class="fas fa-chart-bar"></i> Issuance by trade</div>
|
||||||
<div style="display:flex;flex-direction:column;gap:8px">
|
<div style="display:flex;flex-direction:column;gap:8px">
|
||||||
${byDisc.map((d, i) => `
|
${byDisc.map((d, i) => `
|
||||||
<div>
|
<div>
|
||||||
@ -628,14 +686,14 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ── Excel export (client-side, HTML-table .xls — Excel opens natively) ──
|
// ── Excel export (client-side, HTML-table .xls — Excel opens natively) ──
|
||||||
function exportExcel() {
|
async function exportExcel() {
|
||||||
if (!lastData) return;
|
if (!lastData) return;
|
||||||
const rows = lastData.rows ?? [];
|
const rows = lastData.rows ?? [];
|
||||||
const byDisc = lastData.byDiscipline ?? [];
|
const byDisc = lastData.byDiscipline ?? [];
|
||||||
const topRecv = lastData.topRecipients ?? [];
|
const topRecv = lastData.topRecipients ?? [];
|
||||||
const summary = lastData.summary ?? {};
|
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 => `<th>${_esc(h)}</th>`).join("");
|
.map(h => `<th>${_esc(h)}</th>`).join("");
|
||||||
|
|
||||||
const bodyRows = rows.map(r => `
|
const bodyRows = rows.map(r => `
|
||||||
@ -677,8 +735,8 @@
|
|||||||
|
|
||||||
<br/>
|
<br/>
|
||||||
<table border="1">
|
<table border="1">
|
||||||
<tr><td colspan="2"><b>Issuance by Discipline</b></td></tr>
|
<tr><td colspan="2"><b>Issuance by Trade</b></td></tr>
|
||||||
<tr><th>Discipline</th><th>Slips</th></tr>
|
<tr><th>Trade</th><th>Slips</th></tr>
|
||||||
${discRows}
|
${discRows}
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
|||||||
@ -7,10 +7,27 @@
|
|||||||
<label class="rpt-date-lbl"><i class="fas fa-calendar-check"></i> To</label>
|
<label class="rpt-date-lbl"><i class="fas fa-calendar-check"></i> To</label>
|
||||||
<input type="date" id="inv-rpt-to" class="rpt-date-input">
|
<input type="date" id="inv-rpt-to" class="rpt-date-input">
|
||||||
</div>
|
</div>
|
||||||
|
<div class="inv-department-wrap no-print" id="inv-rpt-dept-wrap">
|
||||||
|
<div class="inv-dep-trigger" id="inv-rpt-dept-trigger">
|
||||||
|
<div class="inv-dep-left">
|
||||||
|
<i class="fas fa-building"></i>
|
||||||
|
<span class="inv-dep-lbl" id="inv-rpt-dept-lbl">All Department</span>
|
||||||
|
</div>
|
||||||
|
<i class="fas fa-chevron-down inv-dep-caret"></i>
|
||||||
|
</div>
|
||||||
|
<div class="inv-dep-dropdown" id="inv-rpt-dept-dropdown">
|
||||||
|
<div class="inv-dep-searchbox">
|
||||||
|
<i class="fas fa-search"></i>
|
||||||
|
<input type="text" id="inv-rpt-dept-search" placeholder="Search department name…">
|
||||||
|
</div>
|
||||||
|
<div class="inv-dep-list" id="inv-rpt-dept-list">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<button id="inv-rpt-generate" class="rpt-btn rpt-btn-primary">
|
<button id="inv-rpt-generate" class="rpt-btn rpt-btn-primary">
|
||||||
<i class="fas fa-sync"></i> Generate
|
<i class="fas fa-sync"></i> Generate
|
||||||
</button>
|
</button>
|
||||||
<button id="inv-rpt-print" class="rpt-btn rpt-btn-outline" onclick="window.print()">
|
<button id="inv-rpt-print" class="rpt-btn rpt-btn-outline no-print">
|
||||||
<i class="fas fa-print"></i> Print / PDF
|
<i class="fas fa-print"></i> Print / PDF
|
||||||
</button>
|
</button>
|
||||||
<button id="inv-rpt-csv" class="rpt-btn rpt-btn-outline">
|
<button id="inv-rpt-csv" class="rpt-btn rpt-btn-outline">
|
||||||
|
|||||||
@ -147,7 +147,6 @@ function clearCustomTable() {
|
|||||||
// =====================================================================
|
// =====================================================================
|
||||||
// UTILITIES
|
// UTILITIES
|
||||||
// =====================================================================
|
// =====================================================================
|
||||||
|
|
||||||
function getGrossFromTable() {
|
function getGrossFromTable() {
|
||||||
let gross = 0;
|
let gross = 0;
|
||||||
|
|
||||||
@ -171,16 +170,12 @@ function getChargesFromTable() {
|
|||||||
|
|
||||||
return charges;
|
return charges;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDiscount() {
|
function getDiscount() {
|
||||||
return parseFloat($('#discount').val()) || 0;
|
return parseFloat($('#discount').val()) || 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPoTypeId() {
|
function getPoTypeId() {
|
||||||
return parseInt($('#poTypeId').val()) || 0;
|
return parseInt($('#poTypeId').val()) || 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function recalculateAll() {
|
function recalculateAll() {
|
||||||
const poTypeId = getPoTypeId();
|
const poTypeId = getPoTypeId();
|
||||||
const grossAmount = getGrossFromTable();
|
const grossAmount = getGrossFromTable();
|
||||||
|
|||||||
217
CPRNIMS.WebApps/wwwroot/Reports/RIS_v2.frx
Normal file
217
CPRNIMS.WebApps/wwwroot/Reports/RIS_v2.frx
Normal file
@ -0,0 +1,217 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Report ScriptLanguage="CSharp" ReportInfo.Created="06/16/2026 15:54:16" ReportInfo.Modified="06/17/2026 16:02:50" ReportInfo.CreatorVersion="2024.2.0.0">
|
||||||
|
<Dictionary>
|
||||||
|
|
||||||
|
<TableDataSource Name="TRIS" Alias="TRIS" DataType="System.Int32" Enabled="true">
|
||||||
|
<Column Name="RISNo" DataType="System.String"/>
|
||||||
|
<Column Name="PRNo" DataType="System.Int64"/>
|
||||||
|
<Column Name="QtyIssued" DataType="System.Decimal"/>
|
||||||
|
<Column Name="IssuedTo" DataType="System.String"/>
|
||||||
|
<Column Name="StatusLabel" Alias="Status" DataType="System.String"/>
|
||||||
|
<Column Name="CreatedBy" DataType="System.String"/>
|
||||||
|
<Column Name="ApprovedBy" DataType="System.String"/>
|
||||||
|
<Column Name="ApprovedDate" DataType="System.DateTime"/>
|
||||||
|
<Column Name="DisciplineName" Alias="Discipline" DataType="System.String"/>
|
||||||
|
<Column Name="ItemName" DataType="System.String"/>
|
||||||
|
<Column Name="ItemNo" DataType="System.Int64"/>
|
||||||
|
<Column Name="QtyIn" DataType="System.Decimal"/>
|
||||||
|
<Column Name="QtyOut" DataType="System.Decimal"/>
|
||||||
|
<Column Name="QtyOnHand" DataType="System.Decimal"/>
|
||||||
|
<Column Name="DepartmentName" Alias="Department" DataType="System.String"/>
|
||||||
|
<Column Name="TotalReturned" DataType="System.Decimal"/>
|
||||||
|
<Column Name="MRSCount" Enabled="false" DataType="System.Int32"/>
|
||||||
|
<Column Name="NetIssued" DataType="System.Decimal"/>
|
||||||
|
<CommandParameter Name="DateFrom" DataType="22"/>
|
||||||
|
<CommandParameter Name="DateTo" DataType="22"/>
|
||||||
|
</TableDataSource>
|
||||||
|
<TableDataSource Name="TDisciplineAgg" Alias="TDisciplineAgg" DataType="System.Int32" Enabled="true">
|
||||||
|
<Column Name="DisciplineName" DataType="System.String"/>
|
||||||
|
<Column Name="SlipCount" DataType="System.Int32"/>
|
||||||
|
<CommandParameter Name="DateFrom" DataType="22"/>
|
||||||
|
<CommandParameter Name="DateTo" DataType="22"/>
|
||||||
|
</TableDataSource>
|
||||||
|
<TableDataSource Name="TTopRecipients" Alias="TTopRecipients" DataType="System.Int32" Enabled="true">
|
||||||
|
<Column Name="Name" DataType="System.String"/>
|
||||||
|
<Column Name="SlipCount" DataType="System.Int32"/>
|
||||||
|
<Column Name="QtyOut" DataType="System.Decimal"/>
|
||||||
|
<CommandParameter Name="DateFrom" DataType="22"/>
|
||||||
|
<CommandParameter Name="DateTo" DataType="22"/>
|
||||||
|
</TableDataSource>
|
||||||
|
<Parameter Name="PreparedBy" DataType="System.String" AsString=""/>
|
||||||
|
<Parameter Name="PrintDate" DataType="System.String" AsString=""/>
|
||||||
|
<Parameter Name="ReportNo" DataType="System.String" AsString=""/>
|
||||||
|
<Parameter Name="TotalRISIssued" DataType="System.String" AsString=""/>
|
||||||
|
<Parameter Name="ApprovedRIS" DataType="System.String" AsString=""/>
|
||||||
|
</Dictionary>
|
||||||
|
<ReportPage Name="Page1" PaperWidth="216" PaperHeight="279" RawPaperSize="1" Watermark.Font="Arial, 60pt">
|
||||||
|
<ReportTitleBand Name="ReportTitle1" Width="740.88" Height="56.7">
|
||||||
|
<TextObject Name="Text1" Left="179.55" Top="18.9" Width="311.85" Height="28.35" Text="Return Issuance Slip Report" HorzAlign="Center" Font="Segoe UI, 18pt"/>
|
||||||
|
<TextObject Name="Text3" Width="283.5" Height="18.9" Text="LLOYD LABORATORIES INCORPORATED" Font="Arial, 10pt"/>
|
||||||
|
<TextObject Name="Text7" Left="-9450" Top="-9450" Width="94.5" Height="18.9" Text="PreparedBy" Font="Arial, 10pt"/>
|
||||||
|
<TextObject Name="Text11" Left="-9450" Top="-9450" Width="94.5" Height="18.9" Text="Table_RIS_Query" Font="Arial, 10pt"/>
|
||||||
|
</ReportTitleBand>
|
||||||
|
<PageHeaderBand Name="PageHeader1" Top="59.9" Width="740.88" Height="42.05">
|
||||||
|
<TextObject Name="Text2" Width="113.4" Height="18.9" Text="PREPARED BY" Font="Arial, 10pt"/>
|
||||||
|
<TextObject Name="Text4" Left="151.2" Width="94.5" Height="18.9" Text="PRINT DATE" Font="Arial, 10pt"/>
|
||||||
|
<TextObject Name="Text5" Left="302.4" Width="94.5" Height="18.9" Text="REPORT NO." Font="Arial, 10pt"/>
|
||||||
|
<TextObject Name="Text6" Top="18.9" Width="94.5" Height="18.9" Text="[PreparedBy]" Font="Arial, 10pt"/>
|
||||||
|
<TextObject Name="Text9" Left="302.4" Top="18.9" Width="94.5" Height="18.9" Text="[ReportNo]" Font="Arial, 10pt"/>
|
||||||
|
<TextObject Name="Text10" Left="154.5" Top="23.15" Width="94.5" Height="18.9" Text="[Date]" Font="Arial, 10pt"/>
|
||||||
|
<TextObject Name="Text8" Left="538.65" Width="94.5" Height="18.9" Text="QtyIssued" Font="Arial, 10pt"/>
|
||||||
|
<TextObject Name="Text15" Left="670.95" Width="94.5" Height="18.9" Text="NetIssued" Font="Arial, 10pt"/>
|
||||||
|
</PageHeaderBand>
|
||||||
|
<DataBand Name="Data1" Top="105.15" Width="740.88" Height="529.2" DataSource="Table">
|
||||||
|
<TableObject Name="TRIS" Width="803.2" Height="311.85">
|
||||||
|
<TableColumn Name="Column6" Width="42.85"/>
|
||||||
|
<TableColumn Name="Column7" Width="42.85"/>
|
||||||
|
<TableColumn Name="Column8" Width="146.77"/>
|
||||||
|
<TableColumn Name="Column11" Width="59.84"/>
|
||||||
|
<TableColumn Name="Column9" Width="90.07"/>
|
||||||
|
<TableColumn Name="Column10" Width="90.07"/>
|
||||||
|
<TableColumn Name="Column20"/>
|
||||||
|
<TableColumn Name="Column21"/>
|
||||||
|
<TableColumn Name="Column22"/>
|
||||||
|
<TableColumn Name="Column23"/>
|
||||||
|
<TableColumn Name="Column24"/>
|
||||||
|
<TableRow Name="Row6" Height="20.79">
|
||||||
|
<TableCell Name="Cell26" Text="[TRIS.RISNo]" Font="Arial, 10pt"/>
|
||||||
|
<TableCell Name="Cell27" Text="[TRIS.PRNo]" Font="Arial, 10pt"/>
|
||||||
|
<TableCell Name="Cell28" Text="[TRIS.ItemName]" Font="Arial, 10pt"/>
|
||||||
|
<TableCell Name="Cell51" Text="[TRIS.ItemNo]" Font="Arial, 10pt"/>
|
||||||
|
<TableCell Name="Cell29" Text="[TRIS.Discipline]" Font="Arial, 10pt"/>
|
||||||
|
<TableCell Name="Cell30" Text="[TRIS.IssuedTo]" Font="Arial, 10pt">
|
||||||
|
<TextObject Name="Text12" Left="75.6" Width="66.15" Height="18.9" Text="[TRIS.QtyIssued]" Format="Currency" Format.UseLocale="true" Format.DecimalDigits="2" HorzAlign="Right" WordWrap="false" Font="Arial, 10pt" Trimming="EllipsisCharacter"/>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell Name="Cell112" Font="Arial, 10pt"/>
|
||||||
|
<TableCell Name="Cell117" Text="[TRIS.TotalReturned]" Font="Arial, 10pt"/>
|
||||||
|
<TableCell Name="Cell122" Text="[TRIS.NetIssued]" Font="Arial, 10pt"/>
|
||||||
|
<TableCell Name="Cell127" Text="[TRIS.Status]" Font="Arial, 10pt"/>
|
||||||
|
<TableCell Name="Cell132" Font="Arial, 10pt"/>
|
||||||
|
</TableRow>
|
||||||
|
<TableRow Name="Row7" Height="20.79">
|
||||||
|
<TableCell Name="Cell31" Font="Arial, 10pt"/>
|
||||||
|
<TableCell Name="Cell32" Font="Arial, 10pt"/>
|
||||||
|
<TableCell Name="Cell33" Font="Arial, 10pt"/>
|
||||||
|
<TableCell Name="Cell52" Font="Arial, 10pt"/>
|
||||||
|
<TableCell Name="Cell34" Font="Arial, 10pt"/>
|
||||||
|
<TableCell Name="Cell35" Font="Arial, 10pt"/>
|
||||||
|
<TableCell Name="Cell113" Font="Arial, 10pt"/>
|
||||||
|
<TableCell Name="Cell118" Font="Arial, 10pt"/>
|
||||||
|
<TableCell Name="Cell123" Font="Arial, 10pt"/>
|
||||||
|
<TableCell Name="Cell128" Font="Arial, 10pt"/>
|
||||||
|
<TableCell Name="Cell133" Font="Arial, 10pt"/>
|
||||||
|
</TableRow>
|
||||||
|
<TableRow Name="Row8" Height="20.79">
|
||||||
|
<TableCell Name="Cell36" Font="Arial, 10pt"/>
|
||||||
|
<TableCell Name="Cell37" Font="Arial, 10pt"/>
|
||||||
|
<TableCell Name="Cell38" Font="Arial, 10pt"/>
|
||||||
|
<TableCell Name="Cell53" Font="Arial, 10pt"/>
|
||||||
|
<TableCell Name="Cell39" Font="Arial, 10pt"/>
|
||||||
|
<TableCell Name="Cell40" Font="Arial, 10pt"/>
|
||||||
|
<TableCell Name="Cell114" Font="Arial, 10pt"/>
|
||||||
|
<TableCell Name="Cell119" Font="Arial, 10pt"/>
|
||||||
|
<TableCell Name="Cell124" Font="Arial, 10pt"/>
|
||||||
|
<TableCell Name="Cell129" Font="Arial, 10pt"/>
|
||||||
|
<TableCell Name="Cell134" Font="Arial, 10pt"/>
|
||||||
|
</TableRow>
|
||||||
|
<TableRow Name="Row9" Height="20.79">
|
||||||
|
<TableCell Name="Cell41" Font="Arial, 10pt"/>
|
||||||
|
<TableCell Name="Cell42" Font="Arial, 10pt"/>
|
||||||
|
<TableCell Name="Cell43" Font="Arial, 10pt"/>
|
||||||
|
<TableCell Name="Cell54" Font="Arial, 10pt"/>
|
||||||
|
<TableCell Name="Cell44" Font="Arial, 10pt"/>
|
||||||
|
<TableCell Name="Cell45" Font="Arial, 10pt"/>
|
||||||
|
<TableCell Name="Cell115" Font="Arial, 10pt"/>
|
||||||
|
<TableCell Name="Cell120" Font="Arial, 10pt"/>
|
||||||
|
<TableCell Name="Cell125" Font="Arial, 10pt"/>
|
||||||
|
<TableCell Name="Cell130" Font="Arial, 10pt"/>
|
||||||
|
<TableCell Name="Cell135" Font="Arial, 10pt"/>
|
||||||
|
</TableRow>
|
||||||
|
<TableRow Name="Row10" Height="228.69">
|
||||||
|
<TableCell Name="Cell46" Font="Arial, 10pt"/>
|
||||||
|
<TableCell Name="Cell47" Font="Arial, 10pt"/>
|
||||||
|
<TableCell Name="Cell48" Font="Arial, 10pt"/>
|
||||||
|
<TableCell Name="Cell55" Font="Arial, 10pt"/>
|
||||||
|
<TableCell Name="Cell49" Font="Arial, 10pt"/>
|
||||||
|
<TableCell Name="Cell50" Font="Arial, 10pt"/>
|
||||||
|
<TableCell Name="Cell116" Font="Arial, 10pt"/>
|
||||||
|
<TableCell Name="Cell121" Font="Arial, 10pt"/>
|
||||||
|
<TableCell Name="Cell126" Font="Arial, 10pt"/>
|
||||||
|
<TableCell Name="Cell131" Font="Arial, 10pt"/>
|
||||||
|
<TableCell Name="Cell136" Font="Arial, 10pt"/>
|
||||||
|
</TableRow>
|
||||||
|
</TableObject>
|
||||||
|
<TextObject Name="Text18" Left="378" Top="340.2" Width="141.75" Height="18.9" Text="TOP RECIPIENTS" Font="Arial, 10pt"/>
|
||||||
|
<TableObject Name="TTopRecipients" Left="378" Top="359.1" Width="321.3" Height="113.37" PrintOnParent="true">
|
||||||
|
<TableColumn Name="Column17" Width="189"/>
|
||||||
|
<TableColumn Name="Column18"/>
|
||||||
|
<TableColumn Name="Column19"/>
|
||||||
|
<TableRow Name="Row18" Height="25.98">
|
||||||
|
<TableCell Name="Cell91" Text="[TTopRecipients.Name]" Font="Arial, 10pt"/>
|
||||||
|
<TableCell Name="Cell92" Text="[TTopRecipients.SlipCount]" Font="Arial, 10pt"/>
|
||||||
|
<TableCell Name="Cell108" Text="[TTopRecipients.QtyOut]" Font="Arial, 10pt"/>
|
||||||
|
</TableRow>
|
||||||
|
<TableRow Name="Row19" Height="25.98">
|
||||||
|
<TableCell Name="Cell96" Font="Arial, 10pt"/>
|
||||||
|
<TableCell Name="Cell97" Font="Arial, 10pt"/>
|
||||||
|
<TableCell Name="Cell109" Font="Arial, 10pt"/>
|
||||||
|
</TableRow>
|
||||||
|
<TableRow Name="Row20" Height="25.98">
|
||||||
|
<TableCell Name="Cell101" Font="Arial, 10pt"/>
|
||||||
|
<TableCell Name="Cell102" Font="Arial, 10pt"/>
|
||||||
|
<TableCell Name="Cell110" Font="Arial, 10pt"/>
|
||||||
|
</TableRow>
|
||||||
|
<TableRow Name="Row21" Height="35.43">
|
||||||
|
<TableCell Name="Cell106" Font="Arial, 10pt"/>
|
||||||
|
<TableCell Name="Cell107" Font="Arial, 10pt"/>
|
||||||
|
<TableCell Name="Cell111" Font="Arial, 10pt"/>
|
||||||
|
</TableRow>
|
||||||
|
</TableObject>
|
||||||
|
<TextObject Name="Text17" Top="340.2" Width="189" Height="18.9" Text="ISSUANCE BY DISCIPLINE" Font="Arial, 10pt"/>
|
||||||
|
<TableObject Name="TDisciplineAgg" Top="359.1" Width="311.42" Height="113.4" PrintOnParent="true">
|
||||||
|
<TableColumn Name="Column12" Width="200.79"/>
|
||||||
|
<TableColumn Name="Column13" Width="87.39"/>
|
||||||
|
<TableColumn Name="Column15" Width="22.24"/>
|
||||||
|
<TableColumn Name="Column16" Width="1"/>
|
||||||
|
<TableRow Name="Row11">
|
||||||
|
<TableCell Name="Cell56" Text="[TDisciplineAgg.DisciplineName]" Font="Arial, 10pt"/>
|
||||||
|
<TableCell Name="Cell57" Text="[TDisciplineAgg.SlipCount]" Font="Arial, 10pt"/>
|
||||||
|
<TableCell Name="Cell59" Font="Arial, 10pt"/>
|
||||||
|
<TableCell Name="Cell60" Font="Arial, 10pt"/>
|
||||||
|
</TableRow>
|
||||||
|
<TableRow Name="Row16">
|
||||||
|
<TableCell Name="Cell81" Font="Arial, 10pt"/>
|
||||||
|
<TableCell Name="Cell82" Font="Arial, 10pt"/>
|
||||||
|
<TableCell Name="Cell84" Font="Arial, 10pt"/>
|
||||||
|
<TableCell Name="Cell85" Font="Arial, 10pt"/>
|
||||||
|
</TableRow>
|
||||||
|
<TableRow Name="Row12">
|
||||||
|
<TableCell Name="Cell61" Font="Arial, 10pt"/>
|
||||||
|
<TableCell Name="Cell62" Font="Arial, 10pt"/>
|
||||||
|
<TableCell Name="Cell64" Font="Arial, 10pt"/>
|
||||||
|
<TableCell Name="Cell65" Font="Arial, 10pt"/>
|
||||||
|
</TableRow>
|
||||||
|
<TableRow Name="Row13">
|
||||||
|
<TableCell Name="Cell66" Font="Arial, 10pt"/>
|
||||||
|
<TableCell Name="Cell67" Font="Arial, 10pt"/>
|
||||||
|
<TableCell Name="Cell69" Font="Arial, 10pt"/>
|
||||||
|
<TableCell Name="Cell70" Font="Arial, 10pt"/>
|
||||||
|
</TableRow>
|
||||||
|
<TableRow Name="Row14">
|
||||||
|
<TableCell Name="Cell71" Font="Arial, 10pt"/>
|
||||||
|
<TableCell Name="Cell72" Font="Arial, 10pt"/>
|
||||||
|
<TableCell Name="Cell74" Font="Arial, 10pt"/>
|
||||||
|
<TableCell Name="Cell75" Font="Arial, 10pt"/>
|
||||||
|
</TableRow>
|
||||||
|
<TableRow Name="Row15">
|
||||||
|
<TableCell Name="Cell76" Font="Arial, 10pt"/>
|
||||||
|
<TableCell Name="Cell77" Font="Arial, 10pt"/>
|
||||||
|
<TableCell Name="Cell79" Font="Arial, 10pt"/>
|
||||||
|
<TableCell Name="Cell80" Font="Arial, 10pt"/>
|
||||||
|
</TableRow>
|
||||||
|
</TableObject>
|
||||||
|
</DataBand>
|
||||||
|
<ReportSummaryBand Name="ReportSummary1" Top="637.55" Width="740.88" Height="37.8"/>
|
||||||
|
<PageFooterBand Name="PageFooter1" Top="678.55" Width="740.88" Height="604.8"/>
|
||||||
|
</ReportPage>
|
||||||
|
</Report>
|
||||||
Loading…
Reference in New Issue
Block a user