Added cache to expenses get list and create expense APIs
This commit is contained in:
parent
d536b9c99c
commit
73cf85a1cc
@ -27,6 +27,7 @@ namespace Marco.Pms.Helpers.CacheHelper
|
||||
|
||||
var update = Builders<EmployeePermissionMongoDB>.Update
|
||||
.AddToSetEach(e => e.ApplicationRoleIds, newRoleIds)
|
||||
.Set(r => r.ExpireAt, DateTime.UtcNow.Date.AddDays(1))
|
||||
.AddToSetEach(e => e.PermissionIds, newPermissionIds);
|
||||
|
||||
var options = new UpdateOptions { IsUpsert = true };
|
||||
@ -46,6 +47,7 @@ namespace Marco.Pms.Helpers.CacheHelper
|
||||
var filter = Builders<EmployeePermissionMongoDB>.Filter.Eq(e => e.Id, employeeId.ToString());
|
||||
|
||||
var update = Builders<EmployeePermissionMongoDB>.Update
|
||||
.Set(r => r.ExpireAt, DateTime.UtcNow.Date.AddDays(1))
|
||||
.AddToSetEach(e => e.ProjectIds, newprojectIds);
|
||||
|
||||
var result = await _collection.UpdateOneAsync(filter, update, new UpdateOptions { IsUpsert = true });
|
||||
@ -187,17 +189,12 @@ namespace Marco.Pms.Helpers.CacheHelper
|
||||
// A private method to handle the one-time setup of the collection's indexes.
|
||||
private async Task InitializeCollectionAsync()
|
||||
{
|
||||
// 1. Define the TTL (Time-To-Live) index on the 'ExpireAt' field.
|
||||
var indexKeys = Builders<EmployeePermissionMongoDB>.IndexKeys.Ascending(x => x.ExpireAt);
|
||||
var indexOptions = new CreateIndexOptions
|
||||
{
|
||||
// This tells MongoDB to automatically delete documents when their 'ExpireAt' time is reached.
|
||||
ExpireAfter = TimeSpan.FromSeconds(0)
|
||||
ExpireAfter = TimeSpan.Zero // required for fixed expiration time
|
||||
};
|
||||
var indexModel = new CreateIndexModel<EmployeePermissionMongoDB>(indexKeys, indexOptions);
|
||||
|
||||
// 2. Create the index. This is an idempotent operation if the index already exists.
|
||||
// Use CreateOneAsync since we are only creating a single index.
|
||||
await _collection.Indexes.CreateOneAsync(indexModel);
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
using Marco.Pms.Model.MongoDBModels.Employees;
|
||||
using Marco.Pms.Model.MongoDBModels.Expenses;
|
||||
using Marco.Pms.Model.Utilities;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using MongoDB.Driver;
|
||||
|
||||
@ -6,7 +7,7 @@ namespace Marco.Pms.Helpers.CacheHelper
|
||||
{
|
||||
public class ExpenseCache
|
||||
{
|
||||
private readonly IMongoCollection<EmployeePermissionMongoDB> _collection;
|
||||
private readonly IMongoCollection<ExpenseDetailsMongoDB> _collection;
|
||||
public ExpenseCache(IConfiguration configuration)
|
||||
{
|
||||
|
||||
@ -14,11 +15,91 @@ namespace Marco.Pms.Helpers.CacheHelper
|
||||
var mongoUrl = new MongoUrl(connectionString);
|
||||
var client = new MongoClient(mongoUrl); // Your MongoDB connection string
|
||||
var mongoDB = client.GetDatabase(mongoUrl.DatabaseName); // Your MongoDB Database name
|
||||
_collection = mongoDB.GetCollection<EmployeePermissionMongoDB>("Expenses");
|
||||
_collection = mongoDB.GetCollection<ExpenseDetailsMongoDB>("Expenses");
|
||||
}
|
||||
public async Task AddExpenseToCacheAsync()
|
||||
public async Task AddExpenseToCacheAsync(ExpenseDetailsMongoDB expense)
|
||||
{
|
||||
await _collection.InsertOneAsync(expense);
|
||||
|
||||
await InitializeCollectionAsync();
|
||||
}
|
||||
public async Task AddExpensesListToCacheAsync(List<ExpenseDetailsMongoDB> expenses)
|
||||
{
|
||||
// 1. Add a guard clause to avoid an unnecessary database call for an empty list.
|
||||
if (expenses == null || !expenses.Any())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. Perform the insert operation. This is the only responsibility of this method.
|
||||
await _collection.InsertManyAsync(expenses);
|
||||
await InitializeCollectionAsync();
|
||||
}
|
||||
public async Task<(int totalPages, long totalCount, List<ExpenseDetailsMongoDB> expenseList)> GetExpenseListFromCacheAsync(Guid tenantId, Guid loggedInEmployeeId, bool viewAll,
|
||||
bool viewSelf, int pageNumber, int pageSize, ExpensesFilter? expenseFilter)
|
||||
{
|
||||
var filterBuilder = Builders<ExpenseDetailsMongoDB>.Filter;
|
||||
var filter = filterBuilder.Empty;
|
||||
|
||||
// Permission-based filter
|
||||
if (!viewAll && viewSelf)
|
||||
{
|
||||
filter &= filterBuilder.Eq(e => e.CreatedById, loggedInEmployeeId.ToString());
|
||||
}
|
||||
|
||||
// Apply filters
|
||||
if (expenseFilter != null)
|
||||
{
|
||||
if (expenseFilter.StartDate.HasValue && expenseFilter.EndDate.HasValue)
|
||||
{
|
||||
filter &= filterBuilder.Gte(e => e.CreatedAt, expenseFilter.StartDate.Value.Date)
|
||||
& filterBuilder.Lte(e => e.CreatedAt, expenseFilter.EndDate.Value.Date.AddDays(1).AddTicks(-1));
|
||||
}
|
||||
|
||||
if (expenseFilter.ProjectIds?.Any() == true)
|
||||
{
|
||||
filter &= filterBuilder.In(e => e.ProjectId, expenseFilter.ProjectIds.Select(p => p.ToString()).ToList());
|
||||
}
|
||||
|
||||
if (expenseFilter.StatusIds?.Any() == true)
|
||||
{
|
||||
filter &= filterBuilder.In(e => e.StatusId, expenseFilter.StatusIds.Select(p => p.ToString()).ToList());
|
||||
}
|
||||
|
||||
if (expenseFilter.PaidById?.Any() == true)
|
||||
{
|
||||
filter &= filterBuilder.In(e => e.PaidById, expenseFilter.PaidById.Select(p => p.ToString()).ToList());
|
||||
}
|
||||
|
||||
if (expenseFilter.CreatedByIds?.Any() == true && viewAll)
|
||||
{
|
||||
filter &= filterBuilder.In(e => e.CreatedById, expenseFilter.CreatedByIds.Select(p => p.ToString()).ToList());
|
||||
}
|
||||
}
|
||||
|
||||
// Total count
|
||||
var totalCount = await _collection.CountDocumentsAsync(filter);
|
||||
var totalPages = (int)Math.Ceiling((double)totalCount / pageSize);
|
||||
|
||||
// Fetch paginated data
|
||||
var expenses = await _collection
|
||||
.Find(filter)
|
||||
.Skip((pageNumber - 1) * pageSize)
|
||||
.Limit(pageSize)
|
||||
.SortByDescending(e => e.CreatedAt)
|
||||
.ToListAsync();
|
||||
|
||||
return (totalPages, totalCount, expenses);
|
||||
}
|
||||
private async Task InitializeCollectionAsync()
|
||||
{
|
||||
var indexKeys = Builders<ExpenseDetailsMongoDB>.IndexKeys.Ascending(x => x.ExpireAt);
|
||||
var indexOptions = new CreateIndexOptions
|
||||
{
|
||||
ExpireAfter = TimeSpan.Zero // required for fixed expiration time
|
||||
};
|
||||
var indexModel = new CreateIndexModel<ExpenseDetailsMongoDB>(indexKeys, indexOptions);
|
||||
await _collection.Indexes.CreateOneAsync(indexModel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -30,13 +30,7 @@ namespace Marco.Pms.Helpers
|
||||
{
|
||||
await _projectCollection.InsertOneAsync(projectDetails);
|
||||
|
||||
var indexKeys = Builders<ProjectMongoDB>.IndexKeys.Ascending(x => x.ExpireAt);
|
||||
var indexOptions = new CreateIndexOptions
|
||||
{
|
||||
ExpireAfter = TimeSpan.Zero // required for fixed expiration time
|
||||
};
|
||||
var indexModel = new CreateIndexModel<ProjectMongoDB>(indexKeys, indexOptions);
|
||||
await _projectCollection.Indexes.CreateOneAsync(indexModel);
|
||||
await InitializeCollectionAsync();
|
||||
|
||||
}
|
||||
public async Task AddProjectDetailsListToCache(List<ProjectMongoDB> projectDetailsList)
|
||||
@ -53,17 +47,12 @@ namespace Marco.Pms.Helpers
|
||||
}
|
||||
private async Task InitializeCollectionAsync()
|
||||
{
|
||||
// 1. Define the TTL (Time-To-Live) index on the 'ExpireAt' field.
|
||||
var indexKeys = Builders<ProjectMongoDB>.IndexKeys.Ascending(x => x.ExpireAt);
|
||||
var indexOptions = new CreateIndexOptions
|
||||
{
|
||||
// This tells MongoDB to automatically delete documents when their 'ExpireAt' time is reached.
|
||||
ExpireAfter = TimeSpan.FromSeconds(0)
|
||||
ExpireAfter = TimeSpan.Zero // required for fixed expiration time
|
||||
};
|
||||
var indexModel = new CreateIndexModel<ProjectMongoDB>(indexKeys, indexOptions);
|
||||
|
||||
// 2. Create the index. This is an idempotent operation if the index already exists.
|
||||
// Use CreateOneAsync since we are only creating a single index.
|
||||
await _projectCollection.Indexes.CreateOneAsync(indexModel);
|
||||
}
|
||||
public async Task<bool> UpdateProjectDetailsOnlyToCache(Project project, StatusMaster projectStatus)
|
||||
|
@ -5,7 +5,6 @@
|
||||
public Guid EmpID { get; set; }
|
||||
public Guid JobRoleId { get; set; }
|
||||
public Guid ProjectId { get; set; }
|
||||
|
||||
public bool Status { get; set; }
|
||||
}
|
||||
|
||||
@ -14,7 +13,6 @@
|
||||
{
|
||||
public Guid ProjectId { get; set; }
|
||||
public Guid JobRoleId { get; set; }
|
||||
|
||||
public bool Status { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,13 @@
|
||||
namespace Marco.Pms.Model.MongoDBModels.Employees
|
||||
{
|
||||
public class BasicEmployeeMongoDB
|
||||
{
|
||||
public string Id { get; set; } = string.Empty;
|
||||
public string? FirstName { get; set; }
|
||||
public string? LastName { get; set; }
|
||||
public byte[]? Photo { get; set; }
|
||||
public string? JobRoleId { get; set; }
|
||||
public string? JobRoleName { get; set; }
|
||||
public string TenantId { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
@ -2,5 +2,27 @@
|
||||
{
|
||||
public class ExpenseDetailsMongoDB
|
||||
{
|
||||
public string Id { get; set; } = string.Empty;
|
||||
public string ProjectId { get; set; } = string.Empty;
|
||||
public string ExpensesTypeId { get; set; } = string.Empty;
|
||||
public string PaymentModeId { get; set; } = string.Empty;
|
||||
public string PaidById { get; set; } = string.Empty;
|
||||
public string CreatedById { get; set; } = string.Empty;
|
||||
public DateTime TransactionDate { get; set; }
|
||||
public DateTime CreatedAt { get; set; }
|
||||
public DateTime ExpireAt { get; set; } = DateTime.UtcNow.Date.AddDays(1);
|
||||
public string SupplerName { get; set; } = string.Empty;
|
||||
public double Amount { get; set; }
|
||||
public string StatusId { get; set; } = string.Empty;
|
||||
public bool PreApproved { get; set; } = false;
|
||||
public string? TransactionId { get; set; }
|
||||
public string Description { get; set; } = string.Empty;
|
||||
public string? Location { get; set; }
|
||||
public List<string> S3Key { get; set; } = new List<string>();
|
||||
public List<string>? ThumbS3Key { get; set; }
|
||||
public string? GSTNumber { get; set; }
|
||||
public int? NoOfPersons { get; set; }
|
||||
public bool IsActive { get; set; } = true;
|
||||
public string TenantId { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,13 @@
|
||||
namespace Marco.Pms.Model.MongoDBModels.Masters
|
||||
{
|
||||
public class ExpensesStatusMasterMongoDB
|
||||
{
|
||||
public string Id { get; set; } = string.Empty;
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string DisplayName { get; set; } = string.Empty;
|
||||
public string Description { get; set; } = string.Empty;
|
||||
public string? Color { get; set; }
|
||||
public bool IsSystem { get; set; } = false;
|
||||
public string TenantId { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
namespace Marco.Pms.Model.MongoDBModels.Masters
|
||||
{
|
||||
public class ExpensesTypeMasterMongoDB
|
||||
{
|
||||
public string Id { get; set; } = string.Empty;
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public bool NoOfPersonsRequired { get; set; }
|
||||
public string Description { get; set; } = string.Empty;
|
||||
public string TenantId { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
namespace Marco.Pms.Model.MongoDBModels.Masters
|
||||
{
|
||||
public class PaymentModeMatserMongoDB
|
||||
{
|
||||
public string Id { get; set; } = string.Empty;
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string Description { get; set; } = string.Empty;
|
||||
public string TenantId { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
10
Marco.Pms.Model/MongoDBModels/Project/ProjectBasicMongoDB.cs
Normal file
10
Marco.Pms.Model/MongoDBModels/Project/ProjectBasicMongoDB.cs
Normal file
@ -0,0 +1,10 @@
|
||||
namespace Marco.Pms.Model.MongoDBModels.Project
|
||||
{
|
||||
public class ProjectBasicMongoDB
|
||||
{
|
||||
public string Id { get; set; } = string.Empty;
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string ShortName { get; set; } = string.Empty;
|
||||
public string TenantId { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
@ -1,40 +1,58 @@
|
||||
using Marco.Pms.Helpers;
|
||||
using Marco.Pms.Helpers.CacheHelper;
|
||||
using AutoMapper;
|
||||
using Marco.Pms.DataAccess.Data;
|
||||
using Marco.Pms.Helpers;
|
||||
using Marco.Pms.Helpers.CacheHelper;
|
||||
using Marco.Pms.Model.Expenses;
|
||||
using Marco.Pms.Model.Master;
|
||||
using Marco.Pms.Model.MongoDBModels.Expenses;
|
||||
using Marco.Pms.Model.MongoDBModels.Masters;
|
||||
using Marco.Pms.Model.MongoDBModels.Project;
|
||||
using Marco.Pms.Model.MongoDBModels.Utility;
|
||||
using Marco.Pms.Model.Projects;
|
||||
using Marco.Pms.Model.Utilities;
|
||||
using MarcoBMS.Services.Service;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using MongoDB.Driver;
|
||||
using Project = Marco.Pms.Model.Projects.Project;
|
||||
|
||||
namespace Marco.Pms.Services.Helpers
|
||||
{
|
||||
public class CacheUpdateHelper
|
||||
{
|
||||
private readonly IDbContextFactory<ApplicationDbContext> _dbContextFactory;
|
||||
private readonly ApplicationDbContext _context;
|
||||
private readonly IMapper _mapper;
|
||||
private readonly ProjectCache _projectCache;
|
||||
private readonly EmployeeCache _employeeCache;
|
||||
private readonly ReportCache _reportCache;
|
||||
private readonly ExpenseCache _expenseCache;
|
||||
private readonly ILoggingService _logger;
|
||||
private readonly IDbContextFactory<ApplicationDbContext> _dbContextFactory;
|
||||
private readonly ApplicationDbContext _context;
|
||||
private readonly GeneralHelper _generalHelper;
|
||||
private static readonly Guid Draft = Guid.Parse("297e0d8f-f668-41b5-bfea-e03b354251c8");
|
||||
private static readonly Guid Rejected = Guid.Parse("d1ee5eec-24b6-4364-8673-a8f859c60729");
|
||||
|
||||
public CacheUpdateHelper(ProjectCache projectCache, EmployeeCache employeeCache, ReportCache reportCache, ILoggingService logger,
|
||||
IDbContextFactory<ApplicationDbContext> dbContextFactory, ApplicationDbContext context, GeneralHelper generalHelper)
|
||||
public CacheUpdateHelper(
|
||||
IMapper mapper,
|
||||
ProjectCache projectCache,
|
||||
EmployeeCache employeeCache,
|
||||
ReportCache reportCache,
|
||||
ExpenseCache expenseCache,
|
||||
ILoggingService logger,
|
||||
IDbContextFactory<ApplicationDbContext> dbContextFactory,
|
||||
ApplicationDbContext context,
|
||||
GeneralHelper generalHelper)
|
||||
{
|
||||
_mapper = mapper;
|
||||
_projectCache = projectCache;
|
||||
_employeeCache = employeeCache;
|
||||
_reportCache = reportCache;
|
||||
_expenseCache = expenseCache;
|
||||
_logger = logger;
|
||||
_dbContextFactory = dbContextFactory;
|
||||
_context = context;
|
||||
_generalHelper = generalHelper;
|
||||
}
|
||||
|
||||
// ------------------------------------ Project Details Cache ---------------------------------------
|
||||
#region ======================================================= Project Details Cache =======================================================
|
||||
|
||||
public async Task AddProjectDetails(Project project)
|
||||
{
|
||||
@ -507,7 +525,9 @@ namespace Marco.Pms.Services.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------ Project Infrastructure Cache ---------------------------------------
|
||||
#endregion
|
||||
|
||||
#region ======================================================= Project Infrastructure Cache =======================================================
|
||||
|
||||
public async Task AddBuildngInfra(Guid projectId, Building? building = null, Floor? floor = null, WorkArea? workArea = null, Guid? buildingId = null)
|
||||
{
|
||||
@ -573,7 +593,9 @@ namespace Marco.Pms.Services.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------- WorkItem -------------------------------------------------------
|
||||
#endregion
|
||||
|
||||
#region ======================================================= WorkItem Cache =======================================================
|
||||
|
||||
public async Task<List<WorkItemMongoDB>?> GetWorkItemsByWorkAreaIds(List<Guid> workAreaIds)
|
||||
{
|
||||
@ -680,8 +702,10 @@ namespace Marco.Pms.Services.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ======================================================= Employee Profile Cache =======================================================
|
||||
|
||||
// ------------------------------------ Employee Profile Cache ---------------------------------------
|
||||
public async Task AddApplicationRole(Guid employeeId, List<Guid> roleIds)
|
||||
{
|
||||
// 1. Guard Clause: Avoid unnecessary database work if there are no roles to add.
|
||||
@ -839,8 +863,146 @@ namespace Marco.Pms.Services.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
// ------------------------------------ Report Cache ---------------------------------------
|
||||
#region ======================================================= Expenses Cache =======================================================
|
||||
public async Task AddExpenseByObjectAsync(Expenses expense)
|
||||
{
|
||||
var expenseCache = _mapper.Map<ExpenseDetailsMongoDB>(expense);
|
||||
|
||||
try
|
||||
{
|
||||
var billAttachments = await _context.BillAttachments
|
||||
.Include(ba => ba.Document)
|
||||
.AsNoTracking()
|
||||
.Where(ba => ba.ExpensesId == expense.Id && ba.Document != null)
|
||||
.GroupBy(ba => ba.ExpensesId)
|
||||
.Select(g => new
|
||||
{
|
||||
S3Keys = g.Select(ba => ba.Document!.S3Key).ToList(),
|
||||
ThumbS3Keys = g.Select(ba => ba.Document!.ThumbS3Key ?? ba.Document.S3Key).ToList()
|
||||
})
|
||||
.FirstOrDefaultAsync(); ;
|
||||
if (billAttachments != null)
|
||||
{
|
||||
expenseCache.S3Key = billAttachments.S3Keys;
|
||||
expenseCache.ThumbS3Key = billAttachments.ThumbS3Keys;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error occurd while fetched expense related tables to save in cahce");
|
||||
}
|
||||
try
|
||||
{
|
||||
await _expenseCache.AddExpenseToCacheAsync(expenseCache);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error occurd while storing expense related table in cahce");
|
||||
}
|
||||
|
||||
}
|
||||
public async Task AddExpenseByIdAsync(Guid Id, Guid tenantId)
|
||||
{
|
||||
var expense = await _context.Expenses.AsNoTracking().FirstOrDefaultAsync(e => e.Id == Id && e.TenantId == tenantId);
|
||||
var expenseCache = _mapper.Map<ExpenseDetailsMongoDB>(expense);
|
||||
if (expense != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var billAttachments = await _context.BillAttachments
|
||||
.Include(ba => ba.Document)
|
||||
.AsNoTracking()
|
||||
.Where(ba => ba.ExpensesId == expense.Id && ba.Document != null)
|
||||
.GroupBy(ba => ba.ExpensesId)
|
||||
.Select(g => new
|
||||
{
|
||||
S3Keys = g.Select(ba => ba.Document!.S3Key).ToList(),
|
||||
ThumbS3Keys = g.Select(ba => ba.Document!.ThumbS3Key ?? ba.Document.S3Key).ToList()
|
||||
})
|
||||
.FirstOrDefaultAsync();
|
||||
if (billAttachments != null)
|
||||
{
|
||||
expenseCache.S3Key = billAttachments.S3Keys;
|
||||
expenseCache.ThumbS3Key = billAttachments.ThumbS3Keys;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error occurd while fetched expense related tables to save in cahce");
|
||||
}
|
||||
}
|
||||
try
|
||||
{
|
||||
await _expenseCache.AddExpenseToCacheAsync(expenseCache);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error occurd while storing expense related table in cahce");
|
||||
}
|
||||
|
||||
}
|
||||
public async Task AddExpensesListToCache(List<Expenses> expenses)
|
||||
{
|
||||
var expensesCache = _mapper.Map<List<ExpenseDetailsMongoDB>>(expenses);
|
||||
var expenseIds = expenses.Select(e => e.Id).ToList();
|
||||
try
|
||||
{
|
||||
var billAttachments = await _context.BillAttachments
|
||||
.Include(ba => ba.Document)
|
||||
.AsNoTracking()
|
||||
.Where(ba => expenseIds.Contains(ba.ExpensesId) && ba.Document != null)
|
||||
.GroupBy(ba => ba.ExpensesId)
|
||||
.Select(g => new
|
||||
{
|
||||
ExpensesId = g.Key,
|
||||
S3Keys = g.Select(ba => ba.Document!.S3Key).ToList(),
|
||||
ThumbS3Keys = g.Select(ba => ba.Document!.ThumbS3Key ?? ba.Document.S3Key).ToList()
|
||||
})
|
||||
.ToListAsync();
|
||||
foreach (var expenseCache in expensesCache)
|
||||
{
|
||||
expenseCache.S3Key = billAttachments.Where(ba => ba.ExpensesId == Guid.Parse(expenseCache.Id)).Select(ba => ba.S3Keys).FirstOrDefault() ?? new List<string>();
|
||||
expenseCache.ThumbS3Key = billAttachments.Where(ba => ba.ExpensesId == Guid.Parse(expenseCache.Id)).Select(ba => ba.ThumbS3Keys).FirstOrDefault();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error occurd while fetched expense related tables to save in cahce");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await _expenseCache.AddExpensesListToCacheAsync(expensesCache);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error occured while saving the list of expenses to Cache");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<(int totalPages, long totalCount, List<ExpenseDetailsMongoDB>? expenseList)> GetExpenseListAsync(Guid tenantId, Guid loggedInEmployeeId, bool viewAll,
|
||||
bool viewSelf, int pageNumber, int pageSize, ExpensesFilter? filter)
|
||||
{
|
||||
try
|
||||
{
|
||||
var (totalPages, totalCount, expenseList) = await _expenseCache.GetExpenseListFromCacheAsync(tenantId, loggedInEmployeeId, viewAll, viewSelf, pageNumber, pageSize, filter);
|
||||
if (expenseList.Any())
|
||||
{
|
||||
return (totalPages, totalCount, expenseList);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error occured while fetching the list of expenses to Cache");
|
||||
}
|
||||
return (0, 0, null);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ======================================================= Report Cache =======================================================
|
||||
|
||||
public async Task<List<ProjectReportEmailMongoDB>?> GetProjectReportMail(bool IsSend)
|
||||
{
|
||||
@ -866,5 +1028,7 @@ namespace Marco.Pms.Services.Helpers
|
||||
_logger.LogError(ex, "Error occured while adding project report mail bodys");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,8 @@ using Marco.Pms.Model.Dtos.Project;
|
||||
using Marco.Pms.Model.Employees;
|
||||
using Marco.Pms.Model.Expenses;
|
||||
using Marco.Pms.Model.Master;
|
||||
using Marco.Pms.Model.MongoDBModels.Employees;
|
||||
using Marco.Pms.Model.MongoDBModels.Expenses;
|
||||
using Marco.Pms.Model.MongoDBModels.Masters;
|
||||
using Marco.Pms.Model.MongoDBModels.Project;
|
||||
using Marco.Pms.Model.Projects;
|
||||
@ -36,6 +38,23 @@ namespace Marco.Pms.Services.MappingProfiles
|
||||
opt => opt.MapFrom(src => new Guid(src.Id))
|
||||
);
|
||||
|
||||
CreateMap<Project, ProjectBasicMongoDB>()
|
||||
.ForMember(
|
||||
dest => dest.Id,
|
||||
// Explicitly and safely convert Guid Id to string Id
|
||||
opt => opt.MapFrom(src => src.Id.ToString()))
|
||||
.ForMember(
|
||||
dest => dest.TenantId,
|
||||
opt => opt.MapFrom(src => src.TenantId.ToString()));
|
||||
CreateMap<ProjectBasicMongoDB, ProjectInfoVM>()
|
||||
.ForMember(
|
||||
dest => dest.Id,
|
||||
opt => opt.MapFrom(src => new Guid(src.Id)))
|
||||
.ForMember(
|
||||
dest => dest.ProjectStatusId,
|
||||
opt => opt.MapFrom(src => Guid.Empty)
|
||||
);
|
||||
|
||||
CreateMap<ProjectMongoDB, Project>()
|
||||
.ForMember(
|
||||
dest => dest.Id,
|
||||
@ -73,6 +92,27 @@ namespace Marco.Pms.Services.MappingProfiles
|
||||
.ForMember(
|
||||
dest => dest.JobRoleName,
|
||||
opt => opt.MapFrom(src => src.JobRole != null ? src.JobRole.Name : ""));
|
||||
|
||||
CreateMap<Employee, BasicEmployeeMongoDB>()
|
||||
.ForMember(
|
||||
dest => dest.Id,
|
||||
opt => opt.MapFrom(src => src.Id.ToString()))
|
||||
.ForMember(
|
||||
dest => dest.JobRoleName,
|
||||
opt => opt.MapFrom(src => src.JobRole != null ? src.JobRole.Name : ""))
|
||||
.ForMember(
|
||||
dest => dest.JobRoleId,
|
||||
opt => opt.MapFrom(src => src.JobRoleId.ToString()))
|
||||
.ForMember(
|
||||
dest => dest.TenantId,
|
||||
opt => opt.MapFrom(src => src.TenantId.ToString()));
|
||||
CreateMap<BasicEmployeeMongoDB, BasicEmployeeVM>()
|
||||
.ForMember(
|
||||
dest => dest.Id,
|
||||
opt => opt.MapFrom(src => Guid.Parse(src.Id)))
|
||||
.ForMember(
|
||||
dest => dest.JobRoleId,
|
||||
opt => opt.MapFrom(src => Guid.Parse(src.JobRoleId ?? "")));
|
||||
#endregion
|
||||
|
||||
#region ======================================================= Expenses =======================================================
|
||||
@ -80,6 +120,62 @@ namespace Marco.Pms.Services.MappingProfiles
|
||||
CreateMap<Expenses, ExpenseList>();
|
||||
CreateMap<CreateExpensesDto, Expenses>();
|
||||
CreateMap<UpdateExpensesDto, Expenses>();
|
||||
CreateMap<Expenses, ExpenseDetailsMongoDB>()
|
||||
.ForMember(
|
||||
dest => dest.Id,
|
||||
opt => opt.MapFrom(src => src.Id.ToString()))
|
||||
.ForMember(
|
||||
dest => dest.ProjectId,
|
||||
opt => opt.MapFrom(src => src.ProjectId.ToString()))
|
||||
.ForMember(
|
||||
dest => dest.ExpensesTypeId,
|
||||
opt => opt.MapFrom(src => src.ExpensesTypeId.ToString()))
|
||||
.ForMember(
|
||||
dest => dest.PaymentModeId,
|
||||
opt => opt.MapFrom(src => src.PaymentModeId.ToString()))
|
||||
.ForMember(
|
||||
dest => dest.PaidById,
|
||||
opt => opt.MapFrom(src => src.PaidById.ToString()))
|
||||
.ForMember(
|
||||
dest => dest.CreatedById,
|
||||
opt => opt.MapFrom(src => src.CreatedById.ToString()))
|
||||
.ForMember(
|
||||
dest => dest.StatusId,
|
||||
opt => opt.MapFrom(src => src.StatusId.ToString()))
|
||||
.ForMember(
|
||||
dest => dest.TenantId,
|
||||
opt => opt.MapFrom(src => src.TenantId.ToString()));
|
||||
|
||||
CreateMap<ExpenseDetailsMongoDB, Expenses>()
|
||||
.ForMember(
|
||||
dest => dest.Id,
|
||||
opt => opt.MapFrom(src => Guid.Parse(src.Id)))
|
||||
.ForMember(
|
||||
dest => dest.ProjectId,
|
||||
opt => opt.MapFrom(src => Guid.Parse(src.ProjectId)))
|
||||
.ForMember(
|
||||
dest => dest.ExpensesTypeId,
|
||||
opt => opt.MapFrom(src => Guid.Parse(src.ExpensesTypeId)))
|
||||
.ForMember(
|
||||
dest => dest.PaymentModeId,
|
||||
opt => opt.MapFrom(src => Guid.Parse(src.PaymentModeId)))
|
||||
.ForMember(
|
||||
dest => dest.PaidById,
|
||||
opt => opt.MapFrom(src => Guid.Parse(src.PaidById)))
|
||||
.ForMember(
|
||||
dest => dest.CreatedById,
|
||||
opt => opt.MapFrom(src => Guid.Parse(src.CreatedById)))
|
||||
.ForMember(
|
||||
dest => dest.StatusId,
|
||||
opt => opt.MapFrom(src => Guid.Parse(src.StatusId)))
|
||||
.ForMember(
|
||||
dest => dest.TenantId,
|
||||
opt => opt.MapFrom(src => Guid.Parse(src.TenantId)));
|
||||
|
||||
CreateMap<ExpenseDetailsMongoDB, ExpenseList>()
|
||||
.ForMember(
|
||||
dest => dest.Id,
|
||||
opt => opt.MapFrom(src => Guid.Parse(src.Id)));
|
||||
|
||||
#endregion
|
||||
|
||||
@ -93,6 +189,44 @@ namespace Marco.Pms.Services.MappingProfiles
|
||||
CreateMap<ExpensesTypeMaster, ExpensesTypeMasterVM>();
|
||||
CreateMap<ExpensesStatusMaster, ExpensesStatusMasterVM>();
|
||||
CreateMap<PaymentModeMatser, PaymentModeMatserVM>();
|
||||
|
||||
CreateMap<ExpensesTypeMaster, ExpensesTypeMasterMongoDB>()
|
||||
.ForMember(
|
||||
dest => dest.Id,
|
||||
opt => opt.MapFrom(src => src.Id.ToString()))
|
||||
.ForMember(
|
||||
dest => dest.TenantId,
|
||||
opt => opt.MapFrom(src => src.TenantId.ToString()));
|
||||
CreateMap<ExpensesTypeMasterMongoDB, ExpensesTypeMasterVM>()
|
||||
.ForMember(
|
||||
dest => dest.Id,
|
||||
opt => opt.MapFrom(src => Guid.Parse(src.Id)));
|
||||
|
||||
CreateMap<ExpensesStatusMaster, ExpensesStatusMasterMongoDB>()
|
||||
.ForMember(
|
||||
dest => dest.Id,
|
||||
opt => opt.MapFrom(src => src.Id.ToString()))
|
||||
.ForMember(
|
||||
dest => dest.TenantId,
|
||||
opt => opt.MapFrom(src => src.TenantId.ToString()));
|
||||
CreateMap<ExpensesStatusMasterMongoDB, ExpensesStatusMasterVM>()
|
||||
.ForMember(
|
||||
dest => dest.Id,
|
||||
opt => opt.MapFrom(src => Guid.Parse(src.Id)));
|
||||
|
||||
CreateMap<PaymentModeMatser, PaymentModeMatserMongoDB>()
|
||||
.ForMember(
|
||||
dest => dest.Id,
|
||||
opt => opt.MapFrom(src => src.Id.ToString()))
|
||||
.ForMember(
|
||||
dest => dest.TenantId,
|
||||
opt => opt.MapFrom(src => src.TenantId.ToString()));
|
||||
CreateMap<PaymentModeMatserMongoDB, PaymentModeMatserVM>()
|
||||
.ForMember(
|
||||
dest => dest.Id,
|
||||
opt => opt.MapFrom(src => Guid.Parse(src.Id)));
|
||||
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
using AutoMapper;
|
||||
using Marco.Pms.Helpers;
|
||||
using Marco.Pms.DataAccess.Data;
|
||||
using Marco.Pms.Model.DocumentManager;
|
||||
using Marco.Pms.Helpers;
|
||||
using Marco.Pms.Model.Dtos.Expenses;
|
||||
using Marco.Pms.Model.Employees;
|
||||
using Marco.Pms.Model.Entitlements;
|
||||
@ -12,10 +11,13 @@ using Marco.Pms.Model.ViewModels.Activities;
|
||||
using Marco.Pms.Model.ViewModels.Expanses;
|
||||
using Marco.Pms.Model.ViewModels.Master;
|
||||
using Marco.Pms.Model.ViewModels.Projects;
|
||||
using Marco.Pms.Services.Helpers;
|
||||
using Marco.Pms.Services.Service.ServiceInterfaces;
|
||||
using MarcoBMS.Services.Service;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Text.Json;
|
||||
using Document = Marco.Pms.Model.DocumentManager.Document;
|
||||
|
||||
namespace Marco.Pms.Services.Service
|
||||
{
|
||||
@ -27,6 +29,7 @@ namespace Marco.Pms.Services.Service
|
||||
private readonly S3UploadService _s3Service;
|
||||
private readonly IServiceScopeFactory _serviceScopeFactory;
|
||||
private readonly UpdateLogHelper _updateLogHelper;
|
||||
private readonly CacheUpdateHelper _cache;
|
||||
private readonly IMapper _mapper;
|
||||
private static readonly Guid Draft = Guid.Parse("297e0d8f-f668-41b5-bfea-e03b354251c8");
|
||||
private static readonly Guid Rejected = Guid.Parse("d1ee5eec-24b6-4364-8673-a8f859c60729");
|
||||
@ -36,6 +39,7 @@ namespace Marco.Pms.Services.Service
|
||||
ApplicationDbContext context,
|
||||
IServiceScopeFactory serviceScopeFactory,
|
||||
UpdateLogHelper updateLogHelper,
|
||||
CacheUpdateHelper cache,
|
||||
ILoggingService logger,
|
||||
S3UploadService s3Service,
|
||||
IMapper mapper)
|
||||
@ -43,6 +47,7 @@ namespace Marco.Pms.Services.Service
|
||||
_dbContextFactory = dbContextFactory;
|
||||
_context = context;
|
||||
_logger = logger;
|
||||
_cache = cache;
|
||||
_serviceScopeFactory = serviceScopeFactory;
|
||||
_updateLogHelper = updateLogHelper;
|
||||
_s3Service = s3Service;
|
||||
@ -73,7 +78,8 @@ namespace Marco.Pms.Services.Service
|
||||
return ApiResponse<object>.ErrorResponse("User not found or not authenticated.", 403);
|
||||
}
|
||||
Guid loggedInEmployeeId = loggedInEmployee.Id;
|
||||
|
||||
List<ExpenseList> expenseVM = new List<ExpenseList>();
|
||||
var totalEntites = 0;
|
||||
var hasViewSelfPermissionTask = Task.Run(async () =>
|
||||
{
|
||||
using var scope = _serviceScopeFactory.CreateScope();
|
||||
@ -90,120 +96,103 @@ namespace Marco.Pms.Services.Service
|
||||
|
||||
await Task.WhenAll(hasViewSelfPermissionTask, hasViewAllPermissionTask);
|
||||
|
||||
// 2. --- Build Base Query and Apply Permissions ---
|
||||
// Start with a base IQueryable. Filters will be chained onto this.
|
||||
var expensesQuery = _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)
|
||||
.Where(e => e.TenantId == tenantId); // Always filter by TenantId first.
|
||||
|
||||
// Apply permission-based filtering BEFORE any other filters or pagination.
|
||||
if (hasViewAllPermissionTask.Result)
|
||||
{
|
||||
// User has 'View All' permission, no initial restriction on who created the expense.
|
||||
_logger.LogInfo("User {EmployeeId} has 'View All' permission.", loggedInEmployeeId);
|
||||
}
|
||||
else if (hasViewSelfPermissionTask.Result)
|
||||
{
|
||||
// User only has 'View Self' permission, so restrict the query to their own expenses.
|
||||
_logger.LogInfo("User {EmployeeId} has 'View Self' permission. Restricting query to their expenses.", loggedInEmployeeId);
|
||||
expensesQuery = expensesQuery.Where(e => e.CreatedById == loggedInEmployeeId);
|
||||
}
|
||||
else
|
||||
if (!hasViewAllPermissionTask.Result && !hasViewSelfPermissionTask.Result)
|
||||
{
|
||||
// User has neither required permission. Deny access.
|
||||
_logger.LogWarning("Access DENIED for employee {EmployeeId} attempting to get expenses list.", loggedInEmployeeId);
|
||||
return ApiResponse<object>.SuccessResponse(new List<ExpenseList>(), "You do not have permission to view any expenses.", 200);
|
||||
}
|
||||
|
||||
// 3. --- Deserialize Filter and Apply ---
|
||||
|
||||
// 2. --- Deserialize Filter and Apply ---
|
||||
ExpensesFilter? expenseFilter = TryDeserializeFilter(filter);
|
||||
|
||||
if (expenseFilter != null)
|
||||
var (totalPages, totalCount, expenseList) = await _cache.GetExpenseListAsync(tenantId, loggedInEmployeeId, hasViewAllPermissionTask.Result, hasViewSelfPermissionTask.Result,
|
||||
pageNumber, pageSize, expenseFilter);
|
||||
|
||||
if (expenseList == null)
|
||||
{
|
||||
// CRITICAL FIX: Apply filters cumulatively using multiple `if` statements, not `if-else if`.
|
||||
if (expenseFilter.StartDate.HasValue && expenseFilter.EndDate.HasValue)
|
||||
|
||||
// 3. --- Build Base Query and Apply Permissions ---
|
||||
// Start with a base IQueryable. Filters will be chained onto this.
|
||||
var expensesQuery = _context.Expenses
|
||||
.Where(e => e.TenantId == tenantId); // Always filter by TenantId first.
|
||||
|
||||
await _cache.AddExpensesListToCache(expenses: await expensesQuery.ToListAsync());
|
||||
|
||||
// Apply permission-based filtering BEFORE any other filters or pagination.
|
||||
|
||||
if (!hasViewAllPermissionTask.Result && hasViewSelfPermissionTask.Result)
|
||||
{
|
||||
expensesQuery = expensesQuery.Where(e => e.CreatedAt.Date >= expenseFilter.StartDate.Value.Date && e.CreatedAt.Date <= expenseFilter.EndDate.Value.Date);
|
||||
// User only has 'View Self' permission, so restrict the query to their own expenses.
|
||||
_logger.LogInfo("User {EmployeeId} has 'View Self' permission. Restricting query to their expenses.", loggedInEmployeeId);
|
||||
expensesQuery = expensesQuery.Where(e => e.CreatedById == loggedInEmployeeId);
|
||||
}
|
||||
|
||||
if (expenseFilter.ProjectIds?.Any() == true)
|
||||
if (expenseFilter != null)
|
||||
{
|
||||
expensesQuery = expensesQuery.Where(e => expenseFilter.ProjectIds.Contains(e.ProjectId));
|
||||
// CRITICAL FIX: Apply filters cumulatively using multiple `if` statements, not `if-else if`.
|
||||
if (expenseFilter.StartDate.HasValue && expenseFilter.EndDate.HasValue)
|
||||
{
|
||||
expensesQuery = expensesQuery.Where(e => e.CreatedAt.Date >= expenseFilter.StartDate.Value.Date && e.CreatedAt.Date <= expenseFilter.EndDate.Value.Date);
|
||||
}
|
||||
|
||||
if (expenseFilter.ProjectIds?.Any() == true)
|
||||
{
|
||||
expensesQuery = expensesQuery.Where(e => expenseFilter.ProjectIds.Contains(e.ProjectId));
|
||||
}
|
||||
|
||||
if (expenseFilter.StatusIds?.Any() == true)
|
||||
{
|
||||
expensesQuery = expensesQuery.Where(e => expenseFilter.StatusIds.Contains(e.StatusId));
|
||||
}
|
||||
|
||||
if (expenseFilter.PaidById?.Any() == true)
|
||||
{
|
||||
expensesQuery = expensesQuery.Where(e => expenseFilter.PaidById.Contains(e.PaidById));
|
||||
}
|
||||
|
||||
// Only allow filtering by 'CreatedBy' if the user has 'View All' permission.
|
||||
if (expenseFilter.CreatedByIds?.Any() == true && hasViewAllPermissionTask.Result)
|
||||
{
|
||||
expensesQuery = expensesQuery.Where(e => expenseFilter.CreatedByIds.Contains(e.CreatedById));
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
if (expenseFilter.StatusIds?.Any() == true)
|
||||
// 4. --- Apply Ordering and Pagination ---
|
||||
// This should be the last step before executing the query.
|
||||
|
||||
totalEntites = await expensesQuery.CountAsync();
|
||||
|
||||
var paginatedQuery = expensesQuery
|
||||
.OrderByDescending(e => e.CreatedAt)
|
||||
.Skip((pageNumber - 1) * pageSize)
|
||||
.Take(pageSize);
|
||||
|
||||
// 5. --- Execute Query and Map Results ---
|
||||
var expensesList = await paginatedQuery.ToListAsync();
|
||||
|
||||
if (!expensesList.Any())
|
||||
{
|
||||
expensesQuery = expensesQuery.Where(e => expenseFilter.StatusIds.Contains(e.StatusId));
|
||||
_logger.LogInfo("No expenses found matching the criteria for employee {EmployeeId}.", loggedInEmployeeId);
|
||||
return ApiResponse<object>.SuccessResponse(new List<ExpenseList>(), "No expenses found for the given criteria.", 200);
|
||||
}
|
||||
|
||||
if (expenseFilter.PaidById?.Any() == true)
|
||||
{
|
||||
expensesQuery = expensesQuery.Where(e => expenseFilter.PaidById.Contains(e.PaidById));
|
||||
}
|
||||
expenseVM = await GetAllExpnesRelatedTables(expensesList);
|
||||
|
||||
|
||||
// Only allow filtering by 'CreatedBy' if the user has 'View All' permission.
|
||||
if (expenseFilter.CreatedByIds?.Any() == true && hasViewAllPermissionTask.Result)
|
||||
{
|
||||
expensesQuery = expensesQuery.Where(e => expenseFilter.CreatedByIds.Contains(e.CreatedById));
|
||||
}
|
||||
}
|
||||
|
||||
// 4. --- Apply Ordering and Pagination ---
|
||||
// This should be the last step before executing the query.
|
||||
|
||||
var totalEntites = await expensesQuery.CountAsync();
|
||||
|
||||
var paginatedQuery = expensesQuery
|
||||
.OrderByDescending(e => e.CreatedAt)
|
||||
.Skip((pageNumber - 1) * pageSize)
|
||||
.Take(pageSize);
|
||||
|
||||
// 5. --- Execute Query and Map Results ---
|
||||
var expensesList = await paginatedQuery.ToListAsync();
|
||||
|
||||
if (!expensesList.Any())
|
||||
else
|
||||
{
|
||||
_logger.LogInfo("No expenses found matching the criteria for employee {EmployeeId}.", loggedInEmployeeId);
|
||||
return ApiResponse<object>.SuccessResponse(new List<ExpenseList>(), "No expenses found for the given criteria.", 200);
|
||||
expenseVM = await GetAllExpnesRelatedTables(_mapper.Map<List<Expenses>>(expenseList));
|
||||
totalEntites = (int)totalCount;
|
||||
}
|
||||
|
||||
var expenseVM = _mapper.Map<List<ExpenseList>>(expensesList);
|
||||
|
||||
// 6. --- Efficiently Fetch and Append 'Next Status' Information ---
|
||||
var statusIds = expensesList.Select(e => e.StatusId).Distinct().ToList();
|
||||
|
||||
var statusMappings = await _context.ExpensesStatusMapping
|
||||
.Include(sm => sm.NextStatus)
|
||||
.Where(sm => statusIds.Contains(sm.StatusId))
|
||||
.ToListAsync();
|
||||
|
||||
// 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);
|
||||
|
||||
foreach (var expense in expenseVM)
|
||||
{
|
||||
if (expense.Status?.Id != null && statusMapLookup.Contains(expense.Status.Id))
|
||||
{
|
||||
expense.NextStatus = statusMapLookup[expense.Status.Id]
|
||||
.Select(sm => _mapper.Map<ExpensesStatusMasterVM>(sm.NextStatus))
|
||||
.ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
expense.NextStatus = new List<ExpensesStatusMasterVM>(); // Ensure it's never null
|
||||
}
|
||||
}
|
||||
|
||||
// 7. --- Return Final Success Response ---
|
||||
var message = $"{expenseVM.Count} expense records fetched successfully.";
|
||||
_logger.LogInfo(message);
|
||||
var totalPages = (int)Math.Ceiling((double)totalEntites / pageSize);
|
||||
var response = new
|
||||
{
|
||||
CurrentPage = pageNumber,
|
||||
@ -211,7 +200,6 @@ namespace Marco.Pms.Services.Service
|
||||
TotalEntites = totalEntites,
|
||||
Data = expenseVM,
|
||||
};
|
||||
|
||||
return ApiResponse<object>.SuccessResponse(response, message, 200);
|
||||
}
|
||||
catch (DbUpdateException dbEx)
|
||||
@ -381,6 +369,8 @@ namespace Marco.Pms.Services.Service
|
||||
// 6. Transaction Commit
|
||||
await transaction.CommitAsync();
|
||||
|
||||
await _cache.AddExpenseByObjectAsync(expense);
|
||||
|
||||
var response = _mapper.Map<ExpenseList>(expense);
|
||||
response.PaidBy = _mapper.Map<BasicEmployeeVM>(paidBy);
|
||||
response.Project = _mapper.Map<ProjectInfoVM>(project);
|
||||
@ -728,6 +718,86 @@ namespace Marco.Pms.Services.Service
|
||||
}
|
||||
#region =================================================================== Helper Functions ===================================================================
|
||||
|
||||
private async Task<List<ExpenseList>> GetAllExpnesRelatedTables(List<Expenses> model)
|
||||
{
|
||||
List<ExpenseList> expenseList = new List<ExpenseList>();
|
||||
var projectIds = model.Select(m => m.ProjectId).ToList();
|
||||
var statusIds = model.Select(m => m.StatusId).ToList();
|
||||
var expensesTypeIds = model.Select(m => m.ExpensesTypeId).ToList();
|
||||
var paymentModeIds = model.Select(m => m.PaymentModeId).ToList();
|
||||
var createdByIds = model.Select(m => m.CreatedById).ToList();
|
||||
var paidByIds = model.Select(m => m.PaidById).ToList();
|
||||
|
||||
var projectTask = Task.Run(async () =>
|
||||
{
|
||||
await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
|
||||
return await dbContext.Projects.AsNoTracking().Where(p => projectIds.Contains(p.Id)).ToListAsync();
|
||||
});
|
||||
var paidByTask = Task.Run(async () =>
|
||||
{
|
||||
await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
|
||||
return await dbContext.Employees.AsNoTracking().Where(e => paidByIds.Contains(e.Id)).ToListAsync();
|
||||
});
|
||||
var createdByTask = Task.Run(async () =>
|
||||
{
|
||||
await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
|
||||
return await dbContext.Employees.AsNoTracking().Where(e => createdByIds.Contains(e.Id)).ToListAsync();
|
||||
});
|
||||
var expenseTypeTask = Task.Run(async () =>
|
||||
{
|
||||
await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
|
||||
return await dbContext.ExpensesTypeMaster.AsNoTracking().Where(et => expensesTypeIds.Contains(et.Id)).ToListAsync();
|
||||
});
|
||||
var paymentModeTask = Task.Run(async () =>
|
||||
{
|
||||
await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
|
||||
return await dbContext.PaymentModeMatser.AsNoTracking().Where(pm => paymentModeIds.Contains(pm.Id)).ToListAsync();
|
||||
});
|
||||
var statusMappingTask = Task.Run(async () =>
|
||||
{
|
||||
await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
|
||||
return await dbContext.ExpensesStatusMapping
|
||||
.Include(s => s.Status)
|
||||
.Include(s => s.NextStatus)
|
||||
.AsNoTracking()
|
||||
.Where(es => statusIds.Contains(es.StatusId) && es.Status != null)
|
||||
.GroupBy(s => s.StatusId)
|
||||
.Select(g => new
|
||||
{
|
||||
StatusId = g.Key,
|
||||
Status = g.Select(s => s.Status).FirstOrDefault(),
|
||||
NextStatus = g.Select(s => s.NextStatus).ToList()
|
||||
}).ToListAsync();
|
||||
});
|
||||
|
||||
// Await all prerequisite checks at once.
|
||||
await Task.WhenAll(projectTask, expenseTypeTask, paymentModeTask, statusMappingTask, paidByTask, createdByTask);
|
||||
|
||||
var projects = await projectTask;
|
||||
var expenseTypes = await expenseTypeTask;
|
||||
var paymentModes = await paymentModeTask;
|
||||
var statusMappings = await statusMappingTask;
|
||||
var paidBys = await paidByTask;
|
||||
var createdBys = await createdByTask;
|
||||
|
||||
expenseList = model.Select(m =>
|
||||
{
|
||||
var response = _mapper.Map<ExpenseList>(m);
|
||||
|
||||
response.Project = projects.Where(p => p.Id == m.ProjectId).Select(p => _mapper.Map<ProjectInfoVM>(p)).FirstOrDefault();
|
||||
response.PaidBy = paidBys.Where(p => p.Id == m.PaidById).Select(p => _mapper.Map<BasicEmployeeVM>(p)).FirstOrDefault();
|
||||
response.CreatedBy = createdBys.Where(e => e.Id == m.CreatedById).Select(e => _mapper.Map<BasicEmployeeVM>(e)).FirstOrDefault();
|
||||
response.Status = statusMappings.Where(s => s.StatusId == m.StatusId).Select(s => _mapper.Map<ExpensesStatusMasterVM>(s.Status)).FirstOrDefault();
|
||||
response.NextStatus = statusMappings.Where(s => s.StatusId == m.StatusId).Select(s => _mapper.Map<List<ExpensesStatusMasterVM>>(s.NextStatus)).FirstOrDefault();
|
||||
response.PaymentMode = paymentModes.Where(pm => pm.Id == m.PaymentModeId).Select(pm => _mapper.Map<PaymentModeMatserVM>(pm)).FirstOrDefault();
|
||||
response.ExpensesType = expenseTypes.Where(et => et.Id == m.ExpensesTypeId).Select(et => _mapper.Map<ExpensesTypeMasterVM>(et)).FirstOrDefault();
|
||||
|
||||
return response;
|
||||
}).ToList();
|
||||
|
||||
return expenseList;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deserializes the filter string, handling multiple potential formats (e.g., direct JSON vs. escaped JSON string).
|
||||
/// </summary>
|
||||
|
Loading…
x
Reference in New Issue
Block a user