using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Newtonsoft.Json; using PSTW_CentralSystem.Areas.Inventory.Models; using PSTW_CentralSystem.DBContext; using PSTW_CentralSystem.Models; namespace PSTW_CentralSystem.Controllers.API { [ApiController] [Route("[controller]")] [Authorize] public class ReportingAPI : Controller { private readonly ILogger _logger; private readonly CentralSystemContext _centralDbContext; private readonly UserManager _userManager; private readonly RoleManager _roleManager; public ReportingAPI(ILogger logger, CentralSystemContext centralDbContext, UserManager userManager, RoleManager roleManager) { _logger = logger; _centralDbContext = centralDbContext; _userManager = userManager; _roleManager = roleManager; } public class ProductReport { public string? ProductName { get; set; } = default!; public int Quantity { get; set; } public float TotalPrice { get; set; } } public class ReportQuery { public string? FormDate { get; set; } public string? DeptId { get; set; } } #region ItemReport [HttpPost("GetInventoryReport/{deptId}")] public async Task GetInventoryReport(int? deptId) { try { var user = await _userManager.GetUserAsync(User); List items = new List(); if (deptId == null || deptId == 0) { items = await _centralDbContext.Items .Include("CreatedBy") .Include("Department") .Include("Product") .ToListAsync(); } else { items = await _centralDbContext.Items .Include("CreatedBy") .Include("Department") .Include("Product") .Where(i => i.DepartmentId == deptId) .ToListAsync(); } var itemListWithDetails = items.Select(item => new { item.ItemID, item.UniqueID, item.CompanyId, item.DepartmentId, item.ProductId, item.SerialNumber, item.Quantity, item.Supplier, PurchaseDate = item.PurchaseDate.ToString("dd/MM/yyyy"), item.PONo, item.Currency, item.DefaultPrice, item.CurrencyRate, item.ConvertPrice, item.DODate, item.Warranty, EndWDate = item.EndWDate.ToString("dd/MM/yyyy"), InvoiceDate = item.InvoiceDate?.ToString("dd/MM/yyyy"), item.Department?.DepartmentName, CreatedBy = item.CreatedBy!.UserName, item.Product!.ProductName, item.Product!.ProductShortName, item.Product!.Category, //CurrentUser = item.Movement?.FromUser?.UserName, CurrentUser = item.Movement?.FromUser?.UserName, CurrentStore = item.Movement?.FromStore?.StoreName, CurrentStation = item.Movement?.FromStation?.StationName, QRString = $"{HttpContext.Request.Scheme}://{HttpContext.Request.Host.Value}/I/{item.UniqueID}" // Generate QR String }).ToList(); int itemCountRegistered = items.Count; int itemCountStillInStock = items.Where(i => i.Quantity > 0).Count(); var itemsMovementsThisMonth = _centralDbContext.ItemMovements .Where(i => i.Date.Month == DateTime.Now.Month); int itemCountRegisteredThisMonth = itemsMovementsThisMonth.Count(i => i.Action == "Register"); int itemCountStockOutThisMonth = itemsMovementsThisMonth.Count(i => i.Action == "Stock Out"); var lastMonth = DateTime.Now.AddMonths(-1).Month; var itemsMovementsLastMonth = _centralDbContext.ItemMovements .Where(i => i.Date.Month == lastMonth); int itemCountRegisteredLastMonth = itemsMovementsLastMonth.Count(i => i.Action == "Register"); int itemCountStockOutLastMonth = itemsMovementsLastMonth.Count(i => i.Action == "Stock Out"); var report = new { itemCountRegistered, itemCountStillInStock, itemCountRegisteredThisMonth, itemCountStockOutThisMonth, itemCountRegisteredLastMonth, itemCountStockOutLastMonth }; return Json(report); } catch (Exception ex) { return BadRequest(ex.Message); } } #endregion #region Monthly Report [HttpPost("GetMonthlyReport")] public async Task GetMonthlyReport([FromBody] ReportQuery reportQuery) { try { string FormDate = reportQuery.FormDate!; string DeptId = reportQuery.DeptId!; DateTime parsedDate = DateTime.Parse(FormDate); var currentProductBalance = new List(); var productData = await _centralDbContext.Products .Where(p => p.QuantityJSON != null) .Select(p => new { p.ProductName, p.QuantityJSON, }) .ToListAsync(); foreach (var p in productData) { var deptDict = JsonConvert.DeserializeObject>(p.QuantityJSON!); } var departmentUsers = await _centralDbContext.Users .Include(u => u.Department) .Where(u => u.Department!.DepartmentCode == DeptId) .ToListAsync(); var departmentStations = await _centralDbContext.Stations .Include(s => s.StationPic) .Include(s => s.Department) .Where(s => s.Department!.DepartmentCode == DeptId) .ToListAsync(); var departmentStores = await _centralDbContext.Departments .Include(s => s.Company) .Where(s => s.DepartmentCode == DeptId) .ToListAsync(); var latestItemMovements = await _centralDbContext.ItemMovements .Include(m => m.Item) .Include(m => m.Item!.Product) .Include(m => m.NextUser) .Include(m => m.NextStation) .Include(m => m.NextStore) .Where(m => m.MovementComplete == true) .GroupBy(m => m.ItemId) .Select(g => g .Where(m => m.Date <= parsedDate) .OrderByDescending(m => m.Id) .FirstOrDefault()) .ToListAsync(); var exactMonthStockOut = latestItemMovements .Where(m => m != null && (m.Action!.ToLower() == "stockout" || m.Action!.ToLower() == "stock out") && m.Date.Month == parsedDate.Month) .Select(m => new { UniqueID = m!.Item!.UniqueID, From = m!.LastUser != null ? m.FromUser!.UserName : m.LastStore != null ? m.FromStore!.StoreName : m.LastStation != null ? m.FromStation!.StationName : "Unknown", To = m.NextUser != null ? m.NextUser!.UserName : m.NextStore != null ? m.NextStore!.StoreName : m.NextStation != null ? m.NextStation!.StationName : "Unknown", Item = m.Item!.Product!.ProductName, Quantity = m.Quantity, Date = m.Date, Price = m.Item!.ConvertPrice * m.Quantity, }) .ToList(); var exactMonthStockIn = latestItemMovements .Where(m => m != null && (m.Action!.ToLower() == "stockin" || m.Action!.ToLower() == "stock in") && m.Date.Month == parsedDate.Month) .Select(m => new { UniqueID = m!.Item!.UniqueID, From = m!.LastUser != null ? m.FromUser!.UserName : m.LastStore != null ? m.FromStore!.StoreName : m.LastStation != null ? m.FromStation!.StationName : "Unknown", To = m.NextUser != null ? m.NextUser!.UserName : m.NextStore != null ? m.NextStore!.StoreName : m.NextStation != null ? m.NextStation!.StationName : "Unknown", Item = m.Item!.Product!.ProductName, Quantity = m.Quantity, Date = m.Date, Price = m.Item!.ConvertPrice * m.Quantity, }) .ToList(); //select latestItemMovements with the Touser is not null var latestUserItemMovements = latestItemMovements.Where(m => m != null && m.ItemId != null && m.ToUser > 0 ).ToList(); var latestStationItemMovements = latestItemMovements.Where(m => m != null && m.ItemId != null && m.ToStation > 0).ToList(); var latestStoreItemMovements = latestItemMovements.Where(m => m != null && m.ItemId != null && m.ToStore > 0).ToList(); var usersItemMovements = departmentUsers .Select(u => new { UserId = u.Id, UserName = u.UserName, UserFullName = u.FullName, Items = latestUserItemMovements .Where(m => m != null && m.ToUser == u.Id) .Select(m => new { ItemId = m!.ItemId, ItemName = m.Item!.Product!.ProductName, Quantity = m.Quantity, ItemPrice = m.Item!.ConvertPrice, PO = m.Item!.PONo, DO = m.Item!.DONo, SerialNumber = m.Item.SerialNumber, UniqueID = m.Item.UniqueID, }) .ToList(), TotalItemPrice = latestItemMovements.Where(m => m != null && m.ToUser == u.Id).Select(m => m!.Item!.ConvertPrice).Sum(), }) .Where(u => u.Items.Count > 0).ToList(); var stationItemMovements = departmentStations .Select(u => new { StationName = u.StationName, StationPic = u.StationPic?.FullName, Items = latestStationItemMovements .Where(m => m != null && m.ToStation == u.StationId) .Select(m => new { ItemId = m!.ItemId, ItemName = m.Item!.Product!.ProductName, Quantity = m.Quantity, ItemPrice = m.Item!.ConvertPrice, PO = m.Item!.PONo, DO = m.Item!.DONo, SerialNumber = m.Item.SerialNumber, UniqueID = m.Item.UniqueID, }) .ToList(), TotalItemPrice = latestItemMovements.Where(m => m != null && m.ToStation == u.StationId).Select(m => m!.Item!.ConvertPrice).Sum(), }) .Where(u => u.Items.Count > 0).FirstOrDefault(); var storeItemMovements = departmentStores .Select(u => new { Department = u.DepartmentName, Items = latestStoreItemMovements .Where(m => m != null && m.ToStore > 0 && m.NextStore!.StoreName.Contains(u.DepartmentName)) .Select(m => new { ItemId = m!.ItemId, ItemName = m.Item!.Product!.ProductName, Quantity = m.Quantity, ItemPrice = m.Item!.ConvertPrice, PO = m.Item!.PONo, DO = m.Item!.DONo, SerialNumber = m.Item.SerialNumber, UniqueID = m.Item.UniqueID, }) .ToList(), TotalItemPrice = latestStoreItemMovements.Where(m => m != null && m.ToStore > 0 && m.NextStore!.StoreName.Contains(u.DepartmentName)).Select(m => m!.Item!.ConvertPrice).Sum(), }) .Where(u => u.Items.Count > 0).FirstOrDefault(); var report = new { allProductInStore = currentProductBalance, userItemBalance = usersItemMovements, stationItemBalance = stationItemMovements, storeItemBalance = storeItemMovements, exactMonthStockIn = exactMonthStockIn, exactMonthStockOut = exactMonthStockOut, }; return Json(report); } catch (Exception ex) { return BadRequest(ex.Message); } } #endregion #region Item Movement report [HttpPost("GetItemMovement")] public async Task GetItemMovement([FromBody] String data) { try { if (string.IsNullOrEmpty(data)) { return BadRequest("Invalid Unique ID"); } var itemMovement = await _centralDbContext.ItemMovements .Include(im => im.Item) .Where(im => im.Item!.UniqueID == data) .OrderByDescending(im => im.Id) .Select(im => new { From = im.LastUser != null ? im.FromUser!.FullName : im.LastStation != null ? im.FromStation!.StationName : im.LastStore != null ? im.FromStore!.StoreName : null, To = im.ToUser != null ? im.NextUser!.FullName : im.ToStation != null ? im.NextStation!.StationName : im.ToStore != null ? im.NextStore!.StoreName : null, Date = im.Date, MovementStatus = im.MovementComplete }) .ToListAsync(); return Json(itemMovement); } catch (Exception ex) { return BadRequest(ex.Message); } } #endregion #region Report Inventory ii [HttpPost("GetInventoryReport")] public async Task GetInventoryReport([FromBody] ReportFilterDTO filter) { try { int? deptId = filter.DeptId; // 1. Setup the Date Range DateTime start = new DateTime(2000, 1, 1); DateTime end = DateTime.Now.AddDays(1); if (filter.IsMonthMode && filter.Month.HasValue && filter.Year.HasValue) { start = new DateTime(filter.Year.Value, filter.Month.Value, 1); end = start.AddMonths(1).AddDays(-1).AddHours(23).AddMinutes(59); } else if (filter.StartDate.HasValue && filter.EndDate.HasValue) { start = filter.StartDate.Value; end = filter.EndDate.Value.AddHours(23).AddMinutes(59); } // 2. Fetch Movements DURING the range (Required for Stock Issue logic) var movementsInRange = await _centralDbContext.ItemMovements .Include(m => m.Item).ThenInclude(i => i!.Product) .Include(m => m.FromStation).Include(m => m.FromUser) .Include(m => m.NextStation).Include(m => m.NextUser) .Include(m => m.NextStore) .Where(m => m.Date >= start && m.Date <= end) .OrderBy(m => m.Date) .ToListAsync(); var totalGlobalProductQuantity = await _centralDbContext.Products.SumAsync(p => p.QuantityProduct ?? 0); // 3. Filter Items based on Department IQueryable itemQuery = _centralDbContext.Items .Include(i => i.CreatedBy) .Include(i => i.Department) .Include(i => i.Product); if (deptId != null && deptId != 0) { itemQuery = itemQuery.Where(i => i.DepartmentId == deptId); } var items = await itemQuery.ToListAsync(); // 4. Generate Issued Items List var latestMovementsPerItem = movementsInRange .Where(m => m.ItemId.HasValue) .GroupBy(m => m.ItemId) .Select(g => g.OrderByDescending(m => m.Date).ThenByDescending(m => m.Id).First()) .ToList(); var issuedItemsList = latestMovementsPerItem.Where(m => { string? status = m.LatestStatus; string? action = m.Action; string? other = m.ToOther; if (string.IsNullOrEmpty(status)) { if (other == "Faulty") return false; return (action == "Stock Out" || other == "Return"); } if (status == "Ready to deploy" || status == "Cancelled") return false; if (status == "Delivered") { return (action == "Stock Out" || action == "Assign"); } return false; }) .Select(m => new { uniqueID = m.Item?.UniqueID, description = m.Item?.Product != null ? $"{m.Item.Product.ProductName} ({m.Item.Product.Category})" : "N/A", quantity = m.Quantity ?? 0, unitPrice = m.Item?.ConvertPrice ?? 0, stationName = m.NextStation?.StationName ?? "N/A", requestorName = m.NextUser?.UserName ?? "N/A", action = m.Action, status = m.LatestStatus ?? "N/A", other = m.ToOther ?? "" }).ToList(); // 5. Final Output (Removed balanceReport and stockCardLogs) var finalReport = new { itemCountRegistered = totalGlobalProductQuantity, itemCountStillInStock = items.Sum(i => i.Quantity), receivedItems = items.Where(i => i.PurchaseDate >= start && i.PurchaseDate <= end).Select(i => new { i.ItemID, i.UniqueID, PurchaseDate = i.PurchaseDate.ToString("dd/MM/yyyy"), i.InvoiceNo, // Add this line below to get the InvoiceDate formatted InvoiceDate = i.InvoiceDate.HasValue ? i.InvoiceDate.Value.ToString("dd/MM/yyyy") : "N/A", i.Supplier, ProductName = i.Product?.ProductName, Category = i.Product?.Category, i.Quantity, i.Currency, i.CurrencyRate, i.ConvertPrice }).ToList(), issuedItems = issuedItemsList, selectedPeriodLabel = (filter.Month == null && filter.StartDate == null) ? "All Time" : (filter.IsMonthMode ? $"{filter.Month}/{filter.Year}" : $"{start:dd/MM/yyyy} - {end:dd/MM/yyyy}") }; return Json(finalReport); } catch (Exception ex) { return BadRequest(ex.Message); } } public class ReportFilterDTO { public int? DeptId { get; set; } public bool IsMonthMode { get; set; } public int? Month { get; set; } public int? Year { get; set; } public DateTime? StartDate { get; set; } public DateTime? EndDate { get; set; } } #endregion } }