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<Expenses> Expenses { get; set; }
public DbSet<ExpenseLog> ExpenseLogs { get; set; } public DbSet<ExpenseLog> ExpenseLogs { get; set; }
public DbSet<ExpensesTypeMaster> ExpensesTypeMaster { get; set; } public DbSet<ExpensesTypeMaster> ExpensesTypeMaster { get; set; }
public DbSet<ExpenseCategoryMaster> ExpenseCategoryMasters { get; set; }
public DbSet<PaymentModeMatser> PaymentModeMatser { get; set; } public DbSet<PaymentModeMatser> PaymentModeMatser { get; set; }
public DbSet<ExpensesStatusMaster> ExpensesStatusMaster { get; set; } public DbSet<ExpensesStatusMaster> ExpensesStatusMaster { get; set; }
public DbSet<BillAttachments> BillAttachments { 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<StatusPermissionMapping> StatusPermissionMapping { get; set; }
public DbSet<ExpensesStatusMapping> ExpensesStatusMapping { get; set; } public DbSet<ExpensesStatusMapping> ExpensesStatusMapping { get; set; }
public DbSet<PaymentRequest> PaymentRequests { get; set; } public DbSet<PaymentRequest> PaymentRequests { get; set; }
public DbSet<PaymentRequestAttachment> PaymentRequestAttachments { get; set; }
public DbSet<RecurringPayment> RecurringPayments { get; set; } public DbSet<RecurringPayment> RecurringPayments { get; set; }
public DbSet<AdvancePaymentTransaction> AdvancePaymentTransactions { 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") StatusId = Guid.Parse("61578360-3a49-4c34-8604-7b35a3787b95")
}); });
modelBuilder.Entity<ExpensesTypeMaster>().HasData( modelBuilder.Entity<ExpenseCategoryMaster>().HasData(
new ExpensesTypeMaster new ExpenseCategoryMaster
{ {
Id = Guid.Parse("5e0c6227-d49d-41ff-9f1f-781f0aee2469"), Id = Guid.Parse("5e0c6227-d49d-41ff-9f1f-781f0aee2469"),
Name = "Procurement", Name = "Procurement",
@ -685,7 +687,7 @@ namespace Marco.Pms.DataAccess.Data
IsAttachmentRequried = true, IsAttachmentRequried = true,
TenantId = Guid.Parse("b3466e83-7e11-464c-b93a-daf047838b26") TenantId = Guid.Parse("b3466e83-7e11-464c-b93a-daf047838b26")
}, },
new ExpensesTypeMaster new ExpenseCategoryMaster
{ {
Id = Guid.Parse("2de53163-0dbd-404b-8e60-1b02e6b4886a"), Id = Guid.Parse("2de53163-0dbd-404b-8e60-1b02e6b4886a"),
Name = "Transport", Name = "Transport",
@ -695,7 +697,7 @@ namespace Marco.Pms.DataAccess.Data
IsAttachmentRequried = false, IsAttachmentRequried = false,
TenantId = Guid.Parse("b3466e83-7e11-464c-b93a-daf047838b26") TenantId = Guid.Parse("b3466e83-7e11-464c-b93a-daf047838b26")
}, },
new ExpensesTypeMaster new ExpenseCategoryMaster
{ {
Id = Guid.Parse("dd120bc4-ab0a-45ba-8450-5cd45ff221ca"), Id = Guid.Parse("dd120bc4-ab0a-45ba-8450-5cd45ff221ca"),
Name = "Travelling", Name = "Travelling",
@ -705,7 +707,7 @@ namespace Marco.Pms.DataAccess.Data
IsAttachmentRequried = false, IsAttachmentRequried = false,
TenantId = Guid.Parse("b3466e83-7e11-464c-b93a-daf047838b26") TenantId = Guid.Parse("b3466e83-7e11-464c-b93a-daf047838b26")
}, },
new ExpensesTypeMaster new ExpenseCategoryMaster
{ {
Id = Guid.Parse("52484820-1b54-4865-8f0f-baa2b1d339b9"), Id = Guid.Parse("52484820-1b54-4865-8f0f-baa2b1d339b9"),
Name = "Mobilization", Name = "Mobilization",
@ -715,7 +717,7 @@ namespace Marco.Pms.DataAccess.Data
IsAttachmentRequried = true, IsAttachmentRequried = true,
TenantId = Guid.Parse("b3466e83-7e11-464c-b93a-daf047838b26") TenantId = Guid.Parse("b3466e83-7e11-464c-b93a-daf047838b26")
}, },
new ExpensesTypeMaster new ExpenseCategoryMaster
{ {
Id = Guid.Parse("fc59eb90-98ea-481c-b421-54bfa9e42d8f"), Id = Guid.Parse("fc59eb90-98ea-481c-b421-54bfa9e42d8f"),
Name = "Employee Welfare", Name = "Employee Welfare",
@ -725,7 +727,7 @@ namespace Marco.Pms.DataAccess.Data
IsAttachmentRequried = true, IsAttachmentRequried = true,
TenantId = Guid.Parse("b3466e83-7e11-464c-b93a-daf047838b26") TenantId = Guid.Parse("b3466e83-7e11-464c-b93a-daf047838b26")
}, },
new ExpensesTypeMaster new ExpenseCategoryMaster
{ {
Id = Guid.Parse("77013784-9324-4d8b-bd36-d6f928e68942"), Id = Guid.Parse("77013784-9324-4d8b-bd36-d6f928e68942"),
Name = "Maintenance & Utilities", Name = "Maintenance & Utilities",
@ -735,7 +737,7 @@ namespace Marco.Pms.DataAccess.Data
IsAttachmentRequried = true, IsAttachmentRequried = true,
TenantId = Guid.Parse("b3466e83-7e11-464c-b93a-daf047838b26") TenantId = Guid.Parse("b3466e83-7e11-464c-b93a-daf047838b26")
}, },
new ExpensesTypeMaster new ExpenseCategoryMaster
{ {
Id = Guid.Parse("1e2d697a-76b4-4be8-bc66-87144561a1a0"), Id = Guid.Parse("1e2d697a-76b4-4be8-bc66-87144561a1a0"),
Name = "Vendor/Supplier Payments", Name = "Vendor/Supplier Payments",
@ -745,7 +747,7 @@ namespace Marco.Pms.DataAccess.Data
IsAttachmentRequried = true, IsAttachmentRequried = true,
TenantId = Guid.Parse("b3466e83-7e11-464c-b93a-daf047838b26") TenantId = Guid.Parse("b3466e83-7e11-464c-b93a-daf047838b26")
}, },
new ExpensesTypeMaster new ExpenseCategoryMaster
{ {
Id = Guid.Parse("4842fa61-64eb-4241-aebd-8282065af9f9"), Id = Guid.Parse("4842fa61-64eb-4241-aebd-8282065af9f9"),
Name = "Compliance & Safety", 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"); 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 => modelBuilder.Entity("Marco.Pms.Model.Expenses.ExpenseLog", b =>
{ {
b.Property<Guid>("Id") b.Property<Guid>("Id")
@ -2436,6 +2551,9 @@ namespace Marco.Pms.DataAccess.Migrations
b.Property<bool>("IsActive") b.Property<bool>("IsActive")
.HasColumnType("tinyint(1)"); .HasColumnType("tinyint(1)");
b.Property<bool>("IsAdvancePayment")
.HasColumnType("tinyint(1)");
b.Property<string>("Payee") b.Property<string>("Payee")
.IsRequired() .IsRequired()
.HasColumnType("longtext"); .HasColumnType("longtext");
@ -2487,6 +2605,32 @@ namespace Marco.Pms.DataAccess.Migrations
b.ToTable("PaymentRequests"); 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 => modelBuilder.Entity("Marco.Pms.Model.Expenses.RecurringPayment", b =>
{ {
b.Property<Guid>("Id") b.Property<Guid>("Id")
@ -3244,88 +3388,6 @@ namespace Marco.Pms.DataAccess.Migrations
b.HasIndex("TenantId"); b.HasIndex("TenantId");
b.ToTable("ExpensesTypeMaster"); 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 => modelBuilder.Entity("Marco.Pms.Model.Master.Feature", b =>
@ -6186,6 +6248,17 @@ namespace Marco.Pms.DataAccess.Migrations
b.Navigation("Tenant"); 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 => modelBuilder.Entity("Marco.Pms.Model.Expenses.ExpenseLog", b =>
{ {
b.HasOne("Marco.Pms.Model.Expenses.Expenses", "Expense") b.HasOne("Marco.Pms.Model.Expenses.Expenses", "Expense")
@ -6369,7 +6442,7 @@ namespace Marco.Pms.DataAccess.Migrations
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
b.HasOne("Marco.Pms.Model.Master.ExpensesTypeMaster", "ExpenseCategory") b.HasOne("Marco.Pms.Model.Expenses.ExpenseCategoryMaster", "ExpenseCategory")
.WithMany() .WithMany()
.HasForeignKey("ExpenseCategoryId"); .HasForeignKey("ExpenseCategoryId");
@ -6414,6 +6487,33 @@ namespace Marco.Pms.DataAccess.Migrations
b.Navigation("UpdatedBy"); 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 => modelBuilder.Entity("Marco.Pms.Model.Expenses.RecurringPayment", b =>
{ {
b.HasOne("Marco.Pms.Model.Employees.Employee", "CreatedBy") 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 public class PaymentRequestDto
{ {
@ -11,5 +13,7 @@
public required DateTime DueDate { get; set; } public required DateTime DueDate { get; set; }
public Guid? ProjectId { get; set; } public Guid? ProjectId { get; set; }
public required Guid ExpenseCategoryId { 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 string UIDPrefix { get; set; } = default!;
public int UIDPostfix { get; set; } public int UIDPostfix { get; set; }
public string Payee { get; set; } = default!; public string Payee { get; set; } = default!;
public bool IsAdvancePayment { get; set; }
public Guid CurrencyId { get; set; } public Guid CurrencyId { get; set; }
[ValidateNever] [ValidateNever]
@ -36,7 +37,7 @@ namespace Marco.Pms.Model.Expenses
[ValidateNever] [ValidateNever]
[ForeignKey("ExpenseCategoryId")] [ForeignKey("ExpenseCategoryId")]
public ExpensesTypeMaster? ExpenseCategory { get; set; } public ExpenseCategoryMaster? ExpenseCategory { get; set; }
public Guid ExpenseStatusId { get; set; } public Guid ExpenseStatusId { get; set; }
[ValidateNever] [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 RecurringPayment? RecurringPayment { get; set; }
public ExpensesTypeMasterVM? ExpenseCategory { get; set; } public ExpensesTypeMasterVM? ExpenseCategory { get; set; }
public ExpensesStatusMasterVM? ExpenseStatus { get; set; } public ExpensesStatusMasterVM? ExpenseStatus { get; set; }
public bool IsAdvancePayment { get; set; }
public bool IsActive { get; set; } public bool IsActive { get; set; }
public DateTime CreatedAt { get; set; } public DateTime CreatedAt { get; set; }
public BasicEmployeeVM? CreatedBy { 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) opt => opt.MapFrom(src => src.Id ?? Guid.Empty)
); );
CreateMap<ExpensesTypeMaster, ExpensesTypeMasterVM>(); CreateMap<ExpensesTypeMaster, ExpensesTypeMasterVM>();
CreateMap<ExpenseCategoryMaster, ExpensesTypeMasterVM>();
CreateMap<ExpensesTypeMaster, ExpensesTypeMasterMongoDB>() CreateMap<ExpensesTypeMaster, ExpensesTypeMasterMongoDB>()
.ForMember( .ForMember(

View File

@ -1147,6 +1147,42 @@ namespace Marco.Pms.Services.Service
paymentRequest.CreatedById = loggedInEmployee.Id; paymentRequest.CreatedById = loggedInEmployee.Id;
paymentRequest.TenantId = tenantId; 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); var response = _mapper.Map<PaymentRequestVM>(paymentRequest);
response.PaymentRequestUID = $"{paymentRequest.UIDPrefix}/{paymentRequest.UIDPostfix.ToString("D5")}"; response.PaymentRequestUID = $"{paymentRequest.UIDPrefix}/{paymentRequest.UIDPostfix.ToString("D5")}";
response.Currency = currency; response.Currency = currency;
@ -1304,7 +1340,6 @@ namespace Marco.Pms.Services.Service
return expenseList; return expenseList;
} }
private async Task<ExpenseDetailsMongoDB> GetAllExpnesRelatedTablesForSingle(Expenses model, Guid tenantId) private async Task<ExpenseDetailsMongoDB> GetAllExpnesRelatedTablesForSingle(Expenses model, Guid tenantId)
{ {
var statusMappingTask = Task.Run(async () => var statusMappingTask = Task.Run(async () =>
@ -1386,7 +1421,6 @@ namespace Marco.Pms.Services.Service
} }
/// <summary> /// <summary>
/// Deserializes the filter string, handling multiple potential formats (e.g., direct JSON vs. escaped JSON string). /// Deserializes the filter string, handling multiple potential formats (e.g., direct JSON vs. escaped JSON string).
/// </summary> /// </summary>
@ -1449,7 +1483,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 =>
ProcessSingleAttachmentAsync(attachment, expense, employeeId, tenantId, batchId) ProcessSingleExpenseAttachmentAsync(attachment, expense, employeeId, tenantId, batchId)
).ToList(); ).ToList();
var results = await Task.WhenAll(processingTasks); var results = await Task.WhenAll(processingTasks);
@ -1466,7 +1500,7 @@ namespace Marco.Pms.Services.Service
/// <summary> /// <summary>
/// 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)> ProcessSingleAttachmentAsync( private async Task<(Document document, BillAttachments billAttachment)> ProcessSingleExpenseAttachmentAsync(
FileUploadModel attachment, Expenses expense, Guid employeeId, Guid tenantId, Guid batchId) FileUploadModel attachment, Expenses expense, Guid employeeId, 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;
@ -1478,7 +1512,7 @@ namespace Marco.Pms.Services.Service
await _s3Service.UploadFileAsync(base64Data, fileType, objectKey); await _s3Service.UploadFileAsync(base64Data, fileType, objectKey);
_logger.LogInfo("Uploaded file to S3 with key: {ObjectKey}", 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. /// A private static helper method to create Document and BillAttachment entities.
/// This remains unchanged as it's a pure data-shaping method. /// This remains unchanged as it's a pure data-shaping method.
/// </summary> /// </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) Guid batchId, Guid expenseId, Guid uploadedById, Guid tenantId, string s3Key, FileUploadModel attachmentDto)
{ {
var document = new Document var document = new Document
@ -1504,6 +1538,54 @@ namespace Marco.Pms.Services.Service
return (document, billAttachment); 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) private async Task DeleteAttachemnts(List<Guid> documentIds)
{ {
var attachmentTask = Task.Run(async () => var attachmentTask = Task.Run(async () =>