resolver conflict and merged- AdvancePaymentTranaction

This commit is contained in:
pramod.mahajan 2025-11-01 16:43:24 +05:30
commit bdeeb673c7
20 changed files with 22689 additions and 121 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,29 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Marco.Pms.DataAccess.Migrations
{
/// <inheritdoc />
public partial class Added_CurrentBalance_In_AdvancePayment_Tables : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<double>(
name: "CurrentBalance",
table: "AdvancePaymentTransactions",
type: "double",
nullable: false,
defaultValue: 0.0);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "CurrentBalance",
table: "AdvancePaymentTransactions");
}
}
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,145 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Marco.Pms.DataAccess.Migrations
{
/// <inheritdoc />
public partial class Made_UpdatedBy_And_UpdateAt_As_Nullable : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_PaymentRequests_Employees_UpdatedById",
table: "PaymentRequests");
migrationBuilder.DropForeignKey(
name: "FK_RecurringPayments_Employees_UpdatedById",
table: "RecurringPayments");
migrationBuilder.AlterColumn<Guid>(
name: "UpdatedById",
table: "RecurringPayments",
type: "char(36)",
nullable: true,
collation: "ascii_general_ci",
oldClrType: typeof(Guid),
oldType: "char(36)")
.OldAnnotation("Relational:Collation", "ascii_general_ci");
migrationBuilder.AlterColumn<DateTime>(
name: "UpdatedAt",
table: "RecurringPayments",
type: "datetime(6)",
nullable: true,
oldClrType: typeof(DateTime),
oldType: "datetime(6)");
migrationBuilder.AlterColumn<Guid>(
name: "UpdatedById",
table: "PaymentRequests",
type: "char(36)",
nullable: true,
collation: "ascii_general_ci",
oldClrType: typeof(Guid),
oldType: "char(36)")
.OldAnnotation("Relational:Collation", "ascii_general_ci");
migrationBuilder.AlterColumn<DateTime>(
name: "UpdatedAt",
table: "PaymentRequests",
type: "datetime(6)",
nullable: true,
oldClrType: typeof(DateTime),
oldType: "datetime(6)");
migrationBuilder.AddForeignKey(
name: "FK_PaymentRequests_Employees_UpdatedById",
table: "PaymentRequests",
column: "UpdatedById",
principalTable: "Employees",
principalColumn: "Id");
migrationBuilder.AddForeignKey(
name: "FK_RecurringPayments_Employees_UpdatedById",
table: "RecurringPayments",
column: "UpdatedById",
principalTable: "Employees",
principalColumn: "Id");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_PaymentRequests_Employees_UpdatedById",
table: "PaymentRequests");
migrationBuilder.DropForeignKey(
name: "FK_RecurringPayments_Employees_UpdatedById",
table: "RecurringPayments");
migrationBuilder.AlterColumn<Guid>(
name: "UpdatedById",
table: "RecurringPayments",
type: "char(36)",
nullable: false,
defaultValue: new Guid("00000000-0000-0000-0000-000000000000"),
collation: "ascii_general_ci",
oldClrType: typeof(Guid),
oldType: "char(36)",
oldNullable: true)
.OldAnnotation("Relational:Collation", "ascii_general_ci");
migrationBuilder.AlterColumn<DateTime>(
name: "UpdatedAt",
table: "RecurringPayments",
type: "datetime(6)",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified),
oldClrType: typeof(DateTime),
oldType: "datetime(6)",
oldNullable: true);
migrationBuilder.AlterColumn<Guid>(
name: "UpdatedById",
table: "PaymentRequests",
type: "char(36)",
nullable: false,
defaultValue: new Guid("00000000-0000-0000-0000-000000000000"),
collation: "ascii_general_ci",
oldClrType: typeof(Guid),
oldType: "char(36)",
oldNullable: true)
.OldAnnotation("Relational:Collation", "ascii_general_ci");
migrationBuilder.AlterColumn<DateTime>(
name: "UpdatedAt",
table: "PaymentRequests",
type: "datetime(6)",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified),
oldClrType: typeof(DateTime),
oldType: "datetime(6)",
oldNullable: true);
migrationBuilder.AddForeignKey(
name: "FK_PaymentRequests_Employees_UpdatedById",
table: "PaymentRequests",
column: "UpdatedById",
principalTable: "Employees",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "FK_RecurringPayments_Employees_UpdatedById",
table: "RecurringPayments",
column: "UpdatedById",
principalTable: "Employees",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
}
}
}

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

