Added the APi to get count of pending expenses

This commit is contained in:
ashutosh.nehete 2025-10-03 14:32:59 +05:30
parent 548e714ea9
commit 590476a8aa

View File

@ -801,11 +801,18 @@ namespace Marco.Pms.Services.Controllers
return await _permission.HasPermission(PermissionsMaster.ExpenseProcess, loggedInEmployee.Id); return await _permission.HasPermission(PermissionsMaster.ExpenseProcess, loggedInEmployee.Id);
}); });
await Task.WhenAll(hasReviewPermissionTask, hasApprovePermissionTask, hasProcessPermissionTask); // [Parallel Await] var hasManagePermissionTask = Task.Run(async () =>
{
var _permission = scope.ServiceProvider.GetRequiredService<PermissionServices>();
return await _permission.HasPermission(PermissionsMaster.ExpenseManage, loggedInEmployee.Id);
});
await Task.WhenAll(hasReviewPermissionTask, hasApprovePermissionTask, hasProcessPermissionTask, hasManagePermissionTask); // [Parallel Await]
var hasReviewPermission = hasReviewPermissionTask.Result; var hasReviewPermission = hasReviewPermissionTask.Result;
var hasApprovePermission = hasApprovePermissionTask.Result; var hasApprovePermission = hasApprovePermissionTask.Result;
var hasProcessPermission = hasProcessPermissionTask.Result; var hasProcessPermission = hasProcessPermissionTask.Result;
var hasManagePermission = hasManagePermissionTask.Result;
_logger.LogInfo( _logger.LogInfo(
"Permissions resolved: Review={Review}, Approve={Approve}, Process={Process}", "Permissions resolved: Review={Review}, Approve={Approve}, Process={Process}",
@ -813,61 +820,92 @@ namespace Marco.Pms.Services.Controllers
// Build base query: read-only, tenant-scoped // Build base query: read-only, tenant-scoped
var baseQuery = _context.Expenses var baseQuery = _context.Expenses
.AsNoTracking() // Reduce tracking overhead for read-only list
.Where(e => e.IsActive && e.TenantId == tenantId); // [Base Filter]
// Important: fix operator precedence by grouping OR conditions
// Pending means Draft always, plus role-gated statuses
var pendingQuery = baseQuery
.Include(e => e.PaidBy)
.Include(e => e.CreatedBy)
.Include(e => e.ProcessedBy)
.Include(e => e.ApprovedBy)
.Include(e => e.ReviewedBy)
.Include(e => e.PaymentMode)
.Include(e => e.Project)
.Include(e => e.PaymentMode)
.Include(e => e.ExpensesType)
.Include(e => e.Status) .Include(e => e.Status)
.AsNoTracking() .AsNoTracking() // Reduce tracking overhead for read-only list
.Where(e => .Where(e => e.IsActive && e.TenantId == tenantId && e.StatusId != Processed && e.Status != null); // [Base Filter]
(e.StatusId == Draft && e.CreatedById == loggedInEmployee.Id)
|| (hasReviewPermission && e.StatusId == Review)
|| (hasApprovePermission && e.StatusId == Approve)
|| (hasProcessPermission && e.StatusId == ProcessPending)); // [Correct Precedence]
// Project to DTO in SQL to avoid heavy Include graph. // Project to DTO in SQL to avoid heavy Include graph.
if (projectId.HasValue) if (projectId.HasValue)
pendingQuery = pendingQuery.Where(e => e.ProjectId == projectId); baseQuery = baseQuery.Where(e => e.ProjectId == projectId);
// Prefer ProjectTo when profiles exist; otherwise project minimal fields // Prefer ProjectTo when profiles exist; otherwise project minimal fields
var response = await pendingQuery var expenses = await baseQuery
.Where(e => e.Status != null && e.ExpensesType != null && e.PaymentMode != null && e.Project != null && e.CreatedBy != null)
.Select(e => new
{
Id = e.Id,
Amount = e.Amount,
TransactionDate = e.TransactionDate,
StatusId = e.StatusId,
StatusName = e.Status!.Name,
ExpenseTypeName = e.ExpensesType!.Name,
PaymentModeName = e.PaymentMode!.Name,
ProjectName = e.Project!.Name,
CreatedByName = $"{e.CreatedBy!.FirstName} {e.CreatedBy.LastName}",
ReviewedByName = e.ReviewedBy != null ? $"{e.ReviewedBy.FirstName} {e.ReviewedBy.LastName}" : null,
ApprovedByName = e.ApprovedBy != null ? $"{e.ApprovedBy.FirstName} {e.ApprovedBy.LastName}" : null,
ProcessedByName = e.ProcessedBy != null ? $"{e.ProcessedBy.FirstName} {e.ProcessedBy.LastName}" : null,
PaidByName = e.PaidBy != null ? $"{e.PaidBy.FirstName} {e.PaidBy.LastName}" : null
})
.OrderByDescending(x => x.TransactionDate)
.ToListAsync(); // Single round-trip; no Include needed for this shape .ToListAsync(); // Single round-trip; no Include needed for this shape
var draftExpenses = expenses.Where(e => e.StatusId == Draft && e.CreatedById == loggedInEmployee.Id).ToList();
var reviewExpenses = expenses.Where(e => (hasReviewPermission || e.CreatedById == loggedInEmployee.Id) && e.StatusId == Review).ToList();
var approveExpenses = expenses.Where(e => (hasApprovePermission || e.CreatedById == loggedInEmployee.Id) && e.StatusId == Approve).ToList();
var processPendingExpenses = expenses.Where(e => (hasProcessPermission || e.CreatedById == loggedInEmployee.Id) && e.StatusId == ProcessPending).ToList();
var submitedExpenses = expenses.Where(e => e.StatusId != Draft && e.CreatedById == loggedInEmployee.Id).ToList();
if (hasManagePermission)
{
var response = new
{
Draft = new
{
Count = draftExpenses.Count,
TotalAmount = draftExpenses.Sum(e => e.Amount)
},
ReviewPending = new
{
Count = reviewExpenses.Count,
TotalAmount = reviewExpenses.Sum(e => e.Amount)
},
ApprovePending = new
{
Count = approveExpenses.Count,
TotalAmount = approveExpenses.Sum(e => e.Amount)
},
ProcessPending = new
{
Count = processPendingExpenses.Count,
TotalAmount = processPendingExpenses.Sum(e => e.Amount)
},
Submited = new
{
Count = submitedExpenses.Count,
TotalAmount = submitedExpenses.Sum(e => e.Amount)
}
};
_logger.LogInfo( _logger.LogInfo(
"GetPendingExpenseListAsync completed. TenantId={TenantId}, Count={Count}", "GetPendingExpenseListAsync completed. TenantId={TenantId}",
tenantId, response.Count); // [Completion Log] tenantId); // [Completion Log]
return Ok(ApiResponse<object>.SuccessResponse(response, "Pending Expenses fetched successfully", 200)); // [Success Response] return Ok(ApiResponse<object>.SuccessResponse(response, "Pending Expenses fetched successfully", 200)); // [Success Response]
} }
else
{
var response = new
{
Draft = new
{
Count = draftExpenses.Count
},
ReviewPending = new
{
Count = reviewExpenses.Count
},
ApprovePending = new
{
Count = approveExpenses.Count
},
ProcessPending = new
{
Count = processPendingExpenses.Count
},
Submited = new
{
Count = submitedExpenses.Count
}
};
_logger.LogInfo(
"GetPendingExpenseListAsync completed. TenantId={TenantId}",
tenantId); // [Completion Log]
return Ok(ApiResponse<object>.SuccessResponse(response, "Pending Expenses fetched successfully", 200)); // [Success Response]
}
}
catch (OperationCanceledException) catch (OperationCanceledException)
{ {
_logger.LogWarning("GetPendingExpenseListAsync canceled by client. TenantId={TenantId}", tenantId); // [Cancel Log] _logger.LogWarning("GetPendingExpenseListAsync canceled by client. TenantId={TenantId}", tenantId); // [Cancel Log]