Removed the project forign key and able to create payment request for both infra and service project

This commit is contained in:
ashutosh.nehete 2025-11-20 17:59:59 +05:30
parent 9c95b12a8f
commit 1b94592026
5 changed files with 9124 additions and 107 deletions

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,38 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Marco.Pms.DataAccess.Migrations
{
/// <inheritdoc />
public partial class Removed_Project_ForignKey_From_PaymentRequest_Table : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_PaymentRequests_Projects_ProjectId",
table: "PaymentRequests");
migrationBuilder.DropIndex(
name: "IX_PaymentRequests_ProjectId",
table: "PaymentRequests");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateIndex(
name: "IX_PaymentRequests_ProjectId",
table: "PaymentRequests",
column: "ProjectId");
migrationBuilder.AddForeignKey(
name: "FK_PaymentRequests_Projects_ProjectId",
table: "PaymentRequests",
column: "ProjectId",
principalTable: "Projects",
principalColumn: "Id");
}
}
}

View File

@ -2796,8 +2796,6 @@ namespace Marco.Pms.DataAccess.Migrations
b.HasIndex("PaidById"); b.HasIndex("PaidById");
b.HasIndex("ProjectId");
b.HasIndex("RecurringPaymentId"); b.HasIndex("RecurringPaymentId");
b.HasIndex("TenantId"); b.HasIndex("TenantId");
@ -7538,10 +7536,6 @@ namespace Marco.Pms.DataAccess.Migrations
.WithMany() .WithMany()
.HasForeignKey("PaidById"); .HasForeignKey("PaidById");
b.HasOne("Marco.Pms.Model.Projects.Project", "Project")
.WithMany()
.HasForeignKey("ProjectId");
b.HasOne("Marco.Pms.Model.Expenses.RecurringPayment", "RecurringPayment") b.HasOne("Marco.Pms.Model.Expenses.RecurringPayment", "RecurringPayment")
.WithMany() .WithMany()
.HasForeignKey("RecurringPaymentId"); .HasForeignKey("RecurringPaymentId");
@ -7566,8 +7560,6 @@ namespace Marco.Pms.DataAccess.Migrations
b.Navigation("PaidBy"); b.Navigation("PaidBy");
b.Navigation("Project");
b.Navigation("RecurringPayment"); b.Navigation("RecurringPayment");
b.Navigation("Tenant"); b.Navigation("Tenant");

View File

@ -1,7 +1,6 @@
using Marco.Pms.Model.Employees; using Marco.Pms.Model.Employees;
using Marco.Pms.Model.Expenses.Masters; using Marco.Pms.Model.Expenses.Masters;
using Marco.Pms.Model.Master; using Marco.Pms.Model.Master;
using Marco.Pms.Model.Projects;
using Marco.Pms.Model.Utilities; using Marco.Pms.Model.Utilities;
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
@ -28,10 +27,6 @@ namespace Marco.Pms.Model.Expenses
public double? TDSPercentage { get; set; } public double? TDSPercentage { get; set; }
public DateTime DueDate { get; set; } public DateTime DueDate { get; set; }
public Guid? ProjectId { get; set; } public Guid? ProjectId { get; set; }
[ValidateNever]
[ForeignKey("ProjectId")]
public Project? Project { get; set; }
public Guid? RecurringPaymentId { get; set; } public Guid? RecurringPaymentId { get; set; }
[ValidateNever] [ValidateNever]

View File