@ -2081,6 +2081,9 @@ namespace Marco.Pms.DataAccess.Migrations
b.Property<Guid>("CreatedById") b.Property<Guid>("CreatedById")
.HasColumnType("char(36)"); .HasColumnType("char(36)");
b.Property<double>("CurrentBalance")
.HasColumnType("double");
b.Property<Guid>("EmployeeId") b.Property<Guid>("EmployeeId")
.HasColumnType("char(36)"); .HasColumnType("char(36)");
@ -2134,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")
@ -2433,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");
@ -2457,10 +2578,10 @@ namespace Marco.Pms.DataAccess.Migrations
.IsRequired() .IsRequired()
.HasColumnType("longtext"); .HasColumnType("longtext");
b.Property<DateTime>("UpdatedAt") b.Property<DateTime?>("UpdatedAt")
.HasColumnType("datetime(6)"); .HasColumnType("datetime(6)");
b.Property<Guid>("UpdatedById") b.Property<Guid?>("UpdatedById")
.HasColumnType("char(36)"); .HasColumnType("char(36)");
b.HasKey("Id"); b.HasKey("Id");
@ -2484,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")
@ -2561,10 +2708,10 @@ namespace Marco.Pms.DataAccess.Migrations
.IsRequired() .IsRequired()
.HasColumnType("longtext"); .HasColumnType("longtext");
b.Property<DateTime>("UpdatedAt") b.Property<DateTime?>("UpdatedAt")
.HasColumnType("datetime(6)"); .HasColumnType("datetime(6)");
b.Property<Guid>("UpdatedById") b.Property<Guid?>("UpdatedById")
.HasColumnType("char(36)"); .HasColumnType("char(36)");
b.HasKey("Id"); b.HasKey("Id");
@ -3241,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 =>
@ -6183,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")
@ -6366,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");
@ -6392,9 +6468,7 @@ namespace Marco.Pms.DataAccess.Migrations
b.HasOne("Marco.Pms.Model.Employees.Employee", "UpdatedBy") b.HasOne("Marco.Pms.Model.Employees.Employee", "UpdatedBy")
.WithMany() .WithMany()
.HasForeignKey("UpdatedById") .HasForeignKey("UpdatedById");
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("CreatedBy"); b.Navigation("CreatedBy");
@ -6413,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")
@ -6455,9 +6556,7 @@ namespace Marco.Pms.DataAccess.Migrations
b.HasOne("Marco.Pms.Model.Employees.Employee", "UpdatedBy") b.HasOne("Marco.Pms.Model.Employees.Employee", "UpdatedBy")
.WithMany() .WithMany()
.HasForeignKey("UpdatedById") .HasForeignKey("UpdatedById");
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("CreatedBy"); b.Navigation("CreatedBy");

View File

