Added the payment request attachment in create payment request API

This commit is contained in:
ashutosh.nehete 2025-11-01 15:48:36 +05:30
parent e821724e83
commit e7edc2f45c
11 changed files with 7853 additions and 100 deletions

View File

@ -106,6 +106,7 @@ namespace Marco.Pms.DataAccess.Data
public DbSet<Expenses> Expenses { get; set; }
public DbSet<ExpenseLog> ExpenseLogs { get; set; }
public DbSet<ExpensesTypeMaster> ExpensesTypeMaster { get; set; }
public DbSet<ExpenseCategoryMaster> ExpenseCategoryMasters { get; set; }
public DbSet<PaymentModeMatser> PaymentModeMatser { get; set; }
public DbSet<ExpensesStatusMaster> ExpensesStatusMaster { get; set; }
public DbSet<BillAttachments> BillAttachments { get; set; }
@ -114,6 +115,7 @@ namespace Marco.Pms.DataAccess.Data
public DbSet<StatusPermissionMapping> StatusPermissionMapping { get; set; }
public DbSet<ExpensesStatusMapping> ExpensesStatusMapping { get; set; }
public DbSet<PaymentRequest> PaymentRequests { get; set; }
public DbSet<PaymentRequestAttachment> PaymentRequestAttachments { get; set; }
public DbSet<RecurringPayment> RecurringPayments { get; set; }
public DbSet<AdvancePaymentTransaction> AdvancePaymentTransactions { get; set; }
@ -674,8 +676,8 @@ namespace Marco.Pms.DataAccess.Data
StatusId = Guid.Parse("61578360-3a49-4c34-8604-7b35a3787b95")
});
modelBuilder.Entity<ExpensesTypeMaster>().HasData(
new ExpensesTypeMaster
modelBuilder.Entity<ExpenseCategoryMaster>().HasData(
new ExpenseCategoryMaster
{
Id = Guid.Parse("5e0c6227-d49d-41ff-9f1f-781f0aee2469"),
Name = "Procurement",
@ -685,7 +687,7 @@ namespace Marco.Pms.DataAccess.Data
IsAttachmentRequried = true,
TenantId = Guid.Parse("b3466e83-7e11-464c-b93a-daf047838b26")
},
new ExpensesTypeMaster
new ExpenseCategoryMaster
{
Id = Guid.Parse("2de53163-0dbd-404b-8e60-1b02e6b4886a"),
Name = "Transport",
@ -695,7 +697,7 @@ namespace Marco.Pms.DataAccess.Data
IsAttachmentRequried = false,
TenantId = Guid.Parse("b3466e83-7e11-464c-b93a-daf047838b26")
},
new ExpensesTypeMaster
new ExpenseCategoryMaster
{
Id = Guid.Parse("dd120bc4-ab0a-45ba-8450-5cd45ff221ca"),
Name = "Travelling",
@ -705,7 +707,7 @@ namespace Marco.Pms.DataAccess.Data
IsAttachmentRequried = false,
TenantId = Guid.Parse("b3466e83-7e11-464c-b93a-daf047838b26")
},
new ExpensesTypeMaster
new ExpenseCategoryMaster
{
Id = Guid.Parse("52484820-1b54-4865-8f0f-baa2b1d339b9"),
Name = "Mobilization",
@ -715,7 +717,7 @@ namespace Marco.Pms.DataAccess.Data
IsAttachmentRequried = true,
TenantId = Guid.Parse("b3466e83-7e11-464c-b93a-daf047838b26")
},
new ExpensesTypeMaster
new ExpenseCategoryMaster
{
Id = Guid.Parse("fc59eb90-98ea-481c-b421-54bfa9e42d8f"),
Name = "Employee Welfare",
@ -725,7 +727,7 @@ namespace Marco.Pms.DataAccess.Data
IsAttachmentRequried = true,
TenantId = Guid.Parse("b3466e83-7e11-464c-b93a-daf047838b26")
},
new ExpensesTypeMaster
new ExpenseCategoryMaster
{
Id = Guid.Parse("77013784-9324-4d8b-bd36-d6f928e68942"),
Name = "Maintenance & Utilities",
@ -735,7 +737,7 @@ namespace Marco.Pms.DataAccess.Data
IsAttachmentRequried = true,
TenantId = Guid.Parse("b3466e83-7e11-464c-b93a-daf047838b26")
},
new ExpensesTypeMaster
new ExpenseCategoryMaster
{
Id = Guid.Parse("1e2d697a-76b4-4be8-bc66-87144561a1a0"),
Name = "Vendor/Supplier Payments",
@ -745,7 +747,7 @@ namespace Marco.Pms.DataAccess.Data
IsAttachmentRequried = true,
TenantId = Guid.Parse("b3466e83-7e11-464c-b93a-daf047838b26")
},
new ExpensesTypeMaster
new ExpenseCategoryMaster
{
Id = Guid.Parse("4842fa61-64eb-4241-aebd-8282065af9f9"),
Name = "Compliance & Safety",

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,209 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional
namespace Marco.Pms.DataAccess.Migrations
{
/// <inheritdoc />
public partial class Added_PaymentRequestAttachmnets_Table : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_PaymentRequests_ExpensesTypeMaster_ExpenseCategoryId",
table: "PaymentRequests");
migrationBuilder.DeleteData(
table: "ExpensesTypeMaster",
keyColumn: "Id",
keyValue: new Guid("1e2d697a-76b4-4be8-bc66-87144561a1a0"));
migrationBuilder.DeleteData(
table: "ExpensesTypeMaster",
keyColumn: "Id",
keyValue: new Guid("2de53163-0dbd-404b-8e60-1b02e6b4886a"));
migrationBuilder.DeleteData(
table: "ExpensesTypeMaster",
keyColumn: "Id",
keyValue: new Guid("4842fa61-64eb-4241-aebd-8282065af9f9"));
migrationBuilder.DeleteData(
table: "ExpensesTypeMaster",
keyColumn: "Id",
keyValue: new Guid("52484820-1b54-4865-8f0f-baa2b1d339b9"));
migrationBuilder.DeleteData(
table: "ExpensesTypeMaster",
keyColumn: "Id",
keyValue: new Guid("5e0c6227-d49d-41ff-9f1f-781f0aee2469"));
migrationBuilder.DeleteData(
table: "ExpensesTypeMaster",
keyColumn: "Id",
keyValue: new Guid("77013784-9324-4d8b-bd36-d6f928e68942"));
migrationBuilder.DeleteData(
table: "ExpensesTypeMaster",
keyColumn: "Id",
keyValue: new Guid("dd120bc4-ab0a-45ba-8450-5cd45ff221ca"));
migrationBuilder.DeleteData(
table: "ExpensesTypeMaster",
keyColumn: "Id",
keyValue: new Guid("fc59eb90-98ea-481c-b421-54bfa9e42d8f"));
migrationBuilder.AddColumn<bool>(
name: "IsAdvancePayment",
table: "PaymentRequests",
type: "tinyint(1)",
nullable: false,
defaultValue: false);
migrationBuilder.CreateTable(
name: "ExpenseCategoryMasters",
columns: table => new
{
Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
Name = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
NoOfPersonsRequired = table.Column<bool>(type: "tinyint(1)", nullable: false),
Description = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
IsActive = table.Column<bool>(type: "tinyint(1)", nullable: false),
IsAttachmentRequried = table.Column<bool>(type: "tinyint(1)", nullable: false),
TenantId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci")
},
constraints: table =>
{
table.PrimaryKey("PK_ExpenseCategoryMasters", x => x.Id);
table.ForeignKey(
name: "FK_ExpenseCategoryMasters_Tenants_TenantId",
column: x => x.TenantId,
principalTable: "Tenants",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateTable(
name: "PaymentRequestAttachments",
columns: table => new
{
Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
PaymentRequestId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
DocumentId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
TenantId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci")
},
constraints: table =>
{
table.PrimaryKey("PK_PaymentRequestAttachments", x => x.Id);
table.ForeignKey(
name: "FK_PaymentRequestAttachments_Documents_DocumentId",
column: x => x.DocumentId,
principalTable: "Documents",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_PaymentRequestAttachments_PaymentRequests_PaymentRequestId",
column: x => x.PaymentRequestId,
principalTable: "PaymentRequests",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_PaymentRequestAttachments_Tenants_TenantId",
column: x => x.TenantId,
principalTable: "Tenants",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.InsertData(
table: "ExpenseCategoryMasters",
columns: new[] { "Id", "Description", "IsActive", "IsAttachmentRequried", "Name", "NoOfPersonsRequired", "TenantId" },
values: new object[,]
{
{ new Guid("1e2d697a-76b4-4be8-bc66-87144561a1a0"), "Scheduled payments for external services or goods.", true, true, "Vendor/Supplier Payments", false, new Guid("b3466e83-7e11-464c-b93a-daf047838b26") },
{ new Guid("2de53163-0dbd-404b-8e60-1b02e6b4886a"), "Vehicle fuel, logistics services and delivery of goods or personnel.", true, false, "Transport", false, new Guid("b3466e83-7e11-464c-b93a-daf047838b26") },
{ new Guid("4842fa61-64eb-4241-aebd-8282065af9f9"), "Government fees, insurance, inspections and safety-related expenditures.", true, true, "Compliance & Safety", false, new Guid("b3466e83-7e11-464c-b93a-daf047838b26") },
{ new Guid("52484820-1b54-4865-8f0f-baa2b1d339b9"), "Site setup costs including equipment deployment and temporary infrastructure.", true, true, "Mobilization", false, new Guid("b3466e83-7e11-464c-b93a-daf047838b26") },
{ new Guid("5e0c6227-d49d-41ff-9f1f-781f0aee2469"), "Materials, equipment and supplies purchased for site operations.", true, true, "Procurement", false, new Guid("b3466e83-7e11-464c-b93a-daf047838b26") },
{ new Guid("77013784-9324-4d8b-bd36-d6f928e68942"), "Machinery servicing, electricity, water, and temporary office needs.", true, true, "Maintenance & Utilities", false, new Guid("b3466e83-7e11-464c-b93a-daf047838b26") },
{ new Guid("dd120bc4-ab0a-45ba-8450-5cd45ff221ca"), "Delivery of personnel.", true, false, "Travelling", true, new Guid("b3466e83-7e11-464c-b93a-daf047838b26") },
{ new Guid("fc59eb90-98ea-481c-b421-54bfa9e42d8f"), " Worker amenities like snacks, meals, safety gear, accommodation, medical support etc.", true, true, "Employee Welfare", true, new Guid("b3466e83-7e11-464c-b93a-daf047838b26") }
});
migrationBuilder.CreateIndex(
name: "IX_ExpenseCategoryMasters_TenantId",
table: "ExpenseCategoryMasters",
column: "TenantId");
migrationBuilder.CreateIndex(
name: "IX_PaymentRequestAttachments_DocumentId",
table: "PaymentRequestAttachments",
column: "DocumentId");
migrationBuilder.CreateIndex(
name: "IX_PaymentRequestAttachments_PaymentRequestId",
table: "PaymentRequestAttachments",
column: "PaymentRequestId");
migrationBuilder.CreateIndex(
name: "IX_PaymentRequestAttachments_TenantId",
table: "PaymentRequestAttachments",
column: "TenantId");
migrationBuilder.AddForeignKey(
name: "FK_PaymentRequests_ExpenseCategoryMasters_ExpenseCategoryId",
table: "PaymentRequests",
column: "ExpenseCategoryId",
principalTable: "ExpenseCategoryMasters",
principalColumn: "Id");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_PaymentRequests_ExpenseCategoryMasters_ExpenseCategoryId",
table: "PaymentRequests");
migrationBuilder.DropTable(
name: "ExpenseCategoryMasters");
migrationBuilder.DropTable(
name: "PaymentRequestAttachments");
migrationBuilder.DropColumn(
name: "IsAdvancePayment",
table: "PaymentRequests");
migrationBuilder.InsertData(
table: "ExpensesTypeMaster",
columns: new[] { "Id", "Description", "IsActive", "IsAttachmentRequried", "Name", "NoOfPersonsRequired", "TenantId" },
values: new object[,]
{
{ new Guid("1e2d697a-76b4-4be8-bc66-87144561a1a0"), "Scheduled payments for external services or goods.", true, true, "Vendor/Supplier Payments", false, new Guid("b3466e83-7e11-464c-b93a-daf047838b26") },
{ new Guid("2de53163-0dbd-404b-8e60-1b02e6b4886a"), "Vehicle fuel, logistics services and delivery of goods or personnel.", true, false, "Transport", false, new Guid("b3466e83-7e11-464c-b93a-daf047838b26") },
{ new Guid("4842fa61-64eb-4241-aebd-8282065af9f9"), "Government fees, insurance, inspections and safety-related expenditures.", true, true, "Compliance & Safety", false, new Guid("b3466e83-7e11-464c-b93a-daf047838b26") },
{ new Guid("52484820-1b54-4865-8f0f-baa2b1d339b9"), "Site setup costs including equipment deployment and temporary infrastructure.", true, true, "Mobilization", false, new Guid("b3466e83-7e11-464c-b93a-daf047838b26") },
{ new Guid("5e0c6227-d49d-41ff-9f1f-781f0aee2469"), "Materials, equipment and supplies purchased for site operations.", true, true, "Procurement", false, new Guid("b3466e83-7e11-464c-b93a-daf047838b26") },
{ new Guid("77013784-9324-4d8b-bd36-d6f928e68942"), "Machinery servicing, electricity, water, and temporary office needs.", true, true, "Maintenance & Utilities", false, new Guid("b3466e83-7e11-464c-b93a-daf047838b26") },
{ new Guid("dd120bc4-ab0a-45ba-8450-5cd45ff221ca"), "Delivery of personnel.", true, false, "Travelling", true, new Guid("b3466e83-7e11-464c-b93a-daf047838b26") },
{ new Guid("fc59eb90-98ea-481c-b421-54bfa9e42d8f"), " Worker amenities like snacks, meals, safety gear, accommodation, medical support etc.", true, true, "Employee Welfare", true, new Guid("b3466e83-7e11-464c-b93a-daf047838b26") }
});
migrationBuilder.AddForeignKey(
name: "FK_PaymentRequests_ExpensesTypeMaster_ExpenseCategoryId",
table: "PaymentRequests",
column: "ExpenseCategoryId",
principalTable: "ExpensesTypeMaster",
principalColumn: "Id");
}
}
}

View File

@ -2137,6 +2137,121 @@ namespace Marco.Pms.DataAccess.Migrations
b.ToTable("BillAttachments");
});
modelBuilder.Entity("Marco.Pms.Model.Expenses.ExpenseCategoryMaster", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("char(36)");
b.Property<string>("Description")
.IsRequired()
.HasColumnType("longtext");
b.Property<bool>("IsActive")
.HasColumnType("tinyint(1)");
b.Property<bool>("IsAttachmentRequried")
.HasColumnType("tinyint(1)");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.Property<bool>("NoOfPersonsRequired")
.HasColumnType("tinyint(1)");
b.Property<Guid>("TenantId")
.HasColumnType("char(36)");
b.HasKey("Id");
b.HasIndex("TenantId");
b.ToTable("ExpenseCategoryMasters");
b.HasData(
new
{
Id = new Guid("5e0c6227-d49d-41ff-9f1f-781f0aee2469"),
Description = "Materials, equipment and supplies purchased for site operations.",
IsActive = true,
IsAttachmentRequried = true,
Name = "Procurement",
NoOfPersonsRequired = false,
TenantId = new Guid("b3466e83-7e11-464c-b93a-daf047838b26")
},
new
{
Id = new Guid("2de53163-0dbd-404b-8e60-1b02e6b4886a"),
Description = "Vehicle fuel, logistics services and delivery of goods or personnel.",
IsActive = true,
IsAttachmentRequried = false,
Name = "Transport",
NoOfPersonsRequired = false,
TenantId = new Guid("b3466e83-7e11-464c-b93a-daf047838b26")
},
new
{
Id = new Guid("dd120bc4-ab0a-45ba-8450-5cd45ff221ca"),
Description = "Delivery of personnel.",
IsActive = true,
IsAttachmentRequried = false,
Name = "Travelling",
NoOfPersonsRequired = true,
TenantId = new Guid("b3466e83-7e11-464c-b93a-daf047838b26")
},
new
{
Id = new Guid("52484820-1b54-4865-8f0f-baa2b1d339b9"),
Description = "Site setup costs including equipment deployment and temporary infrastructure.",
IsActive = true,
IsAttachmentRequried = true,
Name = "Mobilization",
NoOfPersonsRequired = false,
TenantId = new Guid("b3466e83-7e11-464c-b93a-daf047838b26")
},
new
{
Id = new Guid("fc59eb90-98ea-481c-b421-54bfa9e42d8f"),
Description = " Worker amenities like snacks, meals, safety gear, accommodation, medical support etc.",
IsActive = true,
IsAttachmentRequried = true,
Name = "Employee Welfare",
NoOfPersonsRequired = true,
TenantId = new Guid("b3466e83-7e11-464c-b93a-daf047838b26")
},
new
{
Id = new Guid("77013784-9324-4d8b-bd36-d6f928e68942"),
Description = "Machinery servicing, electricity, water, and temporary office needs.",
IsActive = true,
IsAttachmentRequried = true,
Name = "Maintenance & Utilities",
NoOfPersonsRequired = false,
TenantId = new Guid("b3466e83-7e11-464c-b93a-daf047838b26")
},
new
{
Id = new Guid("1e2d697a-76b4-4be8-bc66-87144561a1a0"),
Description = "Scheduled payments for external services or goods.",
IsActive = true,
IsAttachmentRequried = true,
Name = "Vendor/Supplier Payments",
NoOfPersonsRequired = false,
TenantId = new Guid("b3466e83-7e11-464c-b93a-daf047838b26")
},
new
{
Id = new Guid("4842fa61-64eb-4241-aebd-8282065af9f9"),
Description = "Government fees, insurance, inspections and safety-related expenditures.",
IsActive = true,
IsAttachmentRequried = true,
Name = "Compliance & Safety",
NoOfPersonsRequired = false,
TenantId = new Guid("b3466e83-7e11-464c-b93a-daf047838b26")
});
});
modelBuilder.Entity("Marco.Pms.Model.Expenses.ExpenseLog", b =>
{
b.Property<Guid>("Id")
@ -2436,6 +2551,9 @@ namespace Marco.Pms.DataAccess.Migrations
b.Property<bool>("IsActive")
.HasColumnType("tinyint(1)");
b.Property<bool>("IsAdvancePayment")
.HasColumnType("tinyint(1)");
b.Property<string>("Payee")
.IsRequired()
.HasColumnType("longtext");
@ -2487,6 +2605,32 @@ namespace Marco.Pms.DataAccess.Migrations
b.ToTable("PaymentRequests");
});
modelBuilder.Entity("Marco.Pms.Model.Expenses.PaymentRequestAttachment", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("char(36)");
b.Property<Guid>("DocumentId")
.HasColumnType("char(36)");
b.Property<Guid>("PaymentRequestId")
.HasColumnType("char(36)");
b.Property<Guid>("TenantId")
.HasColumnType("char(36)");
b.HasKey("Id");
b.HasIndex("DocumentId");
b.HasIndex("PaymentRequestId");
b.HasIndex("TenantId");
b.ToTable("PaymentRequestAttachments");
});
modelBuilder.Entity("Marco.Pms.Model.Expenses.RecurringPayment", b =>
{
b.Property<Guid>("Id")
@ -3244,88 +3388,6 @@ namespace Marco.Pms.DataAccess.Migrations
b.HasIndex("TenantId");
b.ToTable("ExpensesTypeMaster");
b.HasData(
new
{
Id = new Guid("5e0c6227-d49d-41ff-9f1f-781f0aee2469"),
Description = "Materials, equipment and supplies purchased for site operations.",
IsActive = true,
IsAttachmentRequried = true,
Name = "Procurement",
NoOfPersonsRequired = false,
TenantId = new Guid("b3466e83-7e11-464c-b93a-daf047838b26")
},
new
{
Id = new Guid("2de53163-0dbd-404b-8e60-1b02e6b4886a"),
Description = "Vehicle fuel, logistics services and delivery of goods or personnel.",
IsActive = true,
IsAttachmentRequried = false,
Name = "Transport",
NoOfPersonsRequired = false,
TenantId = new Guid("b3466e83-7e11-464c-b93a-daf047838b26")
},
new
{
Id = new Guid("dd120bc4-ab0a-45ba-8450-5cd45ff221ca"),
Description = "Delivery of personnel.",
IsActive = true,
IsAttachmentRequried = false,
Name = "Travelling",
NoOfPersonsRequired = true,
TenantId = new Guid("b3466e83-7e11-464c-b93a-daf047838b26")
},
new
{
Id = new Guid("52484820-1b54-4865-8f0f-baa2b1d339b9"),
Description = "Site setup costs including equipment deployment and temporary infrastructure.",
IsActive = true,
IsAttachmentRequried = true,
Name = "Mobilization",
NoOfPersonsRequired = false,
TenantId = new Guid("b3466e83-7e11-464c-b93a-daf047838b26")
},
new
{
Id = new Guid("fc59eb90-98ea-481c-b421-54bfa9e42d8f"),
Description = " Worker amenities like snacks, meals, safety gear, accommodation, medical support etc.",
IsActive = true,
IsAttachmentRequried = true,
Name = "Employee Welfare",
NoOfPersonsRequired = true,
TenantId = new Guid("b3466e83-7e11-464c-b93a-daf047838b26")
},
new
{
Id = new Guid("77013784-9324-4d8b-bd36-d6f928e68942"),
Description = "Machinery servicing, electricity, water, and temporary office needs.",
IsActive = true,
IsAttachmentRequried = true,
Name = "Maintenance & Utilities",
NoOfPersonsRequired = false,
TenantId = new Guid("b3466e83-7e11-464c-b93a-daf047838b26")
},
new
{
Id = new Guid("1e2d697a-76b4-4be8-bc66-87144561a1a0"),
Description = "Scheduled payments for external services or goods.",
IsActive = true,
IsAttachmentRequried = true,
Name = "Vendor/Supplier Payments",
NoOfPersonsRequired = false,
TenantId = new Guid("b3466e83-7e11-464c-b93a-daf047838b26")
},
new
{
Id = new Guid("4842fa61-64eb-4241-aebd-8282065af9f9"),
Description = "Government fees, insurance, inspections and safety-related expenditures.",
IsActive = true,
IsAttachmentRequried = true,
Name = "Compliance & Safety",
NoOfPersonsRequired = false,
TenantId = new Guid("b3466e83-7e11-464c-b93a-daf047838b26")
});
});
modelBuilder.Entity("Marco.Pms.Model.Master.Feature", b =>
@ -6186,6 +6248,17 @@ namespace Marco.Pms.DataAccess.Migrations
b.Navigation("Tenant");
});
modelBuilder.Entity("Marco.Pms.Model.Expenses.ExpenseCategoryMaster", b =>
{
b.HasOne("Marco.Pms.Model.TenantModels.Tenant", "Tenant")
.WithMany()
.HasForeignKey("TenantId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Tenant");
});
modelBuilder.Entity("Marco.Pms.Model.Expenses.ExpenseLog", b =>
{
b.HasOne("Marco.Pms.Model.Expenses.Expenses", "Expense")
@ -6369,7 +6442,7 @@ namespace Marco.Pms.DataAccess.Migrations
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Marco.Pms.Model.Master.ExpensesTypeMaster", "ExpenseCategory")
b.HasOne("Marco.Pms.Model.Expenses.ExpenseCategoryMaster", "ExpenseCategory")
.WithMany()
.HasForeignKey("ExpenseCategoryId");
@ -6414,6 +6487,33 @@ namespace Marco.Pms.DataAccess.Migrations
b.Navigation("UpdatedBy");
});
modelBuilder.Entity("Marco.Pms.Model.Expenses.PaymentRequestAttachment", b =>
{
b.HasOne("Marco.Pms.Model.DocumentManager.Document", "Document")
.WithMany()
.HasForeignKey("DocumentId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Marco.Pms.Model.Expenses.PaymentRequest", "PaymentRequest")
.WithMany()
.HasForeignKey("PaymentRequestId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Marco.Pms.Model.TenantModels.Tenant", "Tenant")
.WithMany()
.HasForeignKey("TenantId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Document");
b.Navigation("PaymentRequest");
b.Navigation("Tenant");
});
modelBuilder.Entity("Marco.Pms.Model.Expenses.RecurringPayment", b =>
{
b.HasOne("Marco.Pms.Model.Employees.Employee", "CreatedBy")

View File

@ -1,4 +1,6 @@
namespace Marco.Pms.Model.Dtos.Expenses
using Marco.Pms.Model.Utilities;
namespace Marco.Pms.Model.Dtos.Expenses
{
public class PaymentRequestDto
{
@ -11,5 +13,7 @@
public required DateTime DueDate { get; set; }
public Guid? ProjectId { get; set; }
public required Guid ExpenseCategoryId { get; set; }
public bool IsAdvancePayment { get; set; }
public List<FileUploadModel>? BillAttachments { get; set; }
}
}

View File

@ -0,0 +1,14 @@
using Marco.Pms.Model.Utilities;
namespace Marco.Pms.Model.Expenses
{
public class ExpenseCategoryMaster : TenantRelation
{
public Guid Id { get; set; }
public string Name { get; set; } = string.Empty;
public bool NoOfPersonsRequired { get; set; }
public string Description { get; set; } = string.Empty;
public bool IsActive { get; set; } = true;
public bool IsAttachmentRequried { get; set; } = true;
}
}

View File

@ -15,6 +15,7 @@ namespace Marco.Pms.Model.Expenses
public string UIDPrefix { get; set; } = default!;
public int UIDPostfix { get; set; }
public string Payee { get; set; } = default!;
public bool IsAdvancePayment { get; set; }
public Guid CurrencyId { get; set; }
[ValidateNever]
@ -36,7 +37,7 @@ namespace Marco.Pms.Model.Expenses
[ValidateNever]
[ForeignKey("ExpenseCategoryId")]
public ExpensesTypeMaster? ExpenseCategory { get; set; }
public ExpenseCategoryMaster? ExpenseCategory { get; set; }
public Guid ExpenseStatusId { get; set; }
[ValidateNever]

View 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.Expenses
{
public class PaymentRequestAttachment : TenantRelation
{
public Guid Id { get; set; }
public Guid PaymentRequestId { get; set; }
[ValidateNever]
[ForeignKey("PaymentRequestId")]
public PaymentRequest? PaymentRequest { get; set; }
public Guid DocumentId { get; set; }
[ValidateNever]
[ForeignKey("DocumentId")]
public Document? Document { get; set; }
}
}

View File

@ -20,6 +20,7 @@ namespace Marco.Pms.Model.ViewModels.Expenses
public RecurringPayment? RecurringPayment { get; set; }
public ExpensesTypeMasterVM? ExpenseCategory { get; set; }
public ExpensesStatusMasterVM? ExpenseStatus { get; set; }
public bool IsAdvancePayment { get; set; }
public bool IsActive { get; set; }
public DateTime CreatedAt { get; set; }
public BasicEmployeeVM? CreatedBy { get; set; }

View File

@ -339,6 +339,7 @@ namespace Marco.Pms.Services.MappingProfiles
opt => opt.MapFrom(src => src.Id ?? Guid.Empty)
);
CreateMap<ExpensesTypeMaster, ExpensesTypeMasterVM>();
CreateMap<ExpenseCategoryMaster, ExpensesTypeMasterVM>();
CreateMap<ExpensesTypeMaster, ExpensesTypeMasterMongoDB>()
.ForMember(

View File

@ -1147,6 +1147,42 @@ namespace Marco.Pms.Services.Service
paymentRequest.CreatedById = loggedInEmployee.Id;
paymentRequest.TenantId = tenantId;
_context.PaymentRequests.Add(paymentRequest);
await _context.SaveChangesAsync();
// 4. Process Attachments
if (model.BillAttachments?.Any() ?? false)
{
var attachments = model.BillAttachments;
// Pre-validate all attachments to fail fast before any uploads.
foreach (var attachment in attachments)
{
if (string.IsNullOrWhiteSpace(attachment.Base64Data) || !_s3Service.IsBase64String(attachment.Base64Data))
{
throw new ArgumentException($"Invalid or missing Base64 data for attachment: {attachment.FileName ?? "N/A"}");
}
}
var batchId = Guid.NewGuid();
// Create a list of tasks to be executed concurrently.
var processingTasks = attachments.Select(attachment =>
ProcessSinglePaymentRequestAttachmentAsync(attachment, paymentRequest, loggedInEmployee.Id, tenantId, batchId)
).ToList();
var results = await Task.WhenAll(processingTasks);
// This part is thread-safe as it runs after all concurrent tasks are complete.
foreach (var (document, paymentRequestAttachment) in results)
{
_context.Documents.Add(document);
_context.PaymentRequestAttachments.Add(paymentRequestAttachment);
}
_logger.LogInfo("{AttachmentCount} attachments processed and staged for saving.", results.Length);
}
await _context.SaveChangesAsync();
var response = _mapper.Map<PaymentRequestVM>(paymentRequest);
response.PaymentRequestUID = $"{paymentRequest.UIDPrefix}/{paymentRequest.UIDPostfix.ToString("D5")}";
response.Currency = currency;
@ -1304,7 +1340,6 @@ namespace Marco.Pms.Services.Service
return expenseList;
}
private async Task<ExpenseDetailsMongoDB> GetAllExpnesRelatedTablesForSingle(Expenses model, Guid tenantId)
{
var statusMappingTask = Task.Run(async () =>
@ -1386,7 +1421,6 @@ namespace Marco.Pms.Services.Service
}
/// <summary>
/// Deserializes the filter string, handling multiple potential formats (e.g., direct JSON vs. escaped JSON string).
/// </summary>
@ -1449,7 +1483,7 @@ namespace Marco.Pms.Services.Service
// Create a list of tasks to be executed concurrently.
var processingTasks = attachments.Select(attachment =>
ProcessSingleAttachmentAsync(attachment, expense, employeeId, tenantId, batchId)
ProcessSingleExpenseAttachmentAsync(attachment, expense, employeeId, tenantId, batchId)
).ToList();
var results = await Task.WhenAll(processingTasks);
@ -1466,7 +1500,7 @@ namespace Marco.Pms.Services.Service
/// <summary>
/// Handles the logic for a single attachment: upload to S3 and create corresponding entities.
/// </summary>
private async Task<(Document document, BillAttachments billAttachment)> ProcessSingleAttachmentAsync(
private async Task<(Document document, BillAttachments billAttachment)> ProcessSingleExpenseAttachmentAsync(
FileUploadModel attachment, Expenses expense, Guid employeeId, Guid tenantId, Guid batchId)
{
var base64Data = attachment.Base64Data!.Contains(',') ? attachment.Base64Data[(attachment.Base64Data.IndexOf(",") + 1)..] : attachment.Base64Data;
@ -1478,7 +1512,7 @@ namespace Marco.Pms.Services.Service
await _s3Service.UploadFileAsync(base64Data, fileType, objectKey);
_logger.LogInfo("Uploaded file to S3 with key: {ObjectKey}", objectKey);
return CreateAttachmentEntities(batchId, expense.Id, employeeId, tenantId, objectKey, attachment);
return CreateExpenseAttachmentEntities(batchId, expense.Id, employeeId, tenantId, objectKey, attachment);
}
@ -1486,7 +1520,7 @@ namespace Marco.Pms.Services.Service
/// A private static helper method to create Document and BillAttachment entities.
/// This remains unchanged as it's a pure data-shaping method.
/// </summary>
private static (Document document, BillAttachments billAttachment) CreateAttachmentEntities(
private static (Document document, BillAttachments billAttachment) CreateExpenseAttachmentEntities(
Guid batchId, Guid expenseId, Guid uploadedById, Guid tenantId, string s3Key, FileUploadModel attachmentDto)
{
var document = new Document
@ -1504,6 +1538,54 @@ namespace Marco.Pms.Services.Service
return (document, billAttachment);
}
/// <summary>
/// Handles the logic for a single attachment: upload to S3 and create corresponding entities.
/// </summary>
private async Task<(Document document, PaymentRequestAttachment billAttachment)> ProcessSinglePaymentRequestAttachmentAsync(
FileUploadModel attachment, PaymentRequest paymentRequest, Guid employeeId, Guid tenantId, Guid batchId)
{
var base64Data = attachment.Base64Data!.Contains(',') ? attachment.Base64Data[(attachment.Base64Data.IndexOf(",") + 1)..] : attachment.Base64Data;
var fileType = _s3Service.GetContentTypeFromBase64(base64Data);
var fileName = _s3Service.GenerateFileName(fileType, paymentRequest.Id, "PaymentRequest");
string objectKey;
if (paymentRequest.ProjectId.HasValue)
{
objectKey = $"tenant-{tenantId}/project-{paymentRequest.ProjectId}/PaymentRequest/{fileName}";
}
else
{
objectKey = $"tenant-{tenantId}/PaymentRequest/{fileName}";
}
// Await the I/O-bound upload operation directly.
await _s3Service.UploadFileAsync(base64Data, fileType, objectKey);
_logger.LogInfo("Uploaded file to S3 with key: {ObjectKey}", objectKey);
return CreatePaymentRequestAttachmentEntities(batchId, paymentRequest.Id, employeeId, tenantId, objectKey, attachment);
}
/// <summary>
/// A private static helper method to create Document and BillAttachment entities.
/// This remains unchanged as it's a pure data-shaping method.
/// </summary>
private static (Document document, PaymentRequestAttachment paymentRequestAttachment) CreatePaymentRequestAttachmentEntities(
Guid batchId, Guid paymentRequestId, Guid uploadedById, Guid tenantId, string s3Key, FileUploadModel attachmentDto)
{
var document = new Document
{
BatchId = batchId,
UploadedById = uploadedById,
FileName = attachmentDto.FileName ?? "",
ContentType = attachmentDto.ContentType ?? "",
S3Key = s3Key,
FileSize = attachmentDto.FileSize,
UploadedAt = DateTime.UtcNow,
TenantId = tenantId
};
var paymentRequestAttachment = new PaymentRequestAttachment { Document = document, PaymentRequestId = paymentRequestId, TenantId = tenantId };
return (document, paymentRequestAttachment);
}
private async Task DeleteAttachemnts(List<Guid> documentIds)
{
var attachmentTask = Task.Run(async () =>