Getting the monthly expenses report based on project and category
This commit is contained in:
parent
91f4305995
commit
548e714ea9
@ -14,6 +14,7 @@ using MarcoBMS.Services.Service;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Marco.Pms.Services.Controllers
|
||||
{
|
||||
@ -614,23 +615,12 @@ namespace Marco.Pms.Services.Controllers
|
||||
return Ok(ApiResponse<object>.SuccessResponse(sortedResult, $"{sortedResult.Count} records fetched for attendance overview", 200));
|
||||
}
|
||||
|
||||
[HttpGet("expense/project")]
|
||||
public async Task<IActionResult> GetExpenseReportByProjectsAsync([FromQuery] DateTime startDate, [FromQuery] DateTime endDate)
|
||||
[HttpGet("expense/monthly")]
|
||||
public async Task<IActionResult> GetExpenseReportByProjectsAsync([FromQuery] Guid? projectId, [FromQuery] Guid? categoryId, [FromQuery] int months)
|
||||
{
|
||||
// Structured start log
|
||||
_logger.LogInfo(
|
||||
"GetExpenseReportByProjects started. TenantId={TenantId}, StartDate={StartDate}, EndDate={EndDate}",
|
||||
tenantId, startDate, endDate); // [Start Log] [memory:4][memory:1]
|
||||
|
||||
// Guard: validate range and normalize to inclusive end-of-day
|
||||
if (endDate < startDate)
|
||||
{
|
||||
_logger.LogWarning("Invalid date range. StartDate={StartDate}, EndDate={EndDate}", startDate, endDate); // [Validation Log] [memory:4]
|
||||
return BadRequest(ApiResponse<object>.ErrorResponse("endDate must be on or after startDate.", 400)); // [Validation Response] [memory:1]
|
||||
}
|
||||
|
||||
var start = startDate.Date;
|
||||
var end = endDate.Date.AddDays(1).AddTicks(-1); // inclusive EOD [memory:7]
|
||||
months = 0 - months;
|
||||
var end = DateTime.UtcNow.Date;
|
||||
var start = end.AddMonths(months); // inclusive EOD
|
||||
|
||||
try
|
||||
{
|
||||
@ -641,55 +631,58 @@ namespace Marco.Pms.Services.Controllers
|
||||
e.TenantId == tenantId
|
||||
&& e.IsActive
|
||||
&& e.StatusId != Draft
|
||||
&& e.Project != null
|
||||
&& e.TransactionDate >= start
|
||||
&& e.TransactionDate <= end); // [Server Filters] [memory:7]
|
||||
&& e.TransactionDate <= end); // [Server Filters]
|
||||
|
||||
if (projectId.HasValue)
|
||||
baseQuery.Where(e => e.ProjectId == projectId);
|
||||
|
||||
if (categoryId.HasValue)
|
||||
baseQuery.Where(e => e.ExpensesTypeId == categoryId);
|
||||
|
||||
// Single server-side group/aggregate by project
|
||||
var report = await baseQuery
|
||||
.GroupBy(e => e.Project)
|
||||
.AsNoTracking()
|
||||
.GroupBy(e => new { e.TransactionDate.Year, e.TransactionDate.Month })
|
||||
.Select(g => new
|
||||
{
|
||||
ProjectName = g.Key!.Name,
|
||||
TotalApprovedAmount = g.Where(x => x.StatusId == Processed || x.StatusId == ProcessPending)
|
||||
.Sum(x => x.Amount),
|
||||
TotalPendingAmount = g.Where(x => x.StatusId != Processed
|
||||
&& x.StatusId != RejectedByReviewer
|
||||
&& x.StatusId != RejectedByApprover)
|
||||
.Sum(x => x.Amount),
|
||||
TotalRejectedAmount = g.Where(x => x.StatusId == RejectedByReviewer
|
||||
|| x.StatusId == RejectedByApprover)
|
||||
.Sum(x => x.Amount),
|
||||
TotalProcessedAmount = g.Where(x => x.StatusId == Processed)
|
||||
.Sum(x => x.Amount)
|
||||
Year = g.Key.Year,
|
||||
Month = g.Key.Month,
|
||||
Total = g.Sum(x => x.Amount),
|
||||
Count = g.Count()
|
||||
})
|
||||
.OrderBy(r => r.ProjectName)
|
||||
.ToListAsync(); // [Single Round-trip] [memory:7]
|
||||
.OrderBy(x => x.Year).ThenBy(x => x.Month)
|
||||
.ToListAsync();
|
||||
|
||||
var response = new
|
||||
var culture = CultureInfo.GetCultureInfo("en-IN"); // pick desired locale
|
||||
|
||||
var response = report
|
||||
.Select(x => new
|
||||
{
|
||||
Report = report,
|
||||
TotalAmount = report.Sum(r => r.TotalApprovedAmount)
|
||||
};
|
||||
MonthName = culture.DateTimeFormat.GetMonthName(x.Month), // e.g., "January"
|
||||
Year = x.Year,
|
||||
Total = x.Total,
|
||||
Count = x.Count
|
||||
}).ToList();
|
||||
|
||||
_logger.LogInfo(
|
||||
"GetExpenseReportByProjects completed. TenantId={TenantId}, Rows={Rows}, TotalAmount={TotalAmount}",
|
||||
tenantId, report.Count, response.TotalAmount); // [Completion Log] [memory:4]
|
||||
"GetExpenseReportByProjects completed. TenantId={TenantId}, Rows={Rows}",
|
||||
tenantId, report.Count); // [Completion Log]
|
||||
|
||||
return Ok(ApiResponse<object>.SuccessResponse(response, "Expense report by project fetched successfully", 200)); // [Success Response] [memory:1]
|
||||
return Ok(ApiResponse<object>.SuccessResponse(response, "Expense report by project fetched successfully", 200)); // [Success Response]
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
_logger.LogWarning("GetExpenseReportByProjects canceled by client. TenantId={TenantId}", tenantId); // [Cancel Log] [memory:4]
|
||||
return StatusCode(499, ApiResponse<object>.ErrorResponse("Client has canceled the opration", "Client has canceled the opration", 499)); // [Cancel Response] [memory:1]
|
||||
_logger.LogWarning("GetExpenseReportByProjects canceled by client. TenantId={TenantId}", tenantId); // [Cancel Log]
|
||||
return StatusCode(499, ApiResponse<object>.ErrorResponse("Client has canceled the opration", "Client has canceled the opration", 499)); // [Cancel Response]
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex,
|
||||
"GetExpenseReportByProjects failed. TenantId={TenantId}, StartDate={StartDate}, EndDate={EndDate}",
|
||||
tenantId, start, end); // [Error Log] [memory:4]
|
||||
tenantId, start, end); // [Error Log]
|
||||
return StatusCode(500,
|
||||
ApiResponse<object>.ErrorResponse("An error occurred while fetching the expense report.", 500)); // [Error Response] [memory:1]
|
||||
ApiResponse<object>.ErrorResponse("An error occurred while fetching the expense report.", 500)); // [Error Response]
|
||||
}
|
||||
}
|
||||
|
||||
@ -717,12 +710,12 @@ namespace Marco.Pms.Services.Controllers
|
||||
baseQuery = baseQuery.Where(e => e.ProjectId == projectId.Value); // [Filter] [memory:7]
|
||||
|
||||
// Project to a minimal shape before grouping to avoid loading navigation graphs
|
||||
// Group by expense type name; adjust to the correct key if ExpensesType is an enum or navigation
|
||||
// Group by expense type name; adjust to the correct key if ExpensesCategory is an enum or navigation
|
||||
var query = baseQuery
|
||||
.Where(e => e.ExpensesType != null)
|
||||
.Select(e => new
|
||||
{
|
||||
ExpenseTypeName = e.ExpensesType!.Name, // If enum, use e.ExpensesType.ToString()
|
||||
ExpenseTypeName = e.ExpensesType!.Name, // If enum, use e.ExpensesCategory.ToString()
|
||||
Amount = e.Amount,
|
||||
StatusId = e.StatusId
|
||||
})
|
||||
|
Loading…
x
Reference in New Issue
Block a user