@ -0,0 +1,19 @@
using Marco.Pms.Model.Utilities;
namespace Marco.Pms.Model.Dtos.Expenses
{
public class PaymentRequestDto
{
public Guid? Id { get; set; }
public required string Title { get; set; }
public required string Description { get; set; }
public required string Payee { get; set; }
public required Guid CurrencyId { get; set; }
public required double Amount { get; set; }
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

@ -16,6 +16,7 @@ namespace Marco.Pms.Model.Expenses
[ForeignKey("EmployeeId")] [ForeignKey("EmployeeId")]
public Employee? Employee { get; set; } public Employee? Employee { get; set; }
public double Amount { get; set; } public double Amount { get; set; }
public double CurrentBalance { get; set; }
public DateTime CreatedAt { get; set; } public DateTime CreatedAt { get; set; }
public Guid CreatedById { get; set; } public Guid CreatedById { 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]
@ -49,8 +50,8 @@ namespace Marco.Pms.Model.Expenses
[ValidateNever] [ValidateNever]
[ForeignKey("CreatedById")] [ForeignKey("CreatedById")]
public Employee? CreatedBy { get; set; } public Employee? CreatedBy { get; set; }
public DateTime UpdatedAt { get; set; } public DateTime? UpdatedAt { get; set; }
public Guid UpdatedById { get; set; } public Guid? UpdatedById { get; set; }
[ValidateNever] [ValidateNever]
[ForeignKey("UpdatedById")] [ForeignKey("UpdatedById")]

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

@ -56,8 +56,8 @@ namespace Marco.Pms.Model.Expenses
[ValidateNever] [ValidateNever]
[ForeignKey("CreatedById")] [ForeignKey("CreatedById")]
public Employee? CreatedBy { get; set; } public Employee? CreatedBy { get; set; }
public DateTime UpdatedAt { get; set; } public DateTime? UpdatedAt { get; set; }
public Guid UpdatedById { get; set; } public Guid? UpdatedById { get; set; }
[ValidateNever] [ValidateNever]
[ForeignKey("UpdatedById")] [ForeignKey("UpdatedById")]

View File

@ -0,0 +1,31 @@
using Marco.Pms.Model.Expenses;
using Marco.Pms.Model.Master;
using Marco.Pms.Model.ViewModels.Activities;
using Marco.Pms.Model.ViewModels.Master;
using Marco.Pms.Model.ViewModels.Projects;
namespace Marco.Pms.Model.ViewModels.Expenses
{
public class PaymentRequestVM
{
public Guid Id { get; set; }
public string? Title { get; set; }
public string? Description { get; set; }
public string? PaymentRequestUID { get; set; }
public string? Payee { get; set; }
public CurrencyMaster? Currency { get; set; }
public double Amount { get; set; }
public DateTime DueDate { get; set; }
public BasicProjectVM? Project { get; set; }
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; }
public DateTime? UpdatedAt { get; set; }
public Guid? UpdatedById { get; set; }
public BasicEmployeeVM? UpdatedBy { get; set; }
}
}

View File

@ -29,6 +29,7 @@ namespace Marco.Pms.Services.Controllers
tenantId = userHelper.GetTenantId(); tenantId = userHelper.GetTenantId();
} }
#region =================================================================== Expense Functions ===================================================================
/// <summary> /// <summary>
/// Retrieves a paginated list of expenses based on user permissions and optional filters. /// Retrieves a paginated list of expenses based on user permissions and optional filters.
@ -131,5 +132,21 @@ namespace Marco.Pms.Services.Controllers
return StatusCode(response.StatusCode, response); return StatusCode(response.StatusCode, response);
} }
#endregion
#region =================================================================== Payment Request Functions ===================================================================
[HttpPost("payment-request/create")]
public async Task<IActionResult> CreatePaymentRequest([FromBody] PaymentRequestDto model)
{
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
var response = await _expensesService.CreatePaymentRequestAsync(model, loggedInEmployee, tenantId);
if (response.Success)
{
var notification = new { LoggedInUserId = loggedInEmployee.Id, Keyword = "Payment_Request", Response = response.Data };
await _signalR.SendNotificationAsync(notification);
}
return StatusCode(response.StatusCode, response);
}
#endregion
} }
} }

View File