@ -377,8 +377,8 @@ namespace Marco.Pms.Services.Service
var permissionStatusMappingTask = Task.Run(async () => var permissionStatusMappingTask = Task.Run(async () =>
{ {
await using var dbContext = await _dbContextFactory.CreateDbContextAsync(); await using var context = await _dbContextFactory.CreateDbContextAsync();
return await dbContext.StatusPermissionMapping return await context.StatusPermissionMapping
.GroupBy(ps => ps.StatusId) .GroupBy(ps => ps.StatusId)
.Select(g => new .Select(g => new
{ {
@ -388,8 +388,8 @@ namespace Marco.Pms.Services.Service
}); });
var expenseReimburseTask = Task.Run(async () => var expenseReimburseTask = Task.Run(async () =>
{ {
await using var dbContext = await _dbContextFactory.CreateDbContextAsync(); await using var context = await _dbContextFactory.CreateDbContextAsync();
return await dbContext.ExpensesReimburseMapping return await context.ExpensesReimburseMapping
.Include(er => er.ExpensesReimburse) .Include(er => er.ExpensesReimburse)
.ThenInclude(er => er!.ReimburseBy) .ThenInclude(er => er!.ReimburseBy)
.ThenInclude(e => e!.JobRole) .ThenInclude(e => e!.JobRole)
@ -541,8 +541,8 @@ namespace Marco.Pms.Services.Service
// Each task gets its own DbContext instance. // Each task gets its own DbContext instance.
var infraProjectTask = Task.Run(async () => var infraProjectTask = Task.Run(async () =>
{ {
await using var dbContext = await _dbContextFactory.CreateDbContextAsync(); await using var context = await _dbContextFactory.CreateDbContextAsync();
return await dbContext.Projects return await context.Projects
.AsNoTracking() .AsNoTracking()
.Where(p => p.Id == dto.ProjectId && p.TenantId == tenantId) .Where(p => p.Id == dto.ProjectId && p.TenantId == tenantId)
.Select(p => _mapper.Map<BasicProjectVM>(p)) .Select(p => _mapper.Map<BasicProjectVM>(p))
@ -550,8 +550,8 @@ namespace Marco.Pms.Services.Service
}); });
var serviceProjectTask = Task.Run(async () => var serviceProjectTask = Task.Run(async () =>
{ {
await using var dbContext = await _dbContextFactory.CreateDbContextAsync(); await using var context = await _dbContextFactory.CreateDbContextAsync();
return await dbContext.ServiceProjects return await context.ServiceProjects
.AsNoTracking() .AsNoTracking()
.Where(sp => sp.Id == dto.ProjectId && sp.TenantId == tenantId) .Where(sp => sp.Id == dto.ProjectId && sp.TenantId == tenantId)
.Select(sp => _mapper.Map<BasicProjectVM>(sp)) .Select(sp => _mapper.Map<BasicProjectVM>(sp))
@ -559,23 +559,23 @@ namespace Marco.Pms.Services.Service
}); });
var paidByTask = Task.Run(async () => var paidByTask = Task.Run(async () =>
{ {
await using var dbContext = await _dbContextFactory.CreateDbContextAsync(); await using var context = await _dbContextFactory.CreateDbContextAsync();
return await dbContext.Employees.AsNoTracking().FirstOrDefaultAsync(e => e.Id == dto.PaidById); return await context.Employees.AsNoTracking().FirstOrDefaultAsync(e => e.Id == dto.PaidById);
}); });
var expenseCategoriesTask = Task.Run(async () => var expenseCategoriesTask = Task.Run(async () =>
{ {
await using var dbContext = await _dbContextFactory.CreateDbContextAsync(); await using var context = await _dbContextFactory.CreateDbContextAsync();
return await dbContext.ExpenseCategoryMasters.AsNoTracking().FirstOrDefaultAsync(et => et.Id == dto.ExpenseCategoryId); return await context.ExpenseCategoryMasters.AsNoTracking().FirstOrDefaultAsync(et => et.Id == dto.ExpenseCategoryId);
}); });
var paymentModeTask = Task.Run(async () => var paymentModeTask = Task.Run(async () =>
{ {
await using var dbContext = await _dbContextFactory.CreateDbContextAsync(); await using var context = await _dbContextFactory.CreateDbContextAsync();
return await dbContext.PaymentModeMatser.AsNoTracking().FirstOrDefaultAsync(pm => pm.Id == dto.PaymentModeId); return await context.PaymentModeMatser.AsNoTracking().FirstOrDefaultAsync(pm => pm.Id == dto.PaymentModeId);
}); });
var statusMappingTask = Task.Run(async () => var statusMappingTask = Task.Run(async () =>
{ {
await using var dbContext = await _dbContextFactory.CreateDbContextAsync(); await using var context = await _dbContextFactory.CreateDbContextAsync();
return await dbContext.ExpensesStatusMapping return await context.ExpensesStatusMapping
.Include(s => s.Status) .Include(s => s.Status)
.Include(s => s.NextStatus) .Include(s => s.NextStatus)
.AsNoTracking() .AsNoTracking()
@ -746,22 +746,22 @@ namespace Marco.Pms.Services.Service
// 2. Run Prerequisite Checks in Parallel (Status transition + Permissions) // 2. Run Prerequisite Checks in Parallel (Status transition + Permissions)
var processedStatusTask = Task.Run(async () => var processedStatusTask = Task.Run(async () =>
{ {
await using var dbContext = await _dbContextFactory.CreateDbContextAsync(); await using var context = await _dbContextFactory.CreateDbContextAsync();
return await dbContext.ExpensesStatusMaster return await context.ExpensesStatusMaster
.FirstOrDefaultAsync(es => es.Id == Processed); .FirstOrDefaultAsync(es => es.Id == Processed);
}); });
var statusTransitionTask = Task.Run(async () => var statusTransitionTask = Task.Run(async () =>
{ {
await using var dbContext = await _dbContextFactory.CreateDbContextAsync(); await using var context = await _dbContextFactory.CreateDbContextAsync();
return await dbContext.ExpensesStatusMapping return await context.ExpensesStatusMapping
.Include(m => m.NextStatus) .Include(m => m.NextStatus)
.FirstOrDefaultAsync(m => m.StatusId == expense.StatusId && m.NextStatusId == model.StatusId); .FirstOrDefaultAsync(m => m.StatusId == expense.StatusId && m.NextStatusId == model.StatusId);
}); });
var targetStatusPermissionsTask = Task.Run(async () => var targetStatusPermissionsTask = Task.Run(async () =>
{ {
await using var dbContext = await _dbContextFactory.CreateDbContextAsync(); await using var context = await _dbContextFactory.CreateDbContextAsync();
return await dbContext.StatusPermissionMapping return await context.StatusPermissionMapping
.Where(spm => spm.StatusId == model.StatusId) .Where(spm => spm.StatusId == model.StatusId)
.ToListAsync(); .ToListAsync();
}); });
@ -977,13 +977,13 @@ namespace Marco.Pms.Services.Service
var getNextStatusesTask = _dbContextFactory.CreateDbContextAsync().ContinueWith(async t => var getNextStatusesTask = _dbContextFactory.CreateDbContextAsync().ContinueWith(async t =>
{ {
var dbContext = t.Result; var context = t.Result;
var nextStatuses = await dbContext.ExpensesStatusMapping var nextStatuses = await context.ExpensesStatusMapping
.Include(m => m.NextStatus) .Include(m => m.NextStatus)
.Where(m => m.StatusId == expense.StatusId && m.NextStatus != null) .Where(m => m.StatusId == expense.StatusId && m.NextStatus != null)
.Select(m => m.NextStatus) .Select(m => m.NextStatus)
.ToListAsync(); .ToListAsync();
await dbContext.DisposeAsync(); await context.DisposeAsync();
return nextStatuses; return nextStatuses;
}).Unwrap(); }).Unwrap();
@ -997,8 +997,8 @@ namespace Marco.Pms.Services.Service
var infraProjectTask = Task.Run(async () => var infraProjectTask = Task.Run(async () =>
{ {
await using var dbContext = await _dbContextFactory.CreateDbContextAsync(); await using var context = await _dbContextFactory.CreateDbContextAsync();
return await dbContext.Projects return await context.Projects
.AsNoTracking() .AsNoTracking()
.Where(p => p.Id == expense.ProjectId && p.TenantId == tenantId) .Where(p => p.Id == expense.ProjectId && p.TenantId == tenantId)
.Select(p => _mapper.Map<BasicProjectVM>(p)) .Select(p => _mapper.Map<BasicProjectVM>(p))
@ -1006,8 +1006,8 @@ namespace Marco.Pms.Services.Service
}); });
var serviceProjectTask = Task.Run(async () => var serviceProjectTask = Task.Run(async () =>
{ {
await using var dbContext = await _dbContextFactory.CreateDbContextAsync(); await using var context = await _dbContextFactory.CreateDbContextAsync();
return await dbContext.ServiceProjects return await context.ServiceProjects
.AsNoTracking() .AsNoTracking()
.Where(sp => sp.Id == expense.ProjectId && sp.TenantId == tenantId) .Where(sp => sp.Id == expense.ProjectId && sp.TenantId == tenantId)
.Select(sp => _mapper.Map<BasicProjectVM>(sp)) .Select(sp => _mapper.Map<BasicProjectVM>(sp))
@ -1102,8 +1102,8 @@ namespace Marco.Pms.Services.Service
var infraProjectTask = Task.Run(async () => var infraProjectTask = Task.Run(async () =>
{ {
await using var dbContext = await _dbContextFactory.CreateDbContextAsync(); await using var context = await _dbContextFactory.CreateDbContextAsync();
return await dbContext.Projects return await context.Projects
.AsNoTracking() .AsNoTracking()
.Where(p => p.Id == existingExpense.ProjectId && p.TenantId == tenantId) .Where(p => p.Id == existingExpense.ProjectId && p.TenantId == tenantId)
.Select(p => _mapper.Map<BasicProjectVM>(p)) .Select(p => _mapper.Map<BasicProjectVM>(p))
@ -1111,8 +1111,8 @@ namespace Marco.Pms.Services.Service
}); });
var serviceProjectTask = Task.Run(async () => var serviceProjectTask = Task.Run(async () =>
{ {
await using var dbContext = await _dbContextFactory.CreateDbContextAsync(); await using var context = await _dbContextFactory.CreateDbContextAsync();
return await dbContext.ServiceProjects return await context.ServiceProjects
.AsNoTracking() .AsNoTracking()
.Where(sp => sp.Id == existingExpense.ProjectId && sp.TenantId == tenantId) .Where(sp => sp.Id == existingExpense.ProjectId && sp.TenantId == tenantId)
.Select(sp => _mapper.Map<BasicProjectVM>(sp)) .Select(sp => _mapper.Map<BasicProjectVM>(sp))
@ -1184,15 +1184,15 @@ namespace Marco.Pms.Services.Service
// NOTE: This now fetches a list of all possible next states, which is more useful for a UI. // NOTE: This now fetches a list of all possible next states, which is more useful for a UI.
var getNextStatusesTask = _dbContextFactory.CreateDbContextAsync().ContinueWith(t => var getNextStatusesTask = _dbContextFactory.CreateDbContextAsync().ContinueWith(t =>
{ {
var dbContext = t.Result; var context = t.Result;
return dbContext.ExpensesStatusMapping return context.ExpensesStatusMapping
.Include(s => s.NextStatus) .Include(s => s.NextStatus)
.Where(s => s.StatusId == existingExpense.StatusId && s.NextStatus != null) .Where(s => s.StatusId == existingExpense.StatusId && s.NextStatus != null)
.Select(s => s.NextStatus) // Select only the status object .Select(s => s.NextStatus) // Select only the status object
.ToListAsync() .ToListAsync()
.ContinueWith(res => .ContinueWith(res =>
{ {
dbContext.Dispose(); // Ensure the context is disposed context.Dispose(); // Ensure the context is disposed
return res.Result; return res.Result;
}); });
}).Unwrap(); }).Unwrap();
@ -1238,8 +1238,8 @@ namespace Marco.Pms.Services.Service
{ {
var expenseTask = Task.Run(async () => var expenseTask = Task.Run(async () =>
{ {
await using var dbContext = await _dbContextFactory.CreateDbContextAsync(); await using var context = await _dbContextFactory.CreateDbContextAsync();
return await dbContext.Expenses.Where(e => e.Id == id && e.StatusId == Draft && e.TenantId == tenantId).FirstOrDefaultAsync(); return await context.Expenses.Where(e => e.Id == id && e.StatusId == Draft && e.TenantId == tenantId).FirstOrDefaultAsync();
}); });
var hasAprrovePermissionTask = Task.Run(async () => var hasAprrovePermissionTask = Task.Run(async () =>
@ -1370,7 +1370,6 @@ namespace Marco.Pms.Services.Service
// Initial query including the necessary navigation properties and basic multi-tenant/security constraints // Initial query including the necessary navigation properties and basic multi-tenant/security constraints
var paymentRequestQuery = _context.PaymentRequests var paymentRequestQuery = _context.PaymentRequests
.Include(pr => pr.Currency) .Include(pr => pr.Currency)
.Include(pr => pr.Project)
.Include(pr => pr.RecurringPayment) .Include(pr => pr.RecurringPayment)
.Include(pr => pr.ExpenseCategory) .Include(pr => pr.ExpenseCategory)
.Include(pr => pr.ExpenseStatus) .Include(pr => pr.ExpenseStatus)
@ -1459,10 +1458,30 @@ namespace Marco.Pms.Services.Service
var totalPages = (int)Math.Ceiling((double)totalEntities / pageSize); var totalPages = (int)Math.Ceiling((double)totalEntities / pageSize);
var projectIds = paymentRequests.Select(pr => pr.ProjectId).ToList();
var infraProjectTask = Task.Run(async () =>
{
await using var context = await _dbContextFactory.CreateDbContextAsync();
return await context.Projects.Where(p => projectIds.Contains(p.Id) && p.TenantId == tenantId).Select(p => _mapper.Map<BasicProjectVM>(p)).ToListAsync();
});
var serviceProjectTask = Task.Run(async () =>
{
await using var context = await _dbContextFactory.CreateDbContextAsync();
return await context.ServiceProjects.Where(sp => projectIds.Contains(sp.Id) && sp.TenantId == tenantId).Select(sp => _mapper.Map<BasicProjectVM>(sp)).ToListAsync();
});
await Task.WhenAll(infraProjectTask, serviceProjectTask);
var projects = infraProjectTask.Result;
projects.AddRange(serviceProjectTask.Result);
var results = paymentRequests.Select(pr => var results = paymentRequests.Select(pr =>
{ {
var result = _mapper.Map<PaymentRequestVM>(pr); var result = _mapper.Map<PaymentRequestVM>(pr);
result.PaymentRequestUID = $"{pr.UIDPrefix}/{pr.UIDPostfix:D5}"; result.PaymentRequestUID = $"{pr.UIDPrefix}/{pr.UIDPostfix:D5}";
result.Project = projects.Where(p => p.Id == pr.Id).FirstOrDefault();
return result; return result;
}).ToList(); }).ToList();
@ -1525,6 +1544,12 @@ namespace Marco.Pms.Services.Service
return await permissionService.HasPermission(PermissionsMaster.ExpenseReview, loggedInEmployee.Id); return await permissionService.HasPermission(PermissionsMaster.ExpenseReview, loggedInEmployee.Id);
}); });
await Task.WhenAll(hasViewSelfPermissionTask, hasViewAllPermissionTask, hasReviewPermissionTask);
bool hasViewSelfPermission = hasViewSelfPermissionTask.Result;
bool hasViewAllPermission = hasViewAllPermissionTask.Result;
bool hasReviewPermission = hasReviewPermissionTask.Result;
var hasApprovePermissionTask = Task.Run(async () => var hasApprovePermissionTask = Task.Run(async () =>
{ {
using var scope = _serviceScopeFactory.CreateScope(); using var scope = _serviceScopeFactory.CreateScope();
@ -1546,11 +1571,8 @@ namespace Marco.Pms.Services.Service
return await permissionService.HasPermission(PermissionsMaster.ExpenseManage, loggedInEmployee.Id); return await permissionService.HasPermission(PermissionsMaster.ExpenseManage, loggedInEmployee.Id);
}); });
await Task.WhenAll(hasViewSelfPermissionTask, hasViewAllPermissionTask, hasReviewPermissionTask, hasApprovePermissionTask, hasProcessPermissionTask, hasManagePermissionTask); await Task.WhenAll(hasApprovePermissionTask, hasProcessPermissionTask, hasManagePermissionTask);
bool hasViewSelfPermission = hasViewSelfPermissionTask.Result;
bool hasViewAllPermission = hasViewAllPermissionTask.Result;
bool hasReviewPermission = hasReviewPermissionTask.Result;
bool hasApprovePermission = hasApprovePermissionTask.Result; bool hasApprovePermission = hasApprovePermissionTask.Result;
bool hasProcessPermission = hasProcessPermissionTask.Result; bool hasProcessPermission = hasProcessPermissionTask.Result;
bool hasManagePermission = hasProcessPermissionTask.Result; bool hasManagePermission = hasProcessPermissionTask.Result;
@ -1565,7 +1587,6 @@ namespace Marco.Pms.Services.Service
// Query payment request with all necessary navigation properties and validation constraints // Query payment request with all necessary navigation properties and validation constraints
var paymentRequest = await _context.PaymentRequests var paymentRequest = await _context.PaymentRequests
.Include(pr => pr.Currency) .Include(pr => pr.Currency)
.Include(pr => pr.Project)
.Include(pr => pr.RecurringPayment) .Include(pr => pr.RecurringPayment)
.Include(pr => pr.ExpenseCategory) .Include(pr => pr.ExpenseCategory)
.Include(pr => pr.ExpenseStatus) .Include(pr => pr.ExpenseStatus)
@ -1689,13 +1710,40 @@ namespace Marco.Pms.Services.Service
statusIds = statusIds.Distinct().ToList(); statusIds = statusIds.Distinct().ToList();
var status = await _context.ExpensesStatusMaster.Where(es => statusIds.Contains(es.Id)).ToListAsync(); var infraProjectTask = Task.Run(async () =>
{
await using var context = await _dbContextFactory.CreateDbContextAsync();
return await context.Projects
.AsNoTracking()
.Where(p => p.Id == paymentRequest.ProjectId && p.TenantId == tenantId)
.Select(p => _mapper.Map<BasicProjectVM>(p))
.FirstOrDefaultAsync();
});
var serviceProjectTask = Task.Run(async () =>
{
await using var context = await _dbContextFactory.CreateDbContextAsync();
return await context.ServiceProjects
.AsNoTracking()
.Where(sp => sp.Id == paymentRequest.ProjectId && sp.TenantId == tenantId)
.Select(sp => _mapper.Map<BasicProjectVM>(sp))
.FirstOrDefaultAsync();
});
var statusTask = Task.Run(async () =>
{
await using var context = await _dbContextFactory.CreateDbContextAsync();
return await context.ExpensesStatusMaster.Where(es => statusIds.Contains(es.Id)).ToListAsync();
});
await Task.WhenAll(infraProjectTask, serviceProjectTask, statusTask);
var infraProject = infraProjectTask.Result;
var serviceProject = serviceProjectTask.Result;
var status = statusTask.Result;
// Map main response model and populate additional fields // Map main response model and populate additional fields
var response = _mapper.Map<PaymentRequestDetailsVM>(paymentRequest); var response = _mapper.Map<PaymentRequestDetailsVM>(paymentRequest);
response.PaymentRequestUID = $"{paymentRequest.UIDPrefix}/{paymentRequest.UIDPostfix:D5}"; response.PaymentRequestUID = $"{paymentRequest.UIDPrefix}/{paymentRequest.UIDPostfix:D5}";
//if (paymentRequest.RecurringPayment != null) response.Project = infraProject ?? serviceProject;
// response.RecurringPaymentUID = $"{paymentRequest.RecurringPayment.UIDPrefix}/{paymentRequest.RecurringPayment.UIDPostfix:D5}";
response.Attachments = attachmentVMs; response.Attachments = attachmentVMs;
// Assign nextStatuses only if: // Assign nextStatuses only if:
@ -1759,17 +1807,41 @@ namespace Marco.Pms.Services.Service
{ {
var paymentRequests = await _context.PaymentRequests var paymentRequests = await _context.PaymentRequests
.Include(pr => pr.Currency) .Include(pr => pr.Currency)
.Include(pr => pr.Project)
.Include(pr => pr.ExpenseCategory) .Include(pr => pr.ExpenseCategory)
.Include(pr => pr.ExpenseStatus) .Include(pr => pr.ExpenseStatus)
.Include(pr => pr.CreatedBy) .Include(pr => pr.CreatedBy)
.Where(e => e.TenantId == tenantId) .Where(e => e.TenantId == tenantId)
.ToListAsync(); .ToListAsync();
var projectIds = paymentRequests.Select(pr => pr.ProjectId).ToList();
var infraProjectTask = Task.Run(async () =>
{
await using var context = await _dbContextFactory.CreateDbContextAsync();
return await context.Projects
.Where(p => projectIds.Contains(p.Id) && p.TenantId == tenantId)
.Select(p => new { Id = p.Id, Name = p.Name })
.Distinct().ToListAsync();
});
var serviceProjectTask = Task.Run(async () =>
{
await using var context = await _dbContextFactory.CreateDbContextAsync();
return await context.ServiceProjects
.Where(sp => projectIds.Contains(sp.Id) && sp.TenantId == tenantId)
.Select(sp => new { Id = sp.Id, Name = sp.Name }).Distinct()
.ToListAsync();
});
await Task.WhenAll(infraProjectTask, serviceProjectTask);
var projects = infraProjectTask.Result;
projects.AddRange(serviceProjectTask.Result);
// Construct the final object from the results of the completed tasks. // Construct the final object from the results of the completed tasks.
var response = new var response = new
{ {
Projects = paymentRequests.Where(pr => pr.Project != null).Select(pr => new { Id = pr.Project!.Id, Name = pr.Project.Name }).Distinct().ToList(), Projects = projects,
Currency = paymentRequests.Where(pr => pr.Currency != null).Select(pr => new { Id = pr.Currency!.Id, Name = pr.Currency.CurrencyName }).Distinct().ToList(), Currency = paymentRequests.Where(pr => pr.Currency != null).Select(pr => new { Id = pr.Currency!.Id, Name = pr.Currency.CurrencyName }).Distinct().ToList(),
CreatedBy = paymentRequests.Where(pr => pr.CreatedBy != null).Select(pr => new { Id = pr.CreatedBy!.Id, Name = $"{pr.CreatedBy.FirstName} {pr.CreatedBy.LastName}" }).Distinct().ToList(), CreatedBy = paymentRequests.Where(pr => pr.CreatedBy != null).Select(pr => new { Id = pr.CreatedBy!.Id, Name = $"{pr.CreatedBy.FirstName} {pr.CreatedBy.LastName}" }).Distinct().ToList(),
Status = paymentRequests.Where(pr => pr.ExpenseStatus != null).Select(pr => new { Id = pr.ExpenseStatus!.Id, Name = pr.ExpenseStatus.Name }).Distinct().ToList(), Status = paymentRequests.Where(pr => pr.ExpenseStatus != null).Select(pr => new { Id = pr.ExpenseStatus!.Id, Name = pr.ExpenseStatus.Name }).Distinct().ToList(),
@ -1956,7 +2028,6 @@ namespace Marco.Pms.Services.Service
// 1. Fetch Existing Payment Request with Related Entities (Single Query) // 1. Fetch Existing Payment Request with Related Entities (Single Query)
var paymentRequest = await _context.PaymentRequests var paymentRequest = await _context.PaymentRequests
.Include(pr => pr.Currency) .Include(pr => pr.Currency)
.Include(pr => pr.Project)
.Include(pr => pr.RecurringPayment) .Include(pr => pr.RecurringPayment)
.Include(pr => pr.ExpenseCategory) .Include(pr => pr.ExpenseCategory)
.Include(pr => pr.ExpenseStatus) .Include(pr => pr.ExpenseStatus)
@ -1980,24 +2051,24 @@ namespace Marco.Pms.Services.Service
// 2. Run Prerequisite Checks in Parallel (Status transition + Permissions) // 2. Run Prerequisite Checks in Parallel (Status transition + Permissions)
var statusTransitionTask = Task.Run(async () => var statusTransitionTask = Task.Run(async () =>
{ {
await using var dbContext = await _dbContextFactory.CreateDbContextAsync(); await using var context = await _dbContextFactory.CreateDbContextAsync();
return await dbContext.ExpensesStatusMapping return await context.ExpensesStatusMapping
.Include(m => m.NextStatus) .Include(m => m.NextStatus)
.FirstOrDefaultAsync(m => m.StatusId == paymentRequest.ExpenseStatusId && m.NextStatusId == model.StatusId); .FirstOrDefaultAsync(m => m.StatusId == paymentRequest.ExpenseStatusId && m.NextStatusId == model.StatusId);
}); });
var targetStatusPermissionsTask = Task.Run(async () => var targetStatusPermissionsTask = Task.Run(async () =>
{ {
await using var dbContext = await _dbContextFactory.CreateDbContextAsync(); await using var context = await _dbContextFactory.CreateDbContextAsync();
return await dbContext.StatusPermissionMapping return await context.StatusPermissionMapping
.Where(spm => spm.StatusId == model.StatusId) .Where(spm => spm.StatusId == model.StatusId)
.ToListAsync(); .ToListAsync();
}); });
var infraProjectTask = Task.Run(async () => var infraProjectTask = Task.Run(async () =>
{ {
await using var dbContext = await _dbContextFactory.CreateDbContextAsync(); await using var context = await _dbContextFactory.CreateDbContextAsync();
return await dbContext.Projects return await context.Projects
.AsNoTracking() .AsNoTracking()
.Where(p => p.Id == paymentRequest.ProjectId && p.TenantId == tenantId) .Where(p => p.Id == paymentRequest.ProjectId && p.TenantId == tenantId)
.Select(p => _mapper.Map<BasicProjectVM>(p)) .Select(p => _mapper.Map<BasicProjectVM>(p))
@ -2005,8 +2076,8 @@ namespace Marco.Pms.Services.Service
}); });
var serviceProjectTask = Task.Run(async () => var serviceProjectTask = Task.Run(async () =>
{ {
await using var dbContext = await _dbContextFactory.CreateDbContextAsync(); await using var context = await _dbContextFactory.CreateDbContextAsync();
return await dbContext.ServiceProjects return await context.ServiceProjects
.AsNoTracking() .AsNoTracking()
.Where(sp => sp.Id == paymentRequest.ProjectId && sp.TenantId == tenantId) .Where(sp => sp.Id == paymentRequest.ProjectId && sp.TenantId == tenantId)
.Select(sp => _mapper.Map<BasicProjectVM>(sp)) .Select(sp => _mapper.Map<BasicProjectVM>(sp))
@ -2446,11 +2517,6 @@ namespace Marco.Pms.Services.Service
await using var context = await _dbContextFactory.CreateDbContextAsync(); await using var context = await _dbContextFactory.CreateDbContextAsync();
return await context.CurrencyMaster.FirstOrDefaultAsync(c => c.Id == model.CurrencyId); return await context.CurrencyMaster.FirstOrDefaultAsync(c => c.Id == model.CurrencyId);
}); });
var projectTask = Task.Run(async () =>
{
await using var context = await _dbContextFactory.CreateDbContextAsync();
return model.ProjectId.HasValue ? await context.Projects.FirstOrDefaultAsync(p => p.Id == model.ProjectId.Value) : null;
});
var hasManagePermissionTask = Task.Run(async () => var hasManagePermissionTask = Task.Run(async () =>
{ {
using var scope = _serviceScopeFactory.CreateScope(); using var scope = _serviceScopeFactory.CreateScope();
@ -2458,7 +2524,7 @@ namespace Marco.Pms.Services.Service
return await permissionService.HasPermission(PermissionsMaster.ExpenseManage, loggedInEmployee.Id); return await permissionService.HasPermission(PermissionsMaster.ExpenseManage, loggedInEmployee.Id);
}); });
await Task.WhenAll(expenseCategoryTask, currencyTask, projectTask, hasManagePermissionTask); await Task.WhenAll(expenseCategoryTask, currencyTask, hasManagePermissionTask);
var expenseCategory = await expenseCategoryTask; var expenseCategory = await expenseCategoryTask;
if (expenseCategory == null) if (expenseCategory == null)
@ -2474,12 +2540,46 @@ namespace Marco.Pms.Services.Service
return ApiResponse<object>.ErrorResponse("Currency not found.", "Currency not found.", 404); return ApiResponse<object>.ErrorResponse("Currency not found.", "Currency not found.", 404);
} }
var project = await projectTask; // Project can be null (optional) BasicProjectVM? project = null;
if (model.ProjectId.HasValue)
{
var infraProjectTask = Task.Run(async () =>
{
await using var context = await _dbContextFactory.CreateDbContextAsync();
return await context.Projects
.AsNoTracking()
.Where(p => p.Id == model.ProjectId && p.TenantId == tenantId)
.Select(p => _mapper.Map<BasicProjectVM>(p))
.FirstOrDefaultAsync();
});
var serviceProjectTask = Task.Run(async () =>
{
await using var context = await _dbContextFactory.CreateDbContextAsync();
return await context.ServiceProjects
.AsNoTracking()
.Where(sp => sp.Id == model.ProjectId && sp.TenantId == tenantId)
.Select(sp => _mapper.Map<BasicProjectVM>(sp))
.FirstOrDefaultAsync();
});
await Task.WhenAll(infraProjectTask, serviceProjectTask);
var infraProject = infraProjectTask.Result;
var serviceProject = serviceProjectTask.Result;
if (infraProject == null && serviceProject == null)
{
_logger.LogWarning("Cannot proceed: Both infrastructure and service projects are not found.");
return ApiResponse<object>.ErrorResponse("Cannot proceed: Both infrastructure and service projects are not found.",
"Cannot proceed: Both infrastructure and service projects are not found.", 404);
}
project = infraProject ?? serviceProject;
}
// Retrieve the existing payment request with relevant navigation properties for validation and mapping // Retrieve the existing payment request with relevant navigation properties for validation and mapping
var paymentRequest = await _context.PaymentRequests var paymentRequest = await _context.PaymentRequests
.Include(pr => pr.ExpenseCategory) .Include(pr => pr.ExpenseCategory)
.Include(pr => pr.Project)
.Include(pr => pr.ExpenseStatus) .Include(pr => pr.ExpenseStatus)
.Include(pr => pr.RecurringPayment) .Include(pr => pr.RecurringPayment)
.Include(pr => pr.Currency) .Include(pr => pr.Currency)
@ -2521,7 +2621,10 @@ namespace Marco.Pms.Services.Service
paymentRequest.Amount = model.Amount; paymentRequest.Amount = model.Amount;
} }
paymentRequest.IsAdvancePayment = model.IsAdvancePayment; if (paymentRequest.ExpenseStatusId != ProcessPending && paymentRequest.ExpenseStatusId != Processed && paymentRequest.ExpenseStatusId != Done)
{
paymentRequest.IsAdvancePayment = model.IsAdvancePayment;
}
var paymentRequestUID = $"{paymentRequest.UIDPrefix}/{paymentRequest.UIDPostfix:D5}"; var paymentRequestUID = $"{paymentRequest.UIDPrefix}/{paymentRequest.UIDPostfix:D5}";
@ -2616,7 +2719,7 @@ namespace Marco.Pms.Services.Service
response.PaymentRequestUID = paymentRequestUID; response.PaymentRequestUID = paymentRequestUID;
response.Currency = currency; response.Currency = currency;
response.ExpenseCategory = _mapper.Map<ExpenseCategoryMasterVM>(expenseCategory); response.ExpenseCategory = _mapper.Map<ExpenseCategoryMasterVM>(expenseCategory);
response.Project = _mapper.Map<BasicProjectVM>(project); response.Project = project;
return ApiResponse<object>.SuccessResponse(response, "Payment Request updated successfully.", 200); return ApiResponse<object>.SuccessResponse(response, "Payment Request updated successfully.", 200);
} }
@ -2639,8 +2742,8 @@ namespace Marco.Pms.Services.Service
{ {
var paymentRequestTask = Task.Run(async () => var paymentRequestTask = Task.Run(async () =>
{ {
await using var dbContext = await _dbContextFactory.CreateDbContextAsync(); await using var context = await _dbContextFactory.CreateDbContextAsync();
return await dbContext.PaymentRequests.AsNoTracking().Where(e => e.Id == id && e.ExpenseStatusId == Draft && e.TenantId == tenantId).FirstOrDefaultAsync(); return await context.PaymentRequests.AsNoTracking().Where(e => e.Id == id && e.ExpenseStatusId == Draft && e.TenantId == tenantId).FirstOrDefaultAsync();
}); });
var hasAprrovePermissionTask = Task.Run(async () => var hasAprrovePermissionTask = Task.Run(async () =>
@ -3559,7 +3662,6 @@ namespace Marco.Pms.Services.Service
} }
} }
#endregion #endregion
#region =================================================================== Helper Functions =================================================================== #region =================================================================== Helper Functions ===================================================================
@ -3582,8 +3684,8 @@ namespace Marco.Pms.Services.Service
{ {
var statusMappingTask = Task.Run(async () => var statusMappingTask = Task.Run(async () =>
{ {
await using var dbContext = await _dbContextFactory.CreateDbContextAsync(); await using var context = await _dbContextFactory.CreateDbContextAsync();
return await dbContext.ExpensesStatusMapping return await context.ExpensesStatusMapping
.Include(s => s.Status) .Include(s => s.Status)
.Include(s => s.NextStatus) .Include(s => s.NextStatus)
.AsNoTracking() .AsNoTracking()
@ -3598,15 +3700,15 @@ namespace Marco.Pms.Services.Service
}); });
var statusTask = Task.Run(async () => var statusTask = Task.Run(async () =>
{ {
await using var dbContext = await _dbContextFactory.CreateDbContextAsync(); await using var context = await _dbContextFactory.CreateDbContextAsync();
return await dbContext.ExpensesStatusMaster return await context.ExpensesStatusMaster
.AsNoTracking() .AsNoTracking()
.FirstOrDefaultAsync(es => es.Id == model.StatusId); .FirstOrDefaultAsync(es => es.Id == model.StatusId);
}); });
var billAttachmentsTask = Task.Run(async () => var billAttachmentsTask = Task.Run(async () =>
{ {
await using var dbContext = await _dbContextFactory.CreateDbContextAsync(); await using var context = await _dbContextFactory.CreateDbContextAsync();
return await dbContext.BillAttachments return await context.BillAttachments
.Include(ba => ba.Document) .Include(ba => ba.Document)
.AsNoTracking() .AsNoTracking()
.Where(ba => ba.ExpensesId == model.Id && ba.Document != null) .Where(ba => ba.ExpensesId == model.Id && ba.Document != null)
@ -3929,21 +4031,21 @@ namespace Marco.Pms.Services.Service
{ {
var attachmentTask = Task.Run(async () => var attachmentTask = Task.Run(async () =>
{ {
await using var dbContext = await _dbContextFactory.CreateDbContextAsync(); await using var context = await _dbContextFactory.CreateDbContextAsync();
var attachments = await dbContext.BillAttachments.AsNoTracking().Where(ba => documentIds.Contains(ba.DocumentId)).ToListAsync(); var attachments = await context.BillAttachments.AsNoTracking().Where(ba => documentIds.Contains(ba.DocumentId)).ToListAsync();
dbContext.BillAttachments.RemoveRange(attachments); context.BillAttachments.RemoveRange(attachments);
await dbContext.SaveChangesAsync(); await context.SaveChangesAsync();
}); });
var documentsTask = Task.Run(async () => var documentsTask = Task.Run(async () =>
{ {
await using var dbContext = await _dbContextFactory.CreateDbContextAsync(); await using var context = await _dbContextFactory.CreateDbContextAsync();
var documents = await dbContext.Documents.AsNoTracking().Where(ba => documentIds.Contains(ba.Id)).ToListAsync(); var documents = await context.Documents.AsNoTracking().Where(ba => documentIds.Contains(ba.Id)).ToListAsync();
if (documents.Any()) if (documents.Any())
{ {
dbContext.Documents.RemoveRange(documents); context.Documents.RemoveRange(documents);
await dbContext.SaveChangesAsync(); await context.SaveChangesAsync();
List<S3DeletionObject> deletionObject = new List<S3DeletionObject>(); List<S3DeletionObject> deletionObject = new List<S3DeletionObject>();
foreach (var document in documents) foreach (var document in documents)
@ -3970,21 +4072,21 @@ namespace Marco.Pms.Services.Service
{ {
var attachmentTask = Task.Run(async () => var attachmentTask = Task.Run(async () =>
{ {
await using var dbContext = await _dbContextFactory.CreateDbContextAsync(); await using var context = await _dbContextFactory.CreateDbContextAsync();
var attachments = await dbContext.PaymentRequestAttachments.AsNoTracking().Where(ba => documentIds.Contains(ba.DocumentId)).ToListAsync(); var attachments = await context.PaymentRequestAttachments.AsNoTracking().Where(ba => documentIds.Contains(ba.DocumentId)).ToListAsync();
dbContext.PaymentRequestAttachments.RemoveRange(attachments); context.PaymentRequestAttachments.RemoveRange(attachments);
await dbContext.SaveChangesAsync(); await context.SaveChangesAsync();
}); });
var documentsTask = Task.Run(async () => var documentsTask = Task.Run(async () =>
{ {
await using var dbContext = await _dbContextFactory.CreateDbContextAsync(); await using var context = await _dbContextFactory.CreateDbContextAsync();
var documents = await dbContext.Documents.AsNoTracking().Where(ba => documentIds.Contains(ba.Id)).ToListAsync(); var documents = await context.Documents.AsNoTracking().Where(ba => documentIds.Contains(ba.Id)).ToListAsync();
if (documents.Any()) if (documents.Any())
{ {
dbContext.Documents.RemoveRange(documents); context.Documents.RemoveRange(documents);
await dbContext.SaveChangesAsync(); await context.SaveChangesAsync();
List<S3DeletionObject> deletionObject = new List<S3DeletionObject>(); List<S3DeletionObject> deletionObject = new List<S3DeletionObject>();
foreach (var document in documents) foreach (var document in documents)
@ -4139,7 +4241,6 @@ namespace Marco.Pms.Services.Service
return nextStrikeDate; return nextStrikeDate;
} }
#endregion #endregion
} }
} }