Removed the project forign key and able to create expense for both infra and service project
This commit is contained in:
parent
df0e9f7b46
commit
9c95b12a8f
8899
Marco.Pms.DataAccess/Migrations/20251120111120_Removed_Project_ForignKey_From_Expenses_Table.Designer.cs
generated
Normal file
8899
Marco.Pms.DataAccess/Migrations/20251120111120_Removed_Project_ForignKey_From_Expenses_Table.Designer.cs
generated
Normal file
File diff suppressed because one or more lines are too long
@ -0,0 +1,39 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Marco.Pms.DataAccess.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class Removed_Project_ForignKey_From_Expenses_Table : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropForeignKey(
|
||||||
|
name: "FK_Expenses_Projects_ProjectId",
|
||||||
|
table: "Expenses");
|
||||||
|
|
||||||
|
migrationBuilder.DropIndex(
|
||||||
|
name: "IX_Expenses_ProjectId",
|
||||||
|
table: "Expenses");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Expenses_ProjectId",
|
||||||
|
table: "Expenses",
|
||||||
|
column: "ProjectId");
|
||||||
|
|
||||||
|
migrationBuilder.AddForeignKey(
|
||||||
|
name: "FK_Expenses_Projects_ProjectId",
|
||||||
|
table: "Expenses",
|
||||||
|
column: "ProjectId",
|
||||||
|
principalTable: "Projects",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -2329,8 +2329,6 @@ namespace Marco.Pms.DataAccess.Migrations
|
|||||||
|
|
||||||
b.HasIndex("ProcessedById");
|
b.HasIndex("ProcessedById");
|
||||||
|
|
||||||
b.HasIndex("ProjectId");
|
|
||||||
|
|
||||||
b.HasIndex("ReviewedById");
|
b.HasIndex("ReviewedById");
|
||||||
|
|
||||||
b.HasIndex("StatusId");
|
b.HasIndex("StatusId");
|
||||||
@ -7378,12 +7376,6 @@ namespace Marco.Pms.DataAccess.Migrations
|
|||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("ProcessedById");
|
.HasForeignKey("ProcessedById");
|
||||||
|
|
||||||
b.HasOne("Marco.Pms.Model.Projects.Project", "Project")
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey("ProjectId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
|
|
||||||
b.HasOne("Marco.Pms.Model.Employees.Employee", "ReviewedBy")
|
b.HasOne("Marco.Pms.Model.Employees.Employee", "ReviewedBy")
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("ReviewedById");
|
.HasForeignKey("ReviewedById");
|
||||||
@ -7416,8 +7408,6 @@ namespace Marco.Pms.DataAccess.Migrations
|
|||||||
|
|
||||||
b.Navigation("ProcessedBy");
|
b.Navigation("ProcessedBy");
|
||||||
|
|
||||||
b.Navigation("Project");
|
|
||||||
|
|
||||||
b.Navigation("ReviewedBy");
|
b.Navigation("ReviewedBy");
|
||||||
|
|
||||||
b.Navigation("Status");
|
b.Navigation("Status");
|
||||||
|
|||||||
12
Marco.Pms.Model/Dtos/ServiceProject/TalkingPointDto.cs
Normal file
12
Marco.Pms.Model/Dtos/ServiceProject/TalkingPointDto.cs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
using Marco.Pms.Model.Utilities;
|
||||||
|
|
||||||
|
namespace Marco.Pms.Model.Dtos.ServiceProject
|
||||||
|
{
|
||||||
|
public class TalkingPointDto
|
||||||
|
{
|
||||||
|
public Guid? Id { get; set; }
|
||||||
|
public required Guid ServiceProjectId { get; set; }
|
||||||
|
public required string Comment { get; set; }
|
||||||
|
public List<FileUploadModel>? Attachments { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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;
|
||||||
@ -14,10 +13,6 @@ namespace Marco.Pms.Model.Expenses
|
|||||||
public string UIDPrefix { get; set; } = default!;
|
public string UIDPrefix { get; set; } = default!;
|
||||||
public int UIDPostfix { get; set; }
|
public int UIDPostfix { get; set; }
|
||||||
public Guid ProjectId { get; set; }
|
public Guid ProjectId { get; set; }
|
||||||
|
|
||||||
[ValidateNever]
|
|
||||||
[ForeignKey("ProjectId")]
|
|
||||||
public Project? Project { get; set; }
|
|
||||||
public Guid ExpensesTypeId { get; set; }
|
public Guid ExpensesTypeId { get; set; }
|
||||||
|
|
||||||
//[ValidateNever]
|
//[ValidateNever]
|
||||||
|
|||||||
31
Marco.Pms.Model/ServiceProject/TalkingPoint.cs
Normal file
31
Marco.Pms.Model/ServiceProject/TalkingPoint.cs
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
using Marco.Pms.Model.Employees;
|
||||||
|
using Marco.Pms.Model.Utilities;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
|
||||||
|
namespace Marco.Pms.Model.ServiceProject
|
||||||
|
{
|
||||||
|
public class TalkingPoint : TenantRelation
|
||||||
|
{
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
public Guid ServiceProjectId { get; set; }
|
||||||
|
|
||||||
|
[ValidateNever]
|
||||||
|
[ForeignKey("ServiceProjectId")]
|
||||||
|
public ServiceProject? ServiceProject { get; set; }
|
||||||
|
public string Comment { get; set; } = string.Empty;
|
||||||
|
public bool IsActive { get; set; } = true;
|
||||||
|
public DateTime CreatedAt { get; set; }
|
||||||
|
public Guid CreatedById { get; set; }
|
||||||
|
|
||||||
|
[ValidateNever]
|
||||||
|
[ForeignKey("CreatedById")]
|
||||||
|
public Employee? CreatedBy { get; set; }
|
||||||
|
public DateTime? UpdatedAt { get; set; }
|
||||||
|
public Guid? UpdatedById { get; set; }
|
||||||
|
|
||||||
|
[ValidateNever]
|
||||||
|
[ForeignKey("UpdatedById")]
|
||||||
|
public Employee? UpdatedBy { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
22
Marco.Pms.Model/ServiceProject/TalkingPointAttachment.cs
Normal file
22
Marco.Pms.Model/ServiceProject/TalkingPointAttachment.cs
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
using Marco.Pms.Model.DocumentManager;
|
||||||
|
using Marco.Pms.Model.Utilities;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
|
||||||
|
namespace Marco.Pms.Model.ServiceProject
|
||||||
|
{
|
||||||
|
public class TalkingPointAttachment : TenantRelation
|
||||||
|
{
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
public Guid DocumentId { get; set; }
|
||||||
|
|
||||||
|
[ValidateNever]
|
||||||
|
[ForeignKey("DocumentId")]
|
||||||
|
public Document? Document { get; set; }
|
||||||
|
public Guid? TalkingPointId { get; set; }
|
||||||
|
|
||||||
|
[ValidateNever]
|
||||||
|
[ForeignKey("TalkingPointId")]
|
||||||
|
public TalkingPoint? TalkingPoint { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -10,7 +10,7 @@ namespace Marco.Pms.Model.ViewModels.Expenses
|
|||||||
public class ExpenseDetailsVM
|
public class ExpenseDetailsVM
|
||||||
{
|
{
|
||||||
public Guid Id { get; set; }
|
public Guid Id { get; set; }
|
||||||
public ProjectInfoVM? Project { get; set; }
|
public BasicProjectVM? Project { get; set; }
|
||||||
public ExpenseCategoryMasterVM? ExpenseCategory { get; set; }
|
public ExpenseCategoryMasterVM? ExpenseCategory { get; set; }
|
||||||
public PaymentModeMatserVM? PaymentMode { get; set; }
|
public PaymentModeMatserVM? PaymentMode { get; set; }
|
||||||
public BasicEmployeeVM? PaidBy { get; set; }
|
public BasicEmployeeVM? PaidBy { get; set; }
|
||||||
|
|||||||
@ -9,7 +9,7 @@ namespace Marco.Pms.Model.ViewModels.Expanses
|
|||||||
public class ExpenseList
|
public class ExpenseList
|
||||||
{
|
{
|
||||||
public Guid Id { get; set; }
|
public Guid Id { get; set; }
|
||||||
public ProjectInfoVM? Project { get; set; }
|
public BasicProjectVM? Project { get; set; }
|
||||||
public ExpenseCategoryMasterVM? ExpenseCategory { get; set; }
|
public ExpenseCategoryMasterVM? ExpenseCategory { get; set; }
|
||||||
public PaymentModeMatserVM? PaymentMode { get; set; }
|
public PaymentModeMatserVM? PaymentMode { get; set; }
|
||||||
public BasicEmployeeVM? PaidBy { get; set; }
|
public BasicEmployeeVM? PaidBy { get; set; }
|
||||||
|
|||||||
18
Marco.Pms.Model/ViewModels/ServiceProject/TalkingPointVM.cs
Normal file
18
Marco.Pms.Model/ViewModels/ServiceProject/TalkingPointVM.cs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
using Marco.Pms.Model.ViewModels.Activities;
|
||||||
|
using Marco.Pms.Model.ViewModels.DocumentManager;
|
||||||
|
|
||||||
|
namespace Marco.Pms.Model.ViewModels.ServiceProject
|
||||||
|
{
|
||||||
|
public class TalkingPointVM
|
||||||
|
{
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
public BasicServiceProjectVM? ServiceProject { get; set; }
|
||||||
|
public string? Comment { get; set; }
|
||||||
|
public bool IsActive { get; set; }
|
||||||
|
public DateTime CreatedAt { get; set; }
|
||||||
|
public BasicEmployeeVM? CreatedBy { get; set; }
|
||||||
|
public DateTime? UpdatedAt { get; set; }
|
||||||
|
public BasicEmployeeVM? UpdatedBy { get; set; }
|
||||||
|
public List<DocumentVM>? Attachments { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1162,35 +1162,23 @@ namespace Marco.Pms.Services.Helpers
|
|||||||
var processedByIds = model.Select(m => m.ProcessedById).ToList();
|
var processedByIds = model.Select(m => m.ProcessedById).ToList();
|
||||||
var paidByIds = model.Select(m => m.PaidById).ToList();
|
var paidByIds = model.Select(m => m.PaidById).ToList();
|
||||||
|
|
||||||
var projectTask = Task.Run(async () =>
|
var infraProjectTask = Task.Run(async () =>
|
||||||
{
|
{
|
||||||
await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
|
await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
|
||||||
return await dbContext.Projects.AsNoTracking().Where(p => projectIds.Contains(p.Id) && p.TenantId == tenantId).ToListAsync();
|
return await dbContext.Projects
|
||||||
|
.AsNoTracking()
|
||||||
|
.Where(p => projectIds.Contains(p.Id) && p.TenantId == tenantId)
|
||||||
|
.Select(p => _mapper.Map<ProjectBasicMongoDB>(p))
|
||||||
|
.ToListAsync();
|
||||||
});
|
});
|
||||||
var paidByTask = Task.Run(async () =>
|
var serviceProjectTask = Task.Run(async () =>
|
||||||
{
|
{
|
||||||
await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
|
await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
|
||||||
return await dbContext.Employees.Include(e => e.JobRole).AsNoTracking().Where(e => paidByIds.Contains(e.Id) && e.TenantId == tenantId).ToListAsync();
|
return await dbContext.ServiceProjects
|
||||||
});
|
.AsNoTracking()
|
||||||
var createdByTask = Task.Run(async () =>
|
.Where(sp => projectIds.Contains(sp.Id) && sp.TenantId == tenantId)
|
||||||
{
|
.Select(sp => _mapper.Map<ProjectBasicMongoDB>(sp))
|
||||||
await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
|
.ToListAsync();
|
||||||
return await dbContext.Employees.Include(e => e.JobRole).AsNoTracking().Where(e => createdByIds.Contains(e.Id) && e.TenantId == tenantId).ToListAsync();
|
|
||||||
});
|
|
||||||
var reviewedByTask = Task.Run(async () =>
|
|
||||||
{
|
|
||||||
await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
|
|
||||||
return await dbContext.Employees.Include(e => e.JobRole).AsNoTracking().Where(e => reviewedByIds.Contains(e.Id) && e.TenantId == tenantId).ToListAsync();
|
|
||||||
});
|
|
||||||
var approvedByTask = Task.Run(async () =>
|
|
||||||
{
|
|
||||||
await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
|
|
||||||
return await dbContext.Employees.Include(e => e.JobRole).AsNoTracking().Where(e => approvedByIds.Contains(e.Id) && e.TenantId == tenantId).ToListAsync();
|
|
||||||
});
|
|
||||||
var processedByTask = Task.Run(async () =>
|
|
||||||
{
|
|
||||||
await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
|
|
||||||
return await dbContext.Employees.Include(e => e.JobRole).AsNoTracking().Where(e => processedByIds.Contains(e.Id) && e.TenantId == tenantId).ToListAsync();
|
|
||||||
});
|
});
|
||||||
var expenseCategoryTask = Task.Run(async () =>
|
var expenseCategoryTask = Task.Run(async () =>
|
||||||
{
|
{
|
||||||
@ -1202,6 +1190,15 @@ namespace Marco.Pms.Services.Helpers
|
|||||||
await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
|
await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
|
||||||
return await dbContext.PaymentModeMatser.AsNoTracking().Where(pm => paymentModeIds.Contains(pm.Id)).ToListAsync();
|
return await dbContext.PaymentModeMatser.AsNoTracking().Where(pm => paymentModeIds.Contains(pm.Id)).ToListAsync();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await Task.WhenAll(infraProjectTask, serviceProjectTask, expenseCategoryTask, paymentModeTask);
|
||||||
|
|
||||||
|
var projects = infraProjectTask.Result;
|
||||||
|
projects.AddRange(serviceProjectTask.Result);
|
||||||
|
|
||||||
|
var expenseCategories = expenseCategoryTask.Result;
|
||||||
|
var paymentModes = paymentModeTask.Result;
|
||||||
|
|
||||||
var statusMappingTask = Task.Run(async () =>
|
var statusMappingTask = Task.Run(async () =>
|
||||||
{
|
{
|
||||||
await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
|
await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
|
||||||
@ -1218,6 +1215,43 @@ namespace Marco.Pms.Services.Helpers
|
|||||||
NextStatus = g.Select(s => s.NextStatus).OrderBy(s => s!.Name).ToList()
|
NextStatus = g.Select(s => s.NextStatus).OrderBy(s => s!.Name).ToList()
|
||||||
}).ToListAsync();
|
}).ToListAsync();
|
||||||
});
|
});
|
||||||
|
var paidByTask = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
|
||||||
|
return await dbContext.Employees.Include(e => e.JobRole).AsNoTracking().Where(e => paidByIds.Contains(e.Id) && e.TenantId == tenantId).ToListAsync();
|
||||||
|
});
|
||||||
|
var createdByTask = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
|
||||||
|
return await dbContext.Employees.Include(e => e.JobRole).AsNoTracking().Where(e => createdByIds.Contains(e.Id) && e.TenantId == tenantId).ToListAsync();
|
||||||
|
});
|
||||||
|
|
||||||
|
await Task.WhenAll(statusMappingTask, paidByTask, createdByTask);
|
||||||
|
var statusMappings = statusMappingTask.Result;
|
||||||
|
var paidBys = paidByTask.Result;
|
||||||
|
var createdBys = createdByTask.Result;
|
||||||
|
|
||||||
|
var reviewedByTask = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
|
||||||
|
return await dbContext.Employees.Include(e => e.JobRole).AsNoTracking().Where(e => reviewedByIds.Contains(e.Id) && e.TenantId == tenantId).ToListAsync();
|
||||||
|
});
|
||||||
|
var approvedByTask = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
|
||||||
|
return await dbContext.Employees.Include(e => e.JobRole).AsNoTracking().Where(e => approvedByIds.Contains(e.Id) && e.TenantId == tenantId).ToListAsync();
|
||||||
|
});
|
||||||
|
var processedByTask = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
|
||||||
|
return await dbContext.Employees.Include(e => e.JobRole).AsNoTracking().Where(e => processedByIds.Contains(e.Id) && e.TenantId == tenantId).ToListAsync();
|
||||||
|
});
|
||||||
|
|
||||||
|
await Task.WhenAll(reviewedByTask, approvedByTask, processedByTask);
|
||||||
|
var reviewedBys = reviewedByTask.Result;
|
||||||
|
var approvedBys = approvedByTask.Result;
|
||||||
|
var processedBy = processedByTask.Result;
|
||||||
|
|
||||||
var statusTask = Task.Run(async () =>
|
var statusTask = Task.Run(async () =>
|
||||||
{
|
{
|
||||||
await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
|
await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
|
||||||
@ -1249,26 +1283,17 @@ namespace Marco.Pms.Services.Helpers
|
|||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Await all prerequisite checks at once.
|
|
||||||
await Task.WhenAll(projectTask, expenseCategoryTask, paymentModeTask, statusMappingTask, paidByTask, createdByTask, reviewedByTask, approvedByTask,
|
|
||||||
processedByTask, statusTask, billAttachmentsTask);
|
|
||||||
|
|
||||||
var projects = projectTask.Result;
|
|
||||||
var expenseCategories = expenseCategoryTask.Result;
|
// Await all prerequisite checks at once.
|
||||||
var paymentModes = paymentModeTask.Result;
|
await Task.WhenAll(statusTask, billAttachmentsTask);
|
||||||
var statusMappings = statusMappingTask.Result;
|
|
||||||
var paidBys = paidByTask.Result;
|
|
||||||
var createdBys = createdByTask.Result;
|
|
||||||
var reviewedBys = reviewedByTask.Result;
|
|
||||||
var approvedBys = approvedByTask.Result;
|
|
||||||
var processedBy = processedByTask.Result;
|
|
||||||
var billAttachments = billAttachmentsTask.Result;
|
var billAttachments = billAttachmentsTask.Result;
|
||||||
|
|
||||||
expenseList = model.Select(m =>
|
expenseList = model.Select(m =>
|
||||||
{
|
{
|
||||||
var response = _mapper.Map<ExpenseDetailsMongoDB>(m);
|
var response = _mapper.Map<ExpenseDetailsMongoDB>(m);
|
||||||
|
|
||||||
response.Project = projects.Where(p => p.Id == m.ProjectId).Select(p => _mapper.Map<ProjectBasicMongoDB>(p)).FirstOrDefault() ?? new ProjectBasicMongoDB();
|
response.Project = projects.Where(p => Guid.Parse(p.Id) == m.ProjectId).FirstOrDefault() ?? new ProjectBasicMongoDB();
|
||||||
response.PaidBy = paidBys.Where(p => p.Id == m.PaidById).Select(p => _mapper.Map<BasicEmployeeMongoDB>(p)).FirstOrDefault() ?? new BasicEmployeeMongoDB();
|
response.PaidBy = paidBys.Where(p => p.Id == m.PaidById).Select(p => _mapper.Map<BasicEmployeeMongoDB>(p)).FirstOrDefault() ?? new BasicEmployeeMongoDB();
|
||||||
response.CreatedBy = createdBys.Where(e => e.Id == m.CreatedById).Select(e => _mapper.Map<BasicEmployeeMongoDB>(e)).FirstOrDefault() ?? new BasicEmployeeMongoDB();
|
response.CreatedBy = createdBys.Where(e => e.Id == m.CreatedById).Select(e => _mapper.Map<BasicEmployeeMongoDB>(e)).FirstOrDefault() ?? new BasicEmployeeMongoDB();
|
||||||
response.ReviewedBy = reviewedBys.Where(e => e.Id == m.CreatedById).Select(e => _mapper.Map<BasicEmployeeMongoDB>(e)).FirstOrDefault();
|
response.ReviewedBy = reviewedBys.Where(e => e.Id == m.CreatedById).Select(e => _mapper.Map<BasicEmployeeMongoDB>(e)).FirstOrDefault();
|
||||||
@ -1292,35 +1317,23 @@ namespace Marco.Pms.Services.Helpers
|
|||||||
}
|
}
|
||||||
private async Task<ExpenseDetailsMongoDB> GetAllExpnesRelatedTablesForSingle(Expenses model, Guid tenantId)
|
private async Task<ExpenseDetailsMongoDB> GetAllExpnesRelatedTablesForSingle(Expenses model, Guid tenantId)
|
||||||
{
|
{
|
||||||
var projectTask = Task.Run(async () =>
|
var infraProjectTask = Task.Run(async () =>
|
||||||
{
|
{
|
||||||
await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
|
await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
|
||||||
return await dbContext.Projects.AsNoTracking().FirstOrDefaultAsync(p => p.Id == model.ProjectId && p.TenantId == tenantId);
|
return await dbContext.Projects
|
||||||
|
.AsNoTracking()
|
||||||
|
.Where(p => p.Id == model.ProjectId && p.TenantId == tenantId)
|
||||||
|
.Select(p => _mapper.Map<ProjectBasicMongoDB>(p))
|
||||||
|
.FirstOrDefaultAsync();
|
||||||
});
|
});
|
||||||
var paidByTask = Task.Run(async () =>
|
var serviceProjectTask = Task.Run(async () =>
|
||||||
{
|
{
|
||||||
await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
|
await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
|
||||||
return await dbContext.Employees.Include(e => e.JobRole).AsNoTracking().FirstOrDefaultAsync(e => e.Id == model.PaidById && e.TenantId == tenantId);
|
return await dbContext.ServiceProjects
|
||||||
});
|
.AsNoTracking()
|
||||||
var createdByTask = Task.Run(async () =>
|
.Where(sp => sp.Id == model.ProjectId && sp.TenantId == tenantId)
|
||||||
{
|
.Select(sp => _mapper.Map<ProjectBasicMongoDB>(sp))
|
||||||
await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
|
.FirstOrDefaultAsync();
|
||||||
return await dbContext.Employees.Include(e => e.JobRole).AsNoTracking().FirstOrDefaultAsync(e => e.Id == model.CreatedById && e.TenantId == tenantId);
|
|
||||||
});
|
|
||||||
var reviewedByTask = Task.Run(async () =>
|
|
||||||
{
|
|
||||||
await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
|
|
||||||
return await dbContext.Employees.Include(e => e.JobRole).AsNoTracking().FirstOrDefaultAsync(e => e.Id == model.ReviewedById && e.TenantId == tenantId);
|
|
||||||
});
|
|
||||||
var approvedByTask = Task.Run(async () =>
|
|
||||||
{
|
|
||||||
await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
|
|
||||||
return await dbContext.Employees.Include(e => e.JobRole).AsNoTracking().FirstOrDefaultAsync(e => e.Id == model.ApprovedById && e.TenantId == tenantId);
|
|
||||||
});
|
|
||||||
var processedByTask = Task.Run(async () =>
|
|
||||||
{
|
|
||||||
await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
|
|
||||||
return await dbContext.Employees.Include(e => e.JobRole).AsNoTracking().FirstOrDefaultAsync(e => e.Id == model.ProcessedById && e.TenantId == tenantId);
|
|
||||||
});
|
});
|
||||||
var expenseCategoryTask = Task.Run(async () =>
|
var expenseCategoryTask = Task.Run(async () =>
|
||||||
{
|
{
|
||||||
@ -1332,6 +1345,12 @@ namespace Marco.Pms.Services.Helpers
|
|||||||
await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
|
await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
|
||||||
return await dbContext.PaymentModeMatser.AsNoTracking().FirstOrDefaultAsync(pm => pm.Id == model.PaymentModeId);
|
return await dbContext.PaymentModeMatser.AsNoTracking().FirstOrDefaultAsync(pm => pm.Id == model.PaymentModeId);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await Task.WhenAll(infraProjectTask, serviceProjectTask, expenseCategoryTask, paymentModeTask);
|
||||||
|
var project = infraProjectTask.Result ?? serviceProjectTask.Result ?? new ProjectBasicMongoDB();
|
||||||
|
var expenseCategory = expenseCategoryTask.Result;
|
||||||
|
var paymentMode = paymentModeTask.Result;
|
||||||
|
|
||||||
var statusMappingTask = Task.Run(async () =>
|
var statusMappingTask = Task.Run(async () =>
|
||||||
{
|
{
|
||||||
await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
|
await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
|
||||||
@ -1348,6 +1367,43 @@ namespace Marco.Pms.Services.Helpers
|
|||||||
NextStatus = g.Select(s => s.NextStatus).OrderBy(s => s!.Name).ToList()
|
NextStatus = g.Select(s => s.NextStatus).OrderBy(s => s!.Name).ToList()
|
||||||
}).FirstOrDefaultAsync();
|
}).FirstOrDefaultAsync();
|
||||||
});
|
});
|
||||||
|
var paidByTask = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
|
||||||
|
return await dbContext.Employees.Include(e => e.JobRole).AsNoTracking().FirstOrDefaultAsync(e => e.Id == model.PaidById && e.TenantId == tenantId);
|
||||||
|
});
|
||||||
|
var createdByTask = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
|
||||||
|
return await dbContext.Employees.Include(e => e.JobRole).AsNoTracking().FirstOrDefaultAsync(e => e.Id == model.CreatedById && e.TenantId == tenantId);
|
||||||
|
});
|
||||||
|
|
||||||
|
await Task.WhenAll(statusMappingTask, paidByTask, createdByTask);
|
||||||
|
var statusMapping = statusMappingTask.Result;
|
||||||
|
var paidBy = paidByTask.Result;
|
||||||
|
var createdBy = createdByTask.Result;
|
||||||
|
|
||||||
|
var reviewedByTask = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
|
||||||
|
return await dbContext.Employees.Include(e => e.JobRole).AsNoTracking().FirstOrDefaultAsync(e => e.Id == model.ReviewedById && e.TenantId == tenantId);
|
||||||
|
});
|
||||||
|
var approvedByTask = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
|
||||||
|
return await dbContext.Employees.Include(e => e.JobRole).AsNoTracking().FirstOrDefaultAsync(e => e.Id == model.ApprovedById && e.TenantId == tenantId);
|
||||||
|
});
|
||||||
|
var processedByTask = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
|
||||||
|
return await dbContext.Employees.Include(e => e.JobRole).AsNoTracking().FirstOrDefaultAsync(e => e.Id == model.ProcessedById && e.TenantId == tenantId);
|
||||||
|
});
|
||||||
|
|
||||||
|
await Task.WhenAll(reviewedByTask, approvedByTask, processedByTask);
|
||||||
|
var reviewedBy = reviewedByTask.Result;
|
||||||
|
var approvedBy = approvedByTask.Result;
|
||||||
|
var processedBy = processedByTask.Result;
|
||||||
|
|
||||||
var statusTask = Task.Run(async () =>
|
var statusTask = Task.Run(async () =>
|
||||||
{
|
{
|
||||||
await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
|
await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
|
||||||
@ -1378,25 +1434,13 @@ namespace Marco.Pms.Services.Helpers
|
|||||||
.FirstOrDefaultAsync();
|
.FirstOrDefaultAsync();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Await all prerequisite checks at once.
|
await Task.WhenAll(statusTask, billAttachmentsTask);
|
||||||
await Task.WhenAll(projectTask, expenseCategoryTask, paymentModeTask, statusMappingTask, paidByTask, createdByTask, reviewedByTask, approvedByTask,
|
|
||||||
processedByTask, statusTask, billAttachmentsTask);
|
|
||||||
|
|
||||||
var project = projectTask.Result;
|
|
||||||
var expenseCategory = expenseCategoryTask.Result;
|
|
||||||
var paymentMode = paymentModeTask.Result;
|
|
||||||
var statusMapping = statusMappingTask.Result;
|
|
||||||
var paidBy = paidByTask.Result;
|
|
||||||
var createdBy = createdByTask.Result;
|
|
||||||
var reviewedBy = reviewedByTask.Result;
|
|
||||||
var approvedBy = approvedByTask.Result;
|
|
||||||
var processedBy = processedByTask.Result;
|
|
||||||
var billAttachment = billAttachmentsTask.Result;
|
var billAttachment = billAttachmentsTask.Result;
|
||||||
|
|
||||||
|
|
||||||
var response = _mapper.Map<ExpenseDetailsMongoDB>(model);
|
var response = _mapper.Map<ExpenseDetailsMongoDB>(model);
|
||||||
|
|
||||||
response.Project = _mapper.Map<ProjectBasicMongoDB>(project);
|
response.Project = project;
|
||||||
response.PaidBy = _mapper.Map<BasicEmployeeMongoDB>(paidBy);
|
response.PaidBy = _mapper.Map<BasicEmployeeMongoDB>(paidBy);
|
||||||
response.CreatedBy = _mapper.Map<BasicEmployeeMongoDB>(createdBy);
|
response.CreatedBy = _mapper.Map<BasicEmployeeMongoDB>(createdBy);
|
||||||
response.ReviewedBy = _mapper.Map<BasicEmployeeMongoDB>(reviewedBy);
|
response.ReviewedBy = _mapper.Map<BasicEmployeeMongoDB>(reviewedBy);
|
||||||
|
|||||||
@ -162,6 +162,10 @@ namespace Marco.Pms.Services.MappingProfiles
|
|||||||
dest => dest.ProjectStatusId,
|
dest => dest.ProjectStatusId,
|
||||||
opt => opt.MapFrom(src => Guid.Empty)
|
opt => opt.MapFrom(src => Guid.Empty)
|
||||||
);
|
);
|
||||||
|
CreateMap<ProjectBasicMongoDB, BasicProjectVM>()
|
||||||
|
.ForMember(
|
||||||
|
dest => dest.Id,
|
||||||
|
opt => opt.MapFrom(src => new Guid(src.Id)));
|
||||||
|
|
||||||
CreateMap<ProjectMongoDB, Project>()
|
CreateMap<ProjectMongoDB, Project>()
|
||||||
.ForMember(
|
.ForMember(
|
||||||
@ -197,6 +201,7 @@ namespace Marco.Pms.Services.MappingProfiles
|
|||||||
CreateMap<ServiceProject, BasicProjectVM>();
|
CreateMap<ServiceProject, BasicProjectVM>();
|
||||||
CreateMap<ServiceProject, ServiceProjectVM>();
|
CreateMap<ServiceProject, ServiceProjectVM>();
|
||||||
CreateMap<ServiceProject, BasicServiceProjectVM>();
|
CreateMap<ServiceProject, BasicServiceProjectVM>();
|
||||||
|
CreateMap<ServiceProject, ProjectBasicMongoDB>();
|
||||||
CreateMap<ServiceProject, ServiceProjectDetailsVM>();
|
CreateMap<ServiceProject, ServiceProjectDetailsVM>();
|
||||||
CreateMap<ServiceProjectAllocation, ServiceProjectAllocationVM>();
|
CreateMap<ServiceProjectAllocation, ServiceProjectAllocationVM>();
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
using AutoMapper;
|
using AutoMapper;
|
||||||
using Marco.Pms.DataAccess.Data;
|
using Marco.Pms.DataAccess.Data;
|
||||||
using Marco.Pms.Helpers.Utility;
|
using Marco.Pms.Helpers.Utility;
|
||||||
using Marco.Pms.Model.DocumentManager;
|
|
||||||
using Marco.Pms.Model.Dtos.Expenses;
|
using Marco.Pms.Model.Dtos.Expenses;
|
||||||
using Marco.Pms.Model.Employees;
|
using Marco.Pms.Model.Employees;
|
||||||
using Marco.Pms.Model.Entitlements;
|
using Marco.Pms.Model.Entitlements;
|
||||||
@ -25,8 +24,10 @@ using Marco.Pms.Model.ViewModels.Projects;
|
|||||||
using Marco.Pms.Services.Helpers;
|
using Marco.Pms.Services.Helpers;
|
||||||
using Marco.Pms.Services.Service.ServiceInterfaces;
|
using Marco.Pms.Services.Service.ServiceInterfaces;
|
||||||
using MarcoBMS.Services.Service;
|
using MarcoBMS.Services.Service;
|
||||||
|
using Microsoft.CodeAnalysis;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
using Document = Marco.Pms.Model.DocumentManager.Document;
|
||||||
|
|
||||||
namespace Marco.Pms.Services.Service
|
namespace Marco.Pms.Services.Service
|
||||||
{
|
{
|
||||||
@ -152,7 +153,6 @@ namespace Marco.Pms.Services.Service
|
|||||||
.Include(e => e.ApprovedBy)
|
.Include(e => e.ApprovedBy)
|
||||||
.Include(e => e.ReviewedBy)
|
.Include(e => e.ReviewedBy)
|
||||||
.Include(e => e.PaymentMode)
|
.Include(e => e.PaymentMode)
|
||||||
.Include(e => e.Project)
|
|
||||||
.Include(e => e.PaymentMode)
|
.Include(e => e.PaymentMode)
|
||||||
.Include(e => e.ExpenseCategory)
|
.Include(e => e.ExpenseCategory)
|
||||||
.Include(e => e.PaymentRequest)
|
.Include(e => e.PaymentRequest)
|
||||||
@ -245,6 +245,25 @@ 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 projectIds = expensesList.Select(e => e.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);
|
||||||
|
|
||||||
//expenseVM = await GetAllExpnesRelatedTables(expensesList, tenantId);
|
//expenseVM = await GetAllExpnesRelatedTables(expensesList, tenantId);
|
||||||
expenseVM = expensesList.Select(e =>
|
expenseVM = expensesList.Select(e =>
|
||||||
{
|
{
|
||||||
@ -252,6 +271,7 @@ namespace Marco.Pms.Services.Service
|
|||||||
result.ExpenseUId = $"{e.UIDPrefix}/{e.UIDPostfix:D5}";
|
result.ExpenseUId = $"{e.UIDPrefix}/{e.UIDPostfix:D5}";
|
||||||
if (e.PaymentRequest != null)
|
if (e.PaymentRequest != null)
|
||||||
result.PaymentRequestUID = $"{e.PaymentRequest.UIDPrefix}/{e.PaymentRequest.UIDPostfix:D5}";
|
result.PaymentRequestUID = $"{e.PaymentRequest.UIDPrefix}/{e.PaymentRequest.UIDPostfix:D5}";
|
||||||
|
result.Project = projects.FirstOrDefault(p => p.Id == e.ProjectId);
|
||||||
return result;
|
return result;
|
||||||
}).ToList();
|
}).ToList();
|
||||||
totalPages = (int)Math.Ceiling((double)totalEntites / pageSize);
|
totalPages = (int)Math.Ceiling((double)totalEntites / pageSize);
|
||||||
@ -327,7 +347,6 @@ namespace Marco.Pms.Services.Service
|
|||||||
.Include(e => e.ApprovedBy)
|
.Include(e => e.ApprovedBy)
|
||||||
.Include(e => e.ReviewedBy)
|
.Include(e => e.ReviewedBy)
|
||||||
.Include(e => e.PaymentMode)
|
.Include(e => e.PaymentMode)
|
||||||
.Include(e => e.Project)
|
|
||||||
.Include(e => e.PaymentMode)
|
.Include(e => e.PaymentMode)
|
||||||
.Include(e => e.ExpenseCategory)
|
.Include(e => e.ExpenseCategory)
|
||||||
.Include(e => e.Status)
|
.Include(e => e.Status)
|
||||||
@ -439,17 +458,42 @@ namespace Marco.Pms.Services.Service
|
|||||||
{
|
{
|
||||||
var expenses = await _context.Expenses
|
var expenses = await _context.Expenses
|
||||||
.Include(e => e.PaidBy)
|
.Include(e => e.PaidBy)
|
||||||
.Include(e => e.Project)
|
|
||||||
.Include(e => e.CreatedBy)
|
.Include(e => e.CreatedBy)
|
||||||
.Include(e => e.Status)
|
.Include(e => e.Status)
|
||||||
.Include(e => e.ExpenseCategory)
|
.Include(e => e.ExpenseCategory)
|
||||||
.Where(e => e.TenantId == tenantId)
|
.Where(e => e.TenantId == tenantId)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
|
|
||||||
|
var projectIds = expenses.Select(e => e.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 = expenses.Where(e => e.Project != null).Select(e => new { Id = e.Project!.Id, Name = e.Project.Name }).Distinct().ToList(),
|
Projects = projects,
|
||||||
PaidBy = expenses.Where(e => e.PaidBy != null).Select(e => new { Id = e.PaidBy!.Id, Name = $"{e.PaidBy.FirstName} {e.PaidBy.LastName}" }).Distinct().ToList(),
|
PaidBy = expenses.Where(e => e.PaidBy != null).Select(e => new { Id = e.PaidBy!.Id, Name = $"{e.PaidBy.FirstName} {e.PaidBy.LastName}" }).Distinct().ToList(),
|
||||||
CreatedBy = expenses.Where(e => e.CreatedBy != null).Select(e => new { Id = e.CreatedBy!.Id, Name = $"{e.CreatedBy.FirstName} {e.CreatedBy.LastName}" }).Distinct().ToList(),
|
CreatedBy = expenses.Where(e => e.CreatedBy != null).Select(e => new { Id = e.CreatedBy!.Id, Name = $"{e.CreatedBy.FirstName} {e.CreatedBy.LastName}" }).Distinct().ToList(),
|
||||||
Status = expenses.Where(e => e.Status != null).Select(e => new { Id = e.Status!.Id, Name = e.Status.Name }).Distinct().ToList(),
|
Status = expenses.Where(e => e.Status != null).Select(e => new { Id = e.Status!.Id, Name = e.Status.Name }).Distinct().ToList(),
|
||||||
@ -495,10 +539,23 @@ namespace Marco.Pms.Services.Service
|
|||||||
|
|
||||||
// VALIDATION CHECKS: Use IDbContextFactory for thread-safe, parallel database queries.
|
// VALIDATION CHECKS: Use IDbContextFactory for thread-safe, parallel database queries.
|
||||||
// Each task gets its own DbContext instance.
|
// Each task gets its own DbContext instance.
|
||||||
var projectTask = Task.Run(async () =>
|
var infraProjectTask = Task.Run(async () =>
|
||||||
{
|
{
|
||||||
await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
|
await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
|
||||||
return await dbContext.Projects.AsNoTracking().FirstOrDefaultAsync(p => p.Id == dto.ProjectId);
|
return await dbContext.Projects
|
||||||
|
.AsNoTracking()
|
||||||
|
.Where(p => p.Id == dto.ProjectId && p.TenantId == tenantId)
|
||||||
|
.Select(p => _mapper.Map<BasicProjectVM>(p))
|
||||||
|
.FirstOrDefaultAsync();
|
||||||
|
});
|
||||||
|
var serviceProjectTask = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
|
||||||
|
return await dbContext.ServiceProjects
|
||||||
|
.AsNoTracking()
|
||||||
|
.Where(sp => sp.Id == dto.ProjectId && sp.TenantId == tenantId)
|
||||||
|
.Select(sp => _mapper.Map<BasicProjectVM>(sp))
|
||||||
|
.FirstOrDefaultAsync();
|
||||||
});
|
});
|
||||||
var paidByTask = Task.Run(async () =>
|
var paidByTask = Task.Run(async () =>
|
||||||
{
|
{
|
||||||
@ -534,7 +591,7 @@ namespace Marco.Pms.Services.Service
|
|||||||
|
|
||||||
|
|
||||||
// Await all prerequisite checks at once.
|
// Await all prerequisite checks at once.
|
||||||
await Task.WhenAll(hasUploadPermissionTask, projectTask, expenseCategoriesTask, paymentModeTask, statusMappingTask, paidByTask);
|
await Task.WhenAll(hasUploadPermissionTask, infraProjectTask, serviceProjectTask);
|
||||||
|
|
||||||
// 2. Aggregate and Check Results
|
// 2. Aggregate and Check Results
|
||||||
if (!await hasUploadPermissionTask)
|
if (!await hasUploadPermissionTask)
|
||||||
@ -544,7 +601,11 @@ namespace Marco.Pms.Services.Service
|
|||||||
}
|
}
|
||||||
|
|
||||||
var validationErrors = new List<string>();
|
var validationErrors = new List<string>();
|
||||||
var project = await projectTask;
|
var infraProject = infraProjectTask.Result;
|
||||||
|
var serviceProject = serviceProjectTask.Result;
|
||||||
|
var project = infraProject ?? serviceProject;
|
||||||
|
|
||||||
|
await Task.WhenAll(expenseCategoriesTask, paymentModeTask, statusMappingTask, paidByTask);
|
||||||
var expenseCategory = await expenseCategoriesTask;
|
var expenseCategory = await expenseCategoriesTask;
|
||||||
var paymentMode = await paymentModeTask;
|
var paymentMode = await paymentModeTask;
|
||||||
var statusMapping = await statusMappingTask;
|
var statusMapping = await statusMappingTask;
|
||||||
@ -590,7 +651,8 @@ namespace Marco.Pms.Services.Service
|
|||||||
// 4. Process Attachments
|
// 4. Process Attachments
|
||||||
if (dto.BillAttachments?.Any() ?? false)
|
if (dto.BillAttachments?.Any() ?? false)
|
||||||
{
|
{
|
||||||
await ProcessAndUploadAttachmentsAsync(dto.BillAttachments, expense, loggedInEmployee.Id, tenantId);
|
bool isServiceProject = serviceProject != null;
|
||||||
|
await ProcessAndUploadAttachmentsAsync(dto.BillAttachments, expense, isServiceProject, loggedInEmployee.Id, tenantId);
|
||||||
}
|
}
|
||||||
|
|
||||||
var expenseLog = new ExpenseLog
|
var expenseLog = new ExpenseLog
|
||||||
@ -616,7 +678,7 @@ namespace Marco.Pms.Services.Service
|
|||||||
var response = _mapper.Map<ExpenseList>(expense);
|
var response = _mapper.Map<ExpenseList>(expense);
|
||||||
response.ExpenseUId = $"{expense.UIDPrefix}/{expense.UIDPostfix:D5}";
|
response.ExpenseUId = $"{expense.UIDPrefix}/{expense.UIDPostfix:D5}";
|
||||||
response.PaidBy = _mapper.Map<BasicEmployeeVM>(paidBy);
|
response.PaidBy = _mapper.Map<BasicEmployeeVM>(paidBy);
|
||||||
response.Project = _mapper.Map<ProjectInfoVM>(project);
|
response.Project = project;
|
||||||
response.Status = _mapper.Map<ExpensesStatusMasterVM>(statusMapping!.Status);
|
response.Status = _mapper.Map<ExpensesStatusMasterVM>(statusMapping!.Status);
|
||||||
response.NextStatus = _mapper.Map<List<ExpensesStatusMasterVM>>(statusMapping.NextStatus);
|
response.NextStatus = _mapper.Map<List<ExpensesStatusMasterVM>>(statusMapping.NextStatus);
|
||||||
response.PaymentMode = _mapper.Map<PaymentModeMatserVM>(paymentMode);
|
response.PaymentMode = _mapper.Map<PaymentModeMatserVM>(paymentMode);
|
||||||
@ -658,7 +720,6 @@ namespace Marco.Pms.Services.Service
|
|||||||
// 1. Fetch Existing Expense with Related Entities (Single Query)
|
// 1. Fetch Existing Expense with Related Entities (Single Query)
|
||||||
var expense = await _context.Expenses
|
var expense = await _context.Expenses
|
||||||
.Include(e => e.ExpenseCategory)
|
.Include(e => e.ExpenseCategory)
|
||||||
.Include(e => e.Project)
|
|
||||||
.Include(e => e.PaidBy).ThenInclude(e => e!.JobRole)
|
.Include(e => e.PaidBy).ThenInclude(e => e!.JobRole)
|
||||||
.Include(e => e.PaymentMode)
|
.Include(e => e.PaymentMode)
|
||||||
.Include(e => e.Status)
|
.Include(e => e.Status)
|
||||||
@ -934,6 +995,29 @@ namespace Marco.Pms.Services.Service
|
|||||||
if (nextPossibleStatuses is { Count: > 0 })
|
if (nextPossibleStatuses is { Count: > 0 })
|
||||||
responseDto.NextStatus = _mapper.Map<List<ExpensesStatusMasterVM>>(nextPossibleStatuses);
|
responseDto.NextStatus = _mapper.Map<List<ExpensesStatusMasterVM>>(nextPossibleStatuses);
|
||||||
|
|
||||||
|
var infraProjectTask = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
|
||||||
|
return await dbContext.Projects
|
||||||
|
.AsNoTracking()
|
||||||
|
.Where(p => p.Id == expense.ProjectId && p.TenantId == tenantId)
|
||||||
|
.Select(p => _mapper.Map<BasicProjectVM>(p))
|
||||||
|
.FirstOrDefaultAsync();
|
||||||
|
});
|
||||||
|
var serviceProjectTask = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
|
||||||
|
return await dbContext.ServiceProjects
|
||||||
|
.AsNoTracking()
|
||||||
|
.Where(sp => sp.Id == expense.ProjectId && sp.TenantId == tenantId)
|
||||||
|
.Select(sp => _mapper.Map<BasicProjectVM>(sp))
|
||||||
|
.FirstOrDefaultAsync();
|
||||||
|
});
|
||||||
|
|
||||||
|
await Task.WhenAll(infraProjectTask, serviceProjectTask);
|
||||||
|
|
||||||
|
responseDto.Project = infraProjectTask.Result ?? serviceProjectTask.Result;
|
||||||
|
|
||||||
return ApiResponse<object>.SuccessResponse(responseDto);
|
return ApiResponse<object>.SuccessResponse(responseDto);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@ -969,7 +1053,6 @@ namespace Marco.Pms.Services.Service
|
|||||||
|
|
||||||
var existingExpense = await _context.Expenses
|
var existingExpense = await _context.Expenses
|
||||||
.Include(e => e.ExpenseCategory)
|
.Include(e => e.ExpenseCategory)
|
||||||
.Include(e => e.Project)
|
|
||||||
.Include(e => e.PaidBy)
|
.Include(e => e.PaidBy)
|
||||||
.ThenInclude(e => e!.JobRole)
|
.ThenInclude(e => e!.JobRole)
|
||||||
.Include(e => e.PaymentMode)
|
.Include(e => e.PaymentMode)
|
||||||
@ -1017,12 +1100,37 @@ namespace Marco.Pms.Services.Service
|
|||||||
return ApiResponse<object>.ErrorResponse("Conflict occurred.", "This project has been modified by someone else. Please refresh and try again.", 409);
|
return ApiResponse<object>.ErrorResponse("Conflict occurred.", "This project has been modified by someone else. Please refresh and try again.", 409);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var infraProjectTask = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
|
||||||
|
return await dbContext.Projects
|
||||||
|
.AsNoTracking()
|
||||||
|
.Where(p => p.Id == existingExpense.ProjectId && p.TenantId == tenantId)
|
||||||
|
.Select(p => _mapper.Map<BasicProjectVM>(p))
|
||||||
|
.FirstOrDefaultAsync();
|
||||||
|
});
|
||||||
|
var serviceProjectTask = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
|
||||||
|
return await dbContext.ServiceProjects
|
||||||
|
.AsNoTracking()
|
||||||
|
.Where(sp => sp.Id == existingExpense.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 (model.BillAttachments?.Any() ?? false)
|
if (model.BillAttachments?.Any() ?? false)
|
||||||
{
|
{
|
||||||
var newBillAttachments = model.BillAttachments.Where(ba => ba.DocumentId == null && ba.IsActive).ToList();
|
var newBillAttachments = model.BillAttachments.Where(ba => ba.DocumentId == null && ba.IsActive).ToList();
|
||||||
if (newBillAttachments.Any())
|
if (newBillAttachments.Any())
|
||||||
{
|
{
|
||||||
await ProcessAndUploadAttachmentsAsync(newBillAttachments, existingExpense, loggedInEmployee.Id, tenantId);
|
bool isServiceProject = serviceProject != null;
|
||||||
|
await ProcessAndUploadAttachmentsAsync(newBillAttachments, existingExpense, isServiceProject, loggedInEmployee.Id, tenantId);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await _context.SaveChangesAsync();
|
await _context.SaveChangesAsync();
|
||||||
@ -1102,6 +1210,9 @@ namespace Marco.Pms.Services.Service
|
|||||||
response.NextStatus = _mapper.Map<List<ExpensesStatusMasterVM>>(nextPossibleStatuses);
|
response.NextStatus = _mapper.Map<List<ExpensesStatusMasterVM>>(nextPossibleStatuses);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
response.Project = serviceProject ?? infraProject;
|
||||||
|
|
||||||
return ApiResponse<object>.SuccessResponse(response, "Expense Updated Successfully", 200);
|
return ApiResponse<object>.SuccessResponse(response, "Expense Updated Successfully", 200);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@ -1883,9 +1994,31 @@ namespace Marco.Pms.Services.Service
|
|||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
});
|
});
|
||||||
|
|
||||||
await Task.WhenAll(statusTransitionTask, targetStatusPermissionsTask);
|
var infraProjectTask = Task.Run(async () =>
|
||||||
var statusTransition = await statusTransitionTask;
|
{
|
||||||
var requiredPermissions = await targetStatusPermissionsTask;
|
await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
|
||||||
|
return await dbContext.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 dbContext = await _dbContextFactory.CreateDbContextAsync();
|
||||||
|
return await dbContext.ServiceProjects
|
||||||
|
.AsNoTracking()
|
||||||
|
.Where(sp => sp.Id == paymentRequest.ProjectId && sp.TenantId == tenantId)
|
||||||
|
.Select(sp => _mapper.Map<BasicProjectVM>(sp))
|
||||||
|
.FirstOrDefaultAsync();
|
||||||
|
});
|
||||||
|
|
||||||
|
await Task.WhenAll(infraProjectTask, serviceProjectTask, statusTransitionTask, targetStatusPermissionsTask);
|
||||||
|
|
||||||
|
var statusTransition = statusTransitionTask.Result;
|
||||||
|
var requiredPermissions = targetStatusPermissionsTask.Result;
|
||||||
|
var infraProject = infraProjectTask.Result;
|
||||||
|
var serviceProject = serviceProjectTask.Result;
|
||||||
|
|
||||||
// 3. Validate Transition and Required Fields
|
// 3. Validate Transition and Required Fields
|
||||||
if (statusTransition == null)
|
if (statusTransition == null)
|
||||||
@ -2055,7 +2188,10 @@ namespace Marco.Pms.Services.Service
|
|||||||
GSTNumber = model.GSTNumber,
|
GSTNumber = model.GSTNumber,
|
||||||
BillAttachments = model.BillAttachments
|
BillAttachments = model.BillAttachments
|
||||||
};
|
};
|
||||||
var response = await ChangeToExpanseFromPaymentRequestAsync(expenseConversion, paymentRequest, loggedInEmployee, tenantId);
|
|
||||||
|
bool isServiceProject = serviceProject != null;
|
||||||
|
|
||||||
|
var response = await ChangeToExpanseFromPaymentRequestAsync(expenseConversion, paymentRequest, isServiceProject, loggedInEmployee, tenantId);
|
||||||
if (!response.Success)
|
if (!response.Success)
|
||||||
{
|
{
|
||||||
return response;
|
return response;
|
||||||
@ -2122,7 +2258,8 @@ namespace Marco.Pms.Services.Service
|
|||||||
return ApiResponse<object>.SuccessResponse(responseDto, "Status updated, but audit logging or cache update failed.");
|
return ApiResponse<object>.SuccessResponse(responseDto, "Status updated, but audit logging or cache update failed.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public async Task<ApiResponse<object>> ChangeToExpanseFromPaymentRequestAsync(ExpenseConversionDto model, PaymentRequest paymentRequest, Employee loggedInEmployee, Guid tenantId)
|
public async Task<ApiResponse<object>> ChangeToExpanseFromPaymentRequestAsync(ExpenseConversionDto model, PaymentRequest paymentRequest, bool isServiceProject,
|
||||||
|
Employee loggedInEmployee, Guid tenantId)
|
||||||
{
|
{
|
||||||
_logger.LogInfo("Start ChangeToExpanseFromPaymentRequestAsync called by EmployeeId: {EmployeeId} for TenantId: {TenantId} PaymentRequestId: {PaymentRequestId}",
|
_logger.LogInfo("Start ChangeToExpanseFromPaymentRequestAsync called by EmployeeId: {EmployeeId} for TenantId: {TenantId} PaymentRequestId: {PaymentRequestId}",
|
||||||
loggedInEmployee.Id, tenantId, model.PaymentRequestId);
|
loggedInEmployee.Id, tenantId, model.PaymentRequestId);
|
||||||
@ -2259,7 +2396,7 @@ namespace Marco.Pms.Services.Service
|
|||||||
// Process and upload bill attachments if present
|
// Process and upload bill attachments if present
|
||||||
if (hasAttachments)
|
if (hasAttachments)
|
||||||
{
|
{
|
||||||
await ProcessAndUploadAttachmentsAsync(model.BillAttachments!, expense, loggedInEmployee.Id, tenantId);
|
await ProcessAndUploadAttachmentsAsync(model.BillAttachments!, expense, isServiceProject, loggedInEmployee.Id, tenantId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mark the payment request as converted to expense to prevent duplicates
|
// Mark the payment request as converted to expense to prevent duplicates
|
||||||
@ -3490,19 +3627,31 @@ namespace Marco.Pms.Services.Service
|
|||||||
.FirstOrDefaultAsync();
|
.FirstOrDefaultAsync();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var infraProjectTask = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
await using var context = await _dbContextFactory.CreateDbContextAsync();
|
||||||
|
return await context.Projects.Where(p => p.Id == model.ProjectId && p.TenantId == tenantId).Select(p => _mapper.Map<ProjectBasicMongoDB>(p)).FirstOrDefaultAsync();
|
||||||
|
});
|
||||||
|
|
||||||
|
var serviceProjectTask = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
await using var context = await _dbContextFactory.CreateDbContextAsync();
|
||||||
|
return await context.ServiceProjects.Where(sp => sp.Id == model.ProjectId && sp.TenantId == tenantId).Select(sp => _mapper.Map<ProjectBasicMongoDB>(sp)).FirstOrDefaultAsync();
|
||||||
|
});
|
||||||
|
|
||||||
// Await all prerequisite checks at once.
|
// Await all prerequisite checks at once.
|
||||||
await Task.WhenAll(statusTask, billAttachmentsTask);
|
await Task.WhenAll(statusTask, billAttachmentsTask, infraProjectTask, serviceProjectTask);
|
||||||
|
|
||||||
var statusMapping = statusMappingTask.Result;
|
var statusMapping = statusMappingTask.Result;
|
||||||
var billAttachment = billAttachmentsTask.Result;
|
var billAttachment = billAttachmentsTask.Result;
|
||||||
|
var project = infraProjectTask.Result ?? serviceProjectTask.Result ?? new ProjectBasicMongoDB();
|
||||||
|
|
||||||
var response = _mapper.Map<ExpenseDetailsMongoDB>(model);
|
var response = _mapper.Map<ExpenseDetailsMongoDB>(model);
|
||||||
|
|
||||||
response.ExpenseUId = $"{model.UIDPrefix}/{model.UIDPostfix:D5}";
|
response.ExpenseUId = $"{model.UIDPrefix}/{model.UIDPostfix:D5}";
|
||||||
if (model.PaymentRequest != null)
|
if (model.PaymentRequest != null)
|
||||||
response.PaymentRequestUID = $"{model.PaymentRequest.UIDPrefix}/{model.PaymentRequest.UIDPostfix:D5}";
|
response.PaymentRequestUID = $"{model.PaymentRequest.UIDPrefix}/{model.PaymentRequest.UIDPostfix:D5}";
|
||||||
response.Project = _mapper.Map<ProjectBasicMongoDB>(model.Project);
|
response.Project = project;
|
||||||
response.PaidBy = _mapper.Map<BasicEmployeeMongoDB>(model.PaidBy);
|
response.PaidBy = _mapper.Map<BasicEmployeeMongoDB>(model.PaidBy);
|
||||||
response.CreatedBy = _mapper.Map<BasicEmployeeMongoDB>(model.CreatedBy);
|
response.CreatedBy = _mapper.Map<BasicEmployeeMongoDB>(model.CreatedBy);
|
||||||
response.ReviewedBy = _mapper.Map<BasicEmployeeMongoDB>(model.ReviewedBy);
|
response.ReviewedBy = _mapper.Map<BasicEmployeeMongoDB>(model.ReviewedBy);
|
||||||
@ -3653,7 +3802,7 @@ namespace Marco.Pms.Services.Service
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Processes and uploads attachments concurrently, then adds the resulting entities to the main DbContext.
|
/// Processes and uploads attachments concurrently, then adds the resulting entities to the main DbContext.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private async Task ProcessAndUploadAttachmentsAsync(IEnumerable<FileUploadModel> attachments, Expenses expense, Guid employeeId, Guid tenantId)
|
private async Task ProcessAndUploadAttachmentsAsync(IEnumerable<FileUploadModel> attachments, Expenses expense, bool isServiceProject, Guid employeeId, Guid tenantId)
|
||||||
{
|
{
|
||||||
// Pre-validate all attachments to fail fast before any uploads.
|
// Pre-validate all attachments to fail fast before any uploads.
|
||||||
foreach (var attachment in attachments)
|
foreach (var attachment in attachments)
|
||||||
@ -3668,7 +3817,7 @@ namespace Marco.Pms.Services.Service
|
|||||||
|
|
||||||
// Create a list of tasks to be executed concurrently.
|
// Create a list of tasks to be executed concurrently.
|
||||||
var processingTasks = attachments.Select(attachment =>
|
var processingTasks = attachments.Select(attachment =>
|
||||||
ProcessSingleExpenseAttachmentAsync(attachment, expense, employeeId, tenantId, batchId)
|
ProcessSingleExpenseAttachmentAsync(attachment, expense, employeeId, isServiceProject, tenantId, batchId)
|
||||||
).ToList();
|
).ToList();
|
||||||
|
|
||||||
var results = await Task.WhenAll(processingTasks);
|
var results = await Task.WhenAll(processingTasks);
|
||||||
@ -3686,12 +3835,17 @@ namespace Marco.Pms.Services.Service
|
|||||||
/// Handles the logic for a single attachment: upload to S3 and create corresponding entities.
|
/// Handles the logic for a single attachment: upload to S3 and create corresponding entities.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private async Task<(Document document, BillAttachments billAttachment)> ProcessSingleExpenseAttachmentAsync(
|
private async Task<(Document document, BillAttachments billAttachment)> ProcessSingleExpenseAttachmentAsync(
|
||||||
FileUploadModel attachment, Expenses expense, Guid employeeId, Guid tenantId, Guid batchId)
|
FileUploadModel attachment, Expenses expense, Guid employeeId, bool isServiceProject, Guid tenantId, Guid batchId)
|
||||||
{
|
{
|
||||||
var base64Data = attachment.Base64Data!.Contains(',') ? attachment.Base64Data[(attachment.Base64Data.IndexOf(",") + 1)..] : attachment.Base64Data;
|
var base64Data = attachment.Base64Data!.Contains(',') ? attachment.Base64Data[(attachment.Base64Data.IndexOf(",") + 1)..] : attachment.Base64Data;
|
||||||
var fileType = _s3Service.GetContentTypeFromBase64(base64Data);
|
var fileType = _s3Service.GetContentTypeFromBase64(base64Data);
|
||||||
var fileName = _s3Service.GenerateFileName(fileType, expense.Id, "Expense");
|
var fileName = _s3Service.GenerateFileName(fileType, expense.Id, "Expense");
|
||||||
var objectKey = $"tenant-{tenantId}/project-{expense.ProjectId}/Expenses/{fileName}";
|
var objectKey = $"tenant-{tenantId}/Project/{expense.ProjectId}/Expenses/{fileName}";
|
||||||
|
|
||||||
|
if (isServiceProject)
|
||||||
|
{
|
||||||
|
objectKey = $"tenant-{tenantId}/ServiceProject/{expense.ProjectId}/Expenses/{fileName}";
|
||||||
|
}
|
||||||
|
|
||||||
// Await the I/O-bound upload operation directly.
|
// Await the I/O-bound upload operation directly.
|
||||||
await _s3Service.UploadFileAsync(base64Data, fileType, objectKey);
|
await _s3Service.UploadFileAsync(base64Data, fileType, objectKey);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user