@ -257,6 +257,10 @@ namespace Marco.Pms.Services.MappingProfiles
.ForMember( .ForMember(
dest => dest.Id, dest => dest.Id,
opt => opt.MapFrom(src => Guid.Parse(src.Id))); opt => opt.MapFrom(src => Guid.Parse(src.Id)));
#region ======================================================= Payment Request =======================================================
CreateMap<PaymentRequestDto, PaymentRequest>();
CreateMap<PaymentRequest, PaymentRequestVM>();
#endregion
CreateMap<AdvancePaymentTransaction, AdvancePaymentTransactionVM>(); CreateMap<AdvancePaymentTransaction, AdvancePaymentTransactionVM>();
@ -336,7 +340,22 @@ namespace Marco.Pms.Services.MappingProfiles
// Explicitly and safely convert nullable Guid to non-nullable Guid // Explicitly and safely convert nullable Guid to non-nullable Guid
opt => opt.MapFrom(src => src.Id ?? Guid.Empty) opt => opt.MapFrom(src => src.Id ?? Guid.Empty)
); );
CreateMap<ExpensesTypeMasterDto, ExpenseCategoryMaster>()
.ForMember(
dest => dest.Id,
// Explicitly and safely convert nullable Guid to non-nullable Guid
opt => opt.MapFrom(src => src.Id ?? Guid.Empty)
);
CreateMap<ExpensesTypeMaster, ExpensesTypeMasterVM>(); CreateMap<ExpensesTypeMaster, ExpensesTypeMasterVM>();
CreateMap<ExpenseCategoryMaster, ExpensesTypeMasterVM>();
CreateMap<ExpenseCategoryMaster, ExpensesTypeMasterMongoDB>()
.ForMember(
dest => dest.Id,
opt => opt.MapFrom(src => src.Id.ToString()))
.ForMember(
dest => dest.TenantId,
opt => opt.MapFrom(src => src.TenantId.ToString()));
CreateMap<ExpensesTypeMaster, ExpensesTypeMasterMongoDB>() CreateMap<ExpensesTypeMaster, ExpensesTypeMasterMongoDB>()
.ForMember( .ForMember(

View File

@ -66,6 +66,8 @@ namespace Marco.Pms.Services.Service
_mapper = mapper; _mapper = mapper;
} }
#region =================================================================== Expense Functions ===================================================================
#region =================================================================== Get Functions =================================================================== #region =================================================================== Get Functions ===================================================================
/// <summary> /// <summary>
@ -1145,6 +1147,159 @@ namespace Marco.Pms.Services.Service
#endregion #endregion
#endregion
#region =================================================================== Payment Request Functions ===================================================================
public async Task<ApiResponse<object>> CreatePaymentRequestAsync(PaymentRequestDto model, Employee loggedInEmployee, Guid tenantId)
{
_logger.LogInfo("Start CreatePaymentRequestAsync for EmployeeId: {EmployeeId} TenantId: {TenantId}", loggedInEmployee.Id, tenantId);
string uIDPrefix = $"PY/{DateTime.Now:MMyy}";
try
{
// Execute database lookups concurrently
var expenseCategoryTask = Task.Run(async () =>
{
await using var context = await _dbContextFactory.CreateDbContextAsync();
return await context.ExpenseCategoryMasters.FirstOrDefaultAsync(et => et.Id == model.ExpenseCategoryId && et.IsActive);
});
var currencyTask = Task.Run(async () =>
{
await using var context = await _dbContextFactory.CreateDbContextAsync();
return await context.CurrencyMaster.FirstOrDefaultAsync(c => c.Id == model.CurrencyId);
});
var expenseStatusTask = Task.Run(async () =>
{
await using var context = await _dbContextFactory.CreateDbContextAsync();
return await context.ExpensesStatusMaster.FirstOrDefaultAsync(es => es.Id == Draft && es.IsActive);
});
var projectTask = Task.Run(async () =>
{
await using var context = await _dbContextFactory.CreateDbContextAsync();
return await context.Projects.FirstOrDefaultAsync(P => model.ProjectId.HasValue && P.Id == model.ProjectId.Value);
});
await Task.WhenAll(expenseCategoryTask, currencyTask, expenseStatusTask, projectTask);
var expenseCategory = await expenseCategoryTask;
if (expenseCategory == null)
{
_logger.LogWarning("Expense Category not found with Id: {ExpenseCategoryId}", model.ExpenseCategoryId);
return ApiResponse<object>.ErrorResponse("Expense Category not found.", "Expense Category not found.", 404);
}
var currency = await currencyTask;
if (currency == null)
{
_logger.LogWarning("Currency not found with Id: {CurrencyId}", model.CurrencyId);
return ApiResponse<object>.ErrorResponse("Currency not found.", "Currency not found.", 404);
}
var expenseStatus = await expenseStatusTask;
if (expenseStatus == null)
{
_logger.LogWarning("Expense Status not found for Draft.");
return ApiResponse<object>.ErrorResponse("Expense Status (Draft) not found.", "Expense Status not found.", 404);
}
var project = await projectTask;
// Project is optional so no error if not found
// Generate unique UID postfix based on existing requests for the current prefix
var lastPR = await _context.PaymentRequests.Where(pr => pr.UIDPrefix == uIDPrefix)
.OrderByDescending(pr => pr.UIDPostfix)
.FirstOrDefaultAsync();
int uIDPostfix = lastPR == null ? 1 : (lastPR.UIDPostfix + 1);
// Map DTO to PaymentRequest entity and set additional mandatory fields
var paymentRequest = _mapper.Map<PaymentRequest>(model);
paymentRequest.ExpenseStatusId = Draft;
paymentRequest.UIDPrefix = uIDPrefix;
paymentRequest.UIDPostfix = uIDPostfix;
paymentRequest.IsActive = true;
paymentRequest.CreatedAt = DateTime.UtcNow;
paymentRequest.CreatedById = loggedInEmployee.Id;
paymentRequest.TenantId = tenantId;
await _context.PaymentRequests.AddAsync(paymentRequest);
await _context.SaveChangesAsync();
// Process bill attachments if any
if (model.BillAttachments?.Any() == true)
{
_logger.LogInfo("Processing {AttachmentCount} attachments for PaymentRequest Id: {PaymentRequestId}",
model.BillAttachments.Count, paymentRequest.Id);
// Validate base64 attachments data before processing
foreach (var attachment in model.BillAttachments)
{
if (string.IsNullOrWhiteSpace(attachment.Base64Data) || !_s3Service.IsBase64String(attachment.Base64Data))
{
_logger.LogWarning("Invalid or missing Base64 data for attachment: {FileName}", attachment.FileName ?? "N/A");
throw new ArgumentException($"Invalid or missing Base64 data for attachment: {attachment.FileName ?? "N/A"}");
}
}
var batchId = Guid.NewGuid();
// Process all attachments concurrently
var processingTasks = model.BillAttachments.Select(attachment =>
ProcessSinglePaymentRequestAttachmentAsync(attachment, paymentRequest, loggedInEmployee.Id, tenantId, batchId)
).ToList();
var results = await Task.WhenAll(processingTasks);
// Add documents and payment request attachments after concurrent processing
foreach (var (document, paymentRequestAttachment) in results)
{
_context.Documents.Add(document);
_context.PaymentRequestAttachments.Add(paymentRequestAttachment);
}
await _context.SaveChangesAsync();
_logger.LogInfo("{AttachmentCount} attachments processed and saved for PaymentRequest Id: {PaymentRequestId}",
results.Length, paymentRequest.Id);
}
// Prepare response VM
var response = _mapper.Map<PaymentRequestVM>(paymentRequest);
response.PaymentRequestUID = $"{paymentRequest.UIDPrefix}/{paymentRequest.UIDPostfix:D5}";
response.Currency = currency;
response.ExpenseCategory = _mapper.Map<ExpensesTypeMasterVM>(expenseCategory);
response.ExpenseStatus = _mapper.Map<ExpensesStatusMasterVM>(expenseStatus);
response.Project = _mapper.Map<BasicProjectVM>(project);
response.CreatedBy = _mapper.Map<BasicEmployeeVM>(loggedInEmployee);
_logger.LogInfo("Payment request created successfully with UID: {PaymentRequestUID}", response.PaymentRequestUID);
return ApiResponse<object>.SuccessResponse(response, "Created the Payment Request Successfully.", 201);
}
catch (ArgumentException ex)
{
_logger.LogError(ex, "Argument error in CreatePaymentRequestAsync: {Message}", ex.Message);
return ApiResponse<object>.ErrorResponse(ex.Message, "Invalid data.", 400);
}
catch (Exception ex)
{
_logger.LogError(ex, "Unexpected error in CreatePaymentRequestAsync: {Message}", ex.Message);
return ApiResponse<object>.ErrorResponse("An error occurred while creating the payment request.", ex.Message, 500);
}
finally
{
_logger.LogInfo("End CreatePaymentRequestAsync for EmployeeId: {EmployeeId}", loggedInEmployee.Id);
}
}
#endregion
#region =================================================================== Payment Request Functions ===================================================================
#endregion
#region =================================================================== Payment Request Functions ===================================================================
#endregion
#region =================================================================== Helper Functions =================================================================== #region =================================================================== Helper Functions ===================================================================
private int ExtractNumber(string id) private int ExtractNumber(string id)
@ -1284,7 +1439,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 () =>
@ -1366,7 +1520,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>
@ -1429,7 +1582,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);
@ -1446,7 +1599,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;
@ -1458,7 +1611,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);
} }
@ -1466,7 +1619,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
@ -1484,6 +1637,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 () =>

View File

@ -10,6 +10,7 @@ using Marco.Pms.Model.Dtos.DocumentManager;
using Marco.Pms.Model.Dtos.Master; using Marco.Pms.Model.Dtos.Master;
using Marco.Pms.Model.Employees; using Marco.Pms.Model.Employees;
using Marco.Pms.Model.Entitlements; using Marco.Pms.Model.Entitlements;
using Marco.Pms.Model.Expenses;
using Marco.Pms.Model.Master; using Marco.Pms.Model.Master;
using Marco.Pms.Model.MongoDBModels.Utility; using Marco.Pms.Model.MongoDBModels.Utility;
using Marco.Pms.Model.Utilities; using Marco.Pms.Model.Utilities;
@ -2051,7 +2052,7 @@ namespace Marco.Pms.Services.Service
try try
{ {
// Featching the list of Expenses Type. // Featching the list of Expenses Type.
var typeList = await _context.ExpensesTypeMaster.Where(et => et.TenantId == tenantId && et.IsActive == isActive).ToListAsync(); var typeList = await _context.ExpenseCategoryMasters.Where(et => et.TenantId == tenantId && et.IsActive == isActive).ToListAsync();
var response = _mapper.Map<List<ExpensesTypeMasterVM>>(typeList); var response = _mapper.Map<List<ExpensesTypeMasterVM>>(typeList);
_logger.LogInfo("{Count} records of expense type have been fetched successfully by employee {EmployeeId}", response.Count, loggedInEmployee.Id); _logger.LogInfo("{Count} records of expense type have been fetched successfully by employee {EmployeeId}", response.Count, loggedInEmployee.Id);
@ -2074,10 +2075,10 @@ namespace Marco.Pms.Services.Service
_logger.LogWarning("Access DENIED for employee {EmployeeId} for managing EXPANSES TYPE MASTER.", loggedInEmployee.Id); _logger.LogWarning("Access DENIED for employee {EmployeeId} for managing EXPANSES TYPE MASTER.", loggedInEmployee.Id);
return ApiResponse<object>.ErrorResponse("Access Denied.", "You do not have permission to Manage masters", 403); return ApiResponse<object>.ErrorResponse("Access Denied.", "You do not have permission to Manage masters", 403);
} }
var expensesType = _mapper.Map<ExpensesTypeMaster>(model); var expensesType = _mapper.Map<ExpenseCategoryMaster>(model);
expensesType.TenantId = tenantId; expensesType.TenantId = tenantId;
_context.ExpensesTypeMaster.Add(expensesType); _context.ExpenseCategoryMasters.Add(expensesType);
await _context.SaveChangesAsync(); await _context.SaveChangesAsync();
_logger.LogInfo("New Expense Type {ExpensesTypeId} was added by employee {EmployeeId}", expensesType.Id, loggedInEmployee.Id); _logger.LogInfo("New Expense Type {ExpensesTypeId} was added by employee {EmployeeId}", expensesType.Id, loggedInEmployee.Id);
@ -2115,7 +2116,7 @@ namespace Marco.Pms.Services.Service
return ApiResponse<object>.ErrorResponse("Invalid Data", "User has send invalid payload", 400); return ApiResponse<object>.ErrorResponse("Invalid Data", "User has send invalid payload", 400);
} }
var expensesType = await _context.ExpensesTypeMaster.AsNoTracking().FirstOrDefaultAsync(et => et.Id == model.Id.Value && et.TenantId == tenantId); var expensesType = await _context.ExpenseCategoryMasters.AsNoTracking().FirstOrDefaultAsync(et => et.Id == model.Id.Value && et.TenantId == tenantId);
// Checking if expense type exists // Checking if expense type exists
if (expensesType == null) if (expensesType == null)
@ -2130,7 +2131,7 @@ namespace Marco.Pms.Services.Service
// Mapping ExpensesTypeMasterDto to ExpensesTypeMaster // Mapping ExpensesTypeMasterDto to ExpensesTypeMaster
_mapper.Map(model, expensesType); _mapper.Map(model, expensesType);
_context.ExpensesTypeMaster.Update(expensesType); _context.ExpenseCategoryMasters.Update(expensesType);
await _context.SaveChangesAsync(); await _context.SaveChangesAsync();
_logger.LogInfo("Expense Type {ExpensesTypeId} was updated by employee {EmployeeId}", expensesType.Id, loggedInEmployee.Id); _logger.LogInfo("Expense Type {ExpensesTypeId} was updated by employee {EmployeeId}", expensesType.Id, loggedInEmployee.Id);
@ -2143,7 +2144,7 @@ namespace Marco.Pms.Services.Service
UpdatedById = loggedInEmployee.Id.ToString(), UpdatedById = loggedInEmployee.Id.ToString(),
OldObject = existingEntityBson, OldObject = existingEntityBson,
UpdatedAt = DateTime.UtcNow UpdatedAt = DateTime.UtcNow
}, "ExpensesTypeMasterModificationLog"); }, "ExpenseCategoryMasterModificationLog");
// Mapping ExpensesTypeMaster to ExpensesTypeMasterVM // Mapping ExpensesTypeMaster to ExpensesTypeMasterVM
var response = _mapper.Map<ExpensesTypeMasterVM>(expensesType); var response = _mapper.Map<ExpensesTypeMasterVM>(expensesType);
@ -2173,7 +2174,7 @@ namespace Marco.Pms.Services.Service
return ApiResponse<object>.ErrorResponse("Access Denied.", "You do not have permission to Manage masters", 403); return ApiResponse<object>.ErrorResponse("Access Denied.", "You do not have permission to Manage masters", 403);
} }
var expensesType = await _context.ExpensesTypeMaster.FirstOrDefaultAsync(et => et.Id == id && et.TenantId == tenantId); var expensesType = await _context.ExpenseCategoryMasters.FirstOrDefaultAsync(et => et.Id == id && et.TenantId == tenantId);
// Checking if expense type exists // Checking if expense type exists
if (expensesType == null) if (expensesType == null)
@ -2198,7 +2199,7 @@ namespace Marco.Pms.Services.Service
UpdatedById = loggedInEmployee.Id.ToString(), UpdatedById = loggedInEmployee.Id.ToString(),
OldObject = existingEntityBson, OldObject = existingEntityBson,
UpdatedAt = DateTime.UtcNow UpdatedAt = DateTime.UtcNow
}, "ExpensesTypeMasterModificationLog"); }, "ExpenseCategoryMasterModificationLog");
// Mapping ExpensesTypeMaster to ExpensesTypeMasterVM // Mapping ExpensesTypeMaster to ExpensesTypeMasterVM
var response = _mapper.Map<ExpensesTypeMasterVM>(expensesType); var response = _mapper.Map<ExpensesTypeMasterVM>(expensesType);

View File

@ -16,5 +16,8 @@ namespace Marco.Pms.Services.Service.ServiceInterfaces
Task<ApiResponse<object>> DeleteExpanseAsync(Guid id, Employee loggedInEmployee, Guid tenantId); Task<ApiResponse<object>> DeleteExpanseAsync(Guid id, Employee loggedInEmployee, Guid tenantId);
Task<ApiResponse<object>> GetAdvancePaymentTransactionAsync(Guid id); Task<ApiResponse<object>> GetAdvancePaymentTransactionAsync(Guid id);
Task<ApiResponse<object>> CreatePaymentRequestAsync(PaymentRequestDto model, Employee loggedInEmployee, Guid tenantId);
} }
} }