Added totalPages , totalEntites and Currect page in response of get expenses list

This commit is contained in:
ashutosh.nehete 2025-07-22 09:58:13 +05:30
parent 2449d2a518
commit 5be154a9f1
5 changed files with 20 additions and 155 deletions

View File

@ -395,7 +395,7 @@ namespace Marco.Pms.DataAccess.Data
Name = "Draft", Name = "Draft",
DisplayName = "Draft", DisplayName = "Draft",
Description = "Expense has been created but not yet submitted.", Description = "Expense has been created but not yet submitted.",
Color = "#212529", Color = "#6c757d",
IsSystem = true, IsSystem = true,
IsActive = true, IsActive = true,
TenantId = Guid.Parse("b3466e83-7e11-464c-b93a-daf047838b26") TenantId = Guid.Parse("b3466e83-7e11-464c-b93a-daf047838b26")

View File

@ -1934,7 +1934,7 @@ namespace Marco.Pms.DataAccess.Migrations
new new
{ {
Id = new Guid("297e0d8f-f668-41b5-bfea-e03b354251c8"), Id = new Guid("297e0d8f-f668-41b5-bfea-e03b354251c8"),
Color = "#212529", Color = "#6c757d",
Description = "Expense has been created but not yet submitted.", Description = "Expense has been created but not yet submitted.",
DisplayName = "Draft", DisplayName = "Draft",
IsActive = true, IsActive = true,

View File

@ -1,5 +1,4 @@
using System; using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable #nullable disable
@ -373,7 +372,7 @@ namespace Marco.Pms.DataAccess.Migrations
columns: new[] { "Id", "Color", "Description", "DisplayName", "IsActive", "IsSystem", "Name", "TenantId" }, columns: new[] { "Id", "Color", "Description", "DisplayName", "IsActive", "IsSystem", "Name", "TenantId" },
values: new object[,] values: new object[,]
{ {
{ new Guid("297e0d8f-f668-41b5-bfea-e03b354251c8"), "#212529", "Expense has been created but not yet submitted.", "Draft", true, true, "Draft", new Guid("b3466e83-7e11-464c-b93a-daf047838b26") }, { new Guid("297e0d8f-f668-41b5-bfea-e03b354251c8"), "#6c757d", "Expense has been created but not yet submitted.", "Draft", true, true, "Draft", new Guid("b3466e83-7e11-464c-b93a-daf047838b26") },
{ new Guid("4068007f-c92f-4f37-a907-bc15fe57d4d8"), "#0dcaf0", "Review is completed, waiting for action of approver.", "Approve", true, true, "Approval Pending", new Guid("b3466e83-7e11-464c-b93a-daf047838b26") }, { new Guid("4068007f-c92f-4f37-a907-bc15fe57d4d8"), "#0dcaf0", "Review is completed, waiting for action of approver.", "Approve", true, true, "Approval Pending", new Guid("b3466e83-7e11-464c-b93a-daf047838b26") },
{ new Guid("61578360-3a49-4c34-8604-7b35a3787b95"), "#198754", "Expense has been settled.", "Paid", true, true, "Processed", new Guid("b3466e83-7e11-464c-b93a-daf047838b26") }, { new Guid("61578360-3a49-4c34-8604-7b35a3787b95"), "#198754", "Expense has been settled.", "Paid", true, true, "Processed", new Guid("b3466e83-7e11-464c-b93a-daf047838b26") },
{ new Guid("6537018f-f4e9-4cb3-a210-6c3b2da999d7"), "#0d6efd", "Reviewer is currently reviewing the expense.", "Review", true, true, "Review Pending", new Guid("b3466e83-7e11-464c-b93a-daf047838b26") }, { new Guid("6537018f-f4e9-4cb3-a210-6c3b2da999d7"), "#0d6efd", "Reviewer is currently reviewing the expense.", "Review", true, true, "Review Pending", new Guid("b3466e83-7e11-464c-b93a-daf047838b26") },

View File

@ -1931,7 +1931,7 @@ namespace Marco.Pms.DataAccess.Migrations
new new
{ {
Id = new Guid("297e0d8f-f668-41b5-bfea-e03b354251c8"), Id = new Guid("297e0d8f-f668-41b5-bfea-e03b354251c8"),
Color = "#212529", Color = "#6c757d",
Description = "Expense has been created but not yet submitted.", Description = "Expense has been created but not yet submitted.",
DisplayName = "Draft", DisplayName = "Draft",
IsActive = true, IsActive = true,

View File

@ -155,6 +155,9 @@ namespace Marco.Pms.Services.Service
// 4. --- Apply Ordering and Pagination --- // 4. --- Apply Ordering and Pagination ---
// This should be the last step before executing the query. // This should be the last step before executing the query.
var totalEntites = await expensesQuery.CountAsync();
var paginatedQuery = expensesQuery var paginatedQuery = expensesQuery
.OrderByDescending(e => e.CreatedAt) .OrderByDescending(e => e.CreatedAt)
.Skip((pageNumber - 1) * pageSize) .Skip((pageNumber - 1) * pageSize)
@ -169,7 +172,7 @@ namespace Marco.Pms.Services.Service
return ApiResponse<object>.SuccessResponse(new List<ExpenseList>(), "No expenses found for the given criteria.", 200); return ApiResponse<object>.SuccessResponse(new List<ExpenseList>(), "No expenses found for the given criteria.", 200);
} }
var response = _mapper.Map<List<ExpenseList>>(expensesList); var expenseVM = _mapper.Map<List<ExpenseList>>(expensesList);
// 6. --- Efficiently Fetch and Append 'Next Status' Information --- // 6. --- Efficiently Fetch and Append 'Next Status' Information ---
var statusIds = expensesList.Select(e => e.StatusId).Distinct().ToList(); var statusIds = expensesList.Select(e => e.StatusId).Distinct().ToList();
@ -182,7 +185,7 @@ namespace Marco.Pms.Services.Service
// Use a Lookup for efficient O(1) mapping. This is much better than repeated `.Where()` in a loop. // Use a Lookup for efficient O(1) mapping. This is much better than repeated `.Where()` in a loop.
var statusMapLookup = statusMappings.ToLookup(sm => sm.StatusId); var statusMapLookup = statusMappings.ToLookup(sm => sm.StatusId);
foreach (var expense in response) foreach (var expense in expenseVM)
{ {
if (expense.Status?.Id != null && statusMapLookup.Contains(expense.Status.Id)) if (expense.Status?.Id != null && statusMapLookup.Contains(expense.Status.Id))
{ {
@ -197,8 +200,17 @@ namespace Marco.Pms.Services.Service
} }
// 7. --- Return Final Success Response --- // 7. --- Return Final Success Response ---
var message = $"{response.Count} expense records fetched successfully."; var message = $"{expenseVM.Count} expense records fetched successfully.";
_logger.LogInfo(message); _logger.LogInfo(message);
var totalPages = (int)Math.Ceiling((double)totalEntites / pageSize);
var response = new
{
CurrentPage = pageNumber,
TotalPages = totalPages,
TotalEntites = totalEntites,
Data = expenseVM,
};
return ApiResponse<object>.SuccessResponse(response, message, 200); return ApiResponse<object>.SuccessResponse(response, message, 200);
} }
catch (DbUpdateException dbEx) catch (DbUpdateException dbEx)
@ -415,152 +427,6 @@ namespace Marco.Pms.Services.Service
} }
} }
public async Task<ApiResponse<object>> ChangeStatus(ExpenseRecordDto model, Employee loggedInEmployee, Guid tenantId)
{
var existingExpense = await _context.Expenses
.Include(e => e.ExpensesType)
.Include(e => e.Project)
.Include(e => e.PaidBy)
.ThenInclude(e => e!.JobRole)
.Include(e => e.PaymentMode)
.Include(e => e.Status)
.Include(e => e.CreatedBy)
.FirstOrDefaultAsync(e => e.Id == model.ExpenseId && e.StatusId != model.StatusId && e.TenantId == tenantId);
if (existingExpense == null)
{
return ApiResponse<object>.ErrorResponse("Expense not found", "Expense not found", 404);
}
var statusMappingTask = Task.Run(async () =>
{
await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
return await dbContext.ExpensesStatusMapping
.Include(s => s.NextStatus)
.FirstOrDefaultAsync(s => s.StatusId == existingExpense.StatusId && s.NextStatus != null && s.NextStatusId == model.StatusId && s.TenantId == tenantId);
});
var statusPermissionMappingTask = Task.Run(async () =>
{
await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
return await dbContext.StatusPermissionMapping.Where(sp => sp.StatusId == model.StatusId).ToListAsync();
});
// Await all prerequisite checks at once.
await Task.WhenAll(statusMappingTask, statusPermissionMappingTask);
var statusMapping = await statusMappingTask;
var statusPermissions = await statusPermissionMappingTask;
if (statusMapping == null)
{
return ApiResponse<object>.ErrorResponse("There is no follow-up status for currect status");
}
if (statusPermissions.Any())
{
foreach (var sp in statusPermissions)
{
using var scope = _serviceScopeFactory.CreateScope();
var permissionService = scope.ServiceProvider.GetRequiredService<PermissionServices>();
if (!await permissionService.HasPermission(sp.PermissionId, loggedInEmployee.Id))
{
_logger.LogWarning("Access DENIED for employee {EmployeeId} attempting to change status of expense from status {StatusId} to {NextStatusId}",
loggedInEmployee.Id, statusMapping.StatusId, statusMapping.NextStatusId);
return ApiResponse<object>.ErrorResponse("You do not have permission", "Access Denied", 403);
}
}
}
else if (existingExpense.CreatedById != loggedInEmployee.Id)
{
_logger.LogWarning("Access DENIED for employee {EmployeeId} attempting to change status of expense from status {StatusId} to {NextStatusId}",
loggedInEmployee.Id, statusMapping.StatusId, statusMapping.NextStatusId);
return ApiResponse<object>.ErrorResponse("You do not have permission", "Access Denied", 403);
}
var existingEntity = _updateLogHelper.EntityToBsonDocument(existingExpense);
existingExpense.StatusId = statusMapping.NextStatusId;
existingExpense.Status = statusMapping.NextStatus;
_context.ExpenseLogs.Add(new ExpenseLog
{
ExpenseId = existingExpense.Id,
Action = statusMapping.NextStatus!.Name,
UpdatedById = loggedInEmployee.Id,
Comment = model.Comment
});
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException dbEx)
{
_logger.LogError(dbEx, "Error occured while update status of expanse.");
return ApiResponse<object>.ErrorResponse("Error occured while update status of expanse.", new
{
Message = dbEx.Message,
StackTrace = dbEx.StackTrace,
Source = dbEx.Source,
innerexcption = new
{
Message = dbEx.InnerException?.Message,
StackTrace = dbEx.InnerException?.StackTrace,
Source = dbEx.InnerException?.Source,
}
}, 500);
}
try
{
var updateLog = new UpdateLogsObject
{
EntityId = existingExpense.Id.ToString(),
UpdatedById = loggedInEmployee.Id.ToString(),
OldObject = existingEntity,
UpdatedAt = DateTime.UtcNow
};
var mongoDBTask = Task.Run(async () =>
{
await _updateLogHelper.PushToUpdateLogsAsync(updateLog, Collection);
});
var getNextStatusTask = Task.Run(async () =>
{
await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
return await dbContext.ExpensesStatusMapping
.Include(s => s.NextStatus)
.FirstOrDefaultAsync(s => s.StatusId == existingExpense.StatusId && s.NextStatus != null && s.TenantId == tenantId);
});
await Task.WhenAll(mongoDBTask, getNextStatusTask);
var getNextStatus = await getNextStatusTask;
var response = _mapper.Map<ExpenseList>(existingExpense);
if (getNextStatus != null)
{
response.NextStatus = _mapper.Map<List<ExpensesStatusMasterVM>>(getNextStatus.NextStatus);
}
return ApiResponse<object>.SuccessResponse(response);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error occured while Saving old entity in mongoDb");
return ApiResponse<object>.ErrorResponse("Error occured while update status of expanse.", new
{
Message = ex.Message,
StackTrace = ex.StackTrace,
Source = ex.Source,
innerexcption = new
{
Message = ex.InnerException?.Message,
StackTrace = ex.InnerException?.StackTrace,
Source = ex.InnerException?.Source,
}
}, 500);
}
}
/// <summary> /// <summary>
/// Changes the status of an expense record, performing validation, permission checks, and logging. /// Changes the status of an expense record, performing validation, permission checks, and logging.
/// </summary> /// </summary>