Compare commits
35 Commits
main
...
Purchase_I
| Author | SHA1 | Date | |
|---|---|---|---|
| a0a65fc08c | |||
| 9b8bb5d0cb | |||
| 20df833c48 | |||
| 8470223f98 | |||
| c5949606aa | |||
| 4c284f9904 | |||
| 852ddc7e02 | |||
| 6bcc67bb63 | |||
| fdb08fae89 | |||
| 94e2e4f18b | |||
| 0960702f0c | |||
| 26ac59fa52 | |||
| 1a3c030495 | |||
| 447e915505 | |||
| 3caf944c50 | |||
| 5d8a5e0cc8 | |||
| a4714d5440 | |||
| 4b981b6c74 | |||
| e92976049e | |||
| 28deae6416 | |||
| 1746cf0300 | |||
| 9e7651f345 | |||
| 49da601092 | |||
| 0fe59223e2 | |||
| 41feb58d45 | |||
| 34c5ac9c25 | |||
| 3dce559de2 | |||
| 886a32b3e3 | |||
| b6baff7d00 | |||
| bbe36ed535 | |||
| fa1c534ba8 | |||
| d1f5240f8f | |||
| c4653b557c | |||
| 8aace3e1d9 | |||
| a6177adb43 |
@ -14,6 +14,7 @@ using Marco.Pms.Model.Master;
|
||||
using Marco.Pms.Model.OrganizationModel;
|
||||
using Marco.Pms.Model.PaymentGetway;
|
||||
using Marco.Pms.Model.Projects;
|
||||
using Marco.Pms.Model.PurchaseInvoice;
|
||||
using Marco.Pms.Model.Roles;
|
||||
using Marco.Pms.Model.ServiceProject;
|
||||
using Marco.Pms.Model.TenantModels;
|
||||
@ -54,7 +55,7 @@ namespace Marco.Pms.DataAccess.Data
|
||||
public DbSet<Document> Documents { get; set; }
|
||||
public DbSet<MailingList> MailingList { get; set; }
|
||||
public DbSet<MailDetails> MailDetails { get; set; }
|
||||
public DbSet<MailLog> MailLogs { get; set; }
|
||||
//public DbSet<MailLog> MailLogs { get; set; }
|
||||
public DbSet<OTPDetails> OTPDetails { get; set; }
|
||||
public DbSet<MPINDetails> MPINDetails { get; set; }
|
||||
public DbSet<FCMTokenMapping> FCMTokenMappings { get; set; }
|
||||
@ -238,6 +239,14 @@ namespace Marco.Pms.DataAccess.Data
|
||||
|
||||
#endregion
|
||||
|
||||
#region ======================================================= Purchase Invoice =======================================================
|
||||
public DbSet<PurchaseInvoiceDetails> PurchaseInvoiceDetails { get; set; }
|
||||
public DbSet<DeliveryChallanDetails> DeliveryChallanDetails { get; set; }
|
||||
public DbSet<PurchaseInvoiceAttachment> PurchaseInvoiceAttachments { get; set; }
|
||||
public DbSet<PurchaseInvoicePayment> PurchaseInvoicePayments { get; set; }
|
||||
public DbSet<PurchaseInvoiceStatus> PurchaseInvoiceStatus { get; set; }
|
||||
public DbSet<InvoiceAttachmentType> InvoiceAttachmentTypes { get; set; }
|
||||
#endregion
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
@ -554,8 +563,102 @@ namespace Marco.Pms.DataAccess.Data
|
||||
}
|
||||
);
|
||||
|
||||
}
|
||||
modelBuilder.Entity<InvoiceAttachmentType>().HasData(
|
||||
new InvoiceAttachmentType
|
||||
{
|
||||
Id = Guid.Parse("ca294108-a586-4207-88c8-163b24305ddc"),
|
||||
Name = "Delivery Challan",
|
||||
Description = "A delivery challan is a formal document accompanying a shipment of goods that lists the items included and serves as proof of delivery upon receipt."
|
||||
},
|
||||
new InvoiceAttachmentType
|
||||
{
|
||||
Id = Guid.Parse("150ddd9b-4b8d-44ac-bae0-2e553c0f069a"),
|
||||
Name = "E Way Bill",
|
||||
Description = "An E-Way Bill (Electronic Way Bill) is a mandatory digital document generated on the GST portal to evidence and track the movement of goods valued over ₹50,000."
|
||||
},
|
||||
new InvoiceAttachmentType
|
||||
{
|
||||
Id = Guid.Parse("3ca08288-0a74-4850-9948-0783aa975b84"),
|
||||
Name = "Tax Invoice",
|
||||
Description = "A Tax Invoice is a mandatory legal document issued by a GST-registered supplier for taxable goods or services, enabling the buyer to claim Input Tax Credit (ITC)."
|
||||
},
|
||||
new InvoiceAttachmentType
|
||||
{
|
||||
Id = Guid.Parse("1fa20cff-b0ee-468e-9ea6-72d5aa144a3f"),
|
||||
Name = "E-Invoice",
|
||||
Description = "An E-Invoice (Electronic Invoice) is a system where B2B invoices are electronically authenticated by the GST Network (GSTN) to generate a unique Invoice Reference Number (IRN) and QR code."
|
||||
},
|
||||
new InvoiceAttachmentType
|
||||
{
|
||||
Id = Guid.Parse("31cd7533-3ffc-4e84-a0b4-db3b94d016b2"),
|
||||
Name = "Proforma Invoice",
|
||||
Description = "Proforma Invoice"
|
||||
},
|
||||
new InvoiceAttachmentType
|
||||
{
|
||||
Id = Guid.Parse("060c79a4-81c7-40a4-8cc3-56362ac9fad6"),
|
||||
Name = "Sales Order",
|
||||
Description = "Sales Order"
|
||||
},
|
||||
new InvoiceAttachmentType
|
||||
{
|
||||
Id = Guid.Parse("12773c2c-64e7-478c-af17-8471f943a5ed"),
|
||||
Name = "Other",
|
||||
Description = "Other"
|
||||
}
|
||||
);
|
||||
|
||||
modelBuilder.Entity<PurchaseInvoiceStatus>().HasData(
|
||||
new PurchaseInvoiceStatus
|
||||
{
|
||||
Id = Guid.Parse("8a5ef25e-3c9e-45de-add9-6b1c1df54381"),
|
||||
Name = "Draft",
|
||||
DisplayName = "Draft",
|
||||
Description = "Draft Status in a Purchase Invoice indicates a preliminary, unfinalized document that is saved for review but has not yet been posted to the general ledger or affected your accounts/inventory.",
|
||||
Color = "#8592a3"
|
||||
},
|
||||
new PurchaseInvoiceStatus
|
||||
{
|
||||
Id = Guid.Parse("16b10201-1651-465c-b2fd-236bdef86f95"),
|
||||
Name = "Review Pending",
|
||||
DisplayName = "Submit for Review",
|
||||
Description = "Review Pending status in a Purchase Invoice indicates that the invoice has been submitted for validation but requires approval from an authorized person (like a manager or auditor) before it can be posted to the ledger or paid.",
|
||||
Color = "#696cff"
|
||||
},
|
||||
new PurchaseInvoiceStatus
|
||||
{
|
||||
Id = Guid.Parse("a05f5f4a-bd9d-4028-af42-48ee0caa3e40"),
|
||||
Name = "Rejected by Reviewer",
|
||||
DisplayName = "Reject",
|
||||
Description = "Rejected by Reviewer status indicates that the invoice failed the approval process due to errors, discrepancies, or policy violations and has been returned to the initiator or vendor for correction.",
|
||||
Color = "#ff3e1d"
|
||||
},
|
||||
new PurchaseInvoiceStatus
|
||||
{
|
||||
Id = Guid.Parse("60027a54-3c23-4619-9f4e-6c20549b50a6"),
|
||||
Name = "Approval Pending",
|
||||
DisplayName = "Mark as Reviewed",
|
||||
Description = "Approval Pending status in a Purchase Invoice indicates that the document has passed initial verification (matching and coding) and is now awaiting final financial authorization from a designated budget holder or signatory.",
|
||||
Color = "#03c3ec"
|
||||
},
|
||||
new PurchaseInvoiceStatus
|
||||
{
|
||||
Id = Guid.Parse("58de9cef-811f-46a4-814d-0069b64d98a9"),
|
||||
Name = "Rejected by Approver",
|
||||
DisplayName = "Reject",
|
||||
Description = "Rejected by Approver status in a Purchase Invoice indicates that the document successfully passed initial verification but was ultimately denied by the final authorizing signatory (such as a Manager or CFO) due to budget or validity concerns.",
|
||||
Color = "#ff3e1d"
|
||||
},
|
||||
new PurchaseInvoiceStatus
|
||||
{
|
||||
Id = Guid.Parse("5b393371-dbcf-4a28-88a8-f406fa34e0d0"),
|
||||
Name = "Approved",
|
||||
DisplayName = "Mark as Approved",
|
||||
Description = "Approved status indicates that the invoice has successfully cleared all necessary verification and authorization levels and is formally accepted by the company as a valid debt.",
|
||||
Color = "#71dd37"
|
||||
}
|
||||
);
|
||||
}
|
||||
private static void ManageApplicationStructure(ModelBuilder modelBuilder)
|
||||
{
|
||||
// Configure ApplicationRole to Tenant relationship (if Tenant exists)
|
||||
@ -1330,6 +1433,7 @@ namespace Marco.Pms.DataAccess.Data
|
||||
new Module { Id = new Guid("2a231490-bcb1-4bdd-91f1-f25fb7f25b23"), Name = "Employee", Description = "Employee Module", Key = "0971c7fb-6ce1-458a-ae3f-8d3205893637" },
|
||||
new Module { Id = new Guid("c43db8c7-ab73-47f4-9d3b-f83e81357924"), Name = "Masters", Description = "Masters Module", Key = "504ec132-e6a9-422f-8f85-050602cfce05" },
|
||||
new Module { Id = new Guid("f482a079-4dec-4f2d-9867-6baf2a4f23d9"), Name = "Tenant", Description = "Tenant Module", Key = "504ec132-e6a9-422f-8f85-050602cfce05" },
|
||||
new Module { Id = new Guid("74e7af50-d55f-4b59-a724-9847ceb7bc17"), Name = "Inventory", Description = "Inventory Module", Key = "504ec132-e6a9-422f-8f85-050602cfce05" },
|
||||
new Module { Id = new Guid("0a79687a-86d7-430d-a2d7-8b8603cc76a1"), Name = "Finance", Description = "Finance Module", Key = "504ec132-e6a9-422f-8f85-050602cfce05" }
|
||||
);
|
||||
|
||||
@ -1353,6 +1457,9 @@ namespace Marco.Pms.DataAccess.Data
|
||||
new Feature { Id = new Guid("39e66f81-efc6-446c-95bd-46bff6cfb606"), Description = "Managing all directory related rights", Name = "Directory Management", ModuleId = new Guid("c43db8c7-ab73-47f4-9d3b-f83e81357924"), IsActive = true },
|
||||
new Feature { Id = new Guid("6d4c82d6-dbce-48ab-b8b8-f785f4d8c914"), Description = "Managing all organization related rights", Name = "Organization Management", ModuleId = new Guid("c43db8c7-ab73-47f4-9d3b-f83e81357924"), IsActive = true },
|
||||
|
||||
// Inventory Module
|
||||
new Feature { Id = new Guid("271cc47f-7b05-46c7-b5ae-ef0177ec3b60"), Description = "Managing all Purchase invoice related rights", Name = "Purchase Invoice Management", ModuleId = new Guid("74e7af50-d55f-4b59-a724-9847ceb7bc17"), IsActive = true },
|
||||
|
||||
// Tenant Module
|
||||
new Feature { Id = new Guid("2f3509b7-160d-410a-b9b6-daadd96c986d"), Description = "Managing all tenant related rights", Name = "Tenant Management", ModuleId = new Guid("f482a079-4dec-4f2d-9867-6baf2a4f23d9"), IsActive = true }
|
||||
);
|
||||
@ -1427,7 +1534,14 @@ namespace Marco.Pms.DataAccess.Data
|
||||
// Organization Management Feature
|
||||
new FeaturePermission { Id = new Guid("068cb3c1-49c5-4746-9f29-1fce16e820ac"), FeatureId = new Guid("6d4c82d6-dbce-48ab-b8b8-f785f4d8c914"), IsEnabled = true, Name = "Add Organization", Description = "Allow user to create new organization" },
|
||||
new FeaturePermission { Id = new Guid("c1ae1363-ab8a-4bd9-a9d1-8c2c6083873a"), FeatureId = new Guid("6d4c82d6-dbce-48ab-b8b8-f785f4d8c914"), IsEnabled = true, Name = "Edit Organization", Description = "Allow the user to update the basic information of the organization" },
|
||||
new FeaturePermission { Id = new Guid("7a6cf830-0008-4e03-b31d-0d050cb634f4"), FeatureId = new Guid("6d4c82d6-dbce-48ab-b8b8-f785f4d8c914"), IsEnabled = true, Name = "View Organization", Description = "Allow the user to view information of the organization" }
|
||||
new FeaturePermission { Id = new Guid("7a6cf830-0008-4e03-b31d-0d050cb634f4"), FeatureId = new Guid("6d4c82d6-dbce-48ab-b8b8-f785f4d8c914"), IsEnabled = true, Name = "View Organization", Description = "Allow the user to view information of the organization" },
|
||||
|
||||
// Purchase Invoice Management Feature
|
||||
new FeaturePermission { Id = new Guid("91e09825-512a-465e-82ad-fa355b305585"), FeatureId = new Guid("271cc47f-7b05-46c7-b5ae-ef0177ec3b60"), IsEnabled = true, Name = "View Self Purchase Invoice", Description = "Allows the user to view only the purchase invoices they created." },
|
||||
new FeaturePermission { Id = new Guid("d6ae78d3-a941-4cc4-8d0a-d40479be4211"), FeatureId = new Guid("271cc47f-7b05-46c7-b5ae-ef0177ec3b60"), IsEnabled = true, Name = "View All Purchase Invoice", Description = "Allows the user to view all purchase invoices across the entire organization." },
|
||||
new FeaturePermission { Id = new Guid("68ff925d-8ebf-4034-a137-8d3317c56ca1"), FeatureId = new Guid("271cc47f-7b05-46c7-b5ae-ef0177ec3b60"), IsEnabled = true, Name = "Manage Purchase Invoice", Description = "Allows full control to create, edit, and process purchase invoices." },
|
||||
new FeaturePermission { Id = new Guid("a4b77638-bf31-42bb-afd4-d5bbd15ccadc"), FeatureId = new Guid("271cc47f-7b05-46c7-b5ae-ef0177ec3b60"), IsEnabled = true, Name = "Delete Purchase Invoice", Description = "Allows the user to mark purchase invoices as inactive or void." },
|
||||
new FeaturePermission { Id = new Guid("b24eba39-4a92-4f7a-b33b-b5308fbc48b9"), FeatureId = new Guid("271cc47f-7b05-46c7-b5ae-ef0177ec3b60"), IsEnabled = true, Name = "Add Delivery Challan", Description = "Allows the user to create delivery challans for purchase invoices." }
|
||||
|
||||
);
|
||||
|
||||
|
||||
9385
Marco.Pms.DataAccess/Migrations/20251125130508_Added_PurchaseInvoice_Related_Tables.Designer.cs
generated
Normal file
9385
Marco.Pms.DataAccess/Migrations/20251125130508_Added_PurchaseInvoice_Related_Tables.Designer.cs
generated
Normal file
File diff suppressed because one or more lines are too long
@ -0,0 +1,423 @@
|
||||
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_PurchaseInvoice_Related_Tables : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_AdvancePaymentTransactions_Projects_ProjectId",
|
||||
table: "AdvancePaymentTransactions");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_AdvancePaymentTransactions_ProjectId",
|
||||
table: "AdvancePaymentTransactions");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "InvoiceAttachmentTypes",
|
||||
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"),
|
||||
Description = table.Column<string>(type: "longtext", nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4")
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_InvoiceAttachmentTypes", x => x.Id);
|
||||
})
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "PurchaseInvoiceDetails",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
|
||||
UIDPrefix = table.Column<string>(type: "longtext", nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
UIDPostfix = table.Column<int>(type: "int", nullable: false),
|
||||
Title = table.Column<string>(type: "longtext", nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
Description = table.Column<string>(type: "longtext", nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
ProjectId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
|
||||
OrganizationId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
|
||||
BillingAddress = table.Column<string>(type: "longtext", nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
ShippingAddress = table.Column<string>(type: "longtext", nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
PurchaseOrderNumber = table.Column<string>(type: "longtext", nullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
PurchaseOrderDate = table.Column<DateTime>(type: "datetime(6)", nullable: true),
|
||||
SupplierId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
|
||||
ProformaInvoiceNumber = table.Column<string>(type: "longtext", nullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
ProformaInvoiceDate = table.Column<DateTime>(type: "datetime(6)", nullable: true),
|
||||
ProformaInvoiceAmount = table.Column<double>(type: "double", nullable: true),
|
||||
InvoiceNumber = table.Column<string>(type: "longtext", nullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
InvoiceDate = table.Column<DateTime>(type: "datetime(6)", nullable: true),
|
||||
EWayBillNumber = table.Column<string>(type: "longtext", nullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
EWayBillDate = table.Column<DateTime>(type: "datetime(6)", nullable: true),
|
||||
InvoiceReferenceNumber = table.Column<string>(type: "longtext", nullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
AcknowledgmentNumber = table.Column<string>(type: "longtext", nullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
AcknowledgmentDate = table.Column<DateTime>(type: "datetime(6)", nullable: true),
|
||||
BaseAmount = table.Column<double>(type: "double", nullable: false),
|
||||
TaxAmount = table.Column<double>(type: "double", nullable: false),
|
||||
TransportCharges = table.Column<double>(type: "double", nullable: true),
|
||||
TotalAmount = table.Column<double>(type: "double", nullable: false),
|
||||
PaymentDueDate = table.Column<DateTime>(type: "datetime(6)", nullable: false),
|
||||
IsActive = table.Column<bool>(type: "tinyint(1)", nullable: false),
|
||||
CreatedById = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
|
||||
CreatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false),
|
||||
UpdatedById = table.Column<Guid>(type: "char(36)", nullable: true, collation: "ascii_general_ci"),
|
||||
UpdatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: true),
|
||||
TenantId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci")
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_PurchaseInvoiceDetails", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_PurchaseInvoiceDetails_Employees_CreatedById",
|
||||
column: x => x.CreatedById,
|
||||
principalTable: "Employees",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_PurchaseInvoiceDetails_Employees_UpdatedById",
|
||||
column: x => x.UpdatedById,
|
||||
principalTable: "Employees",
|
||||
principalColumn: "Id");
|
||||
table.ForeignKey(
|
||||
name: "FK_PurchaseInvoiceDetails_Organizations_OrganizationId",
|
||||
column: x => x.OrganizationId,
|
||||
principalTable: "Organizations",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_PurchaseInvoiceDetails_Organizations_SupplierId",
|
||||
column: x => x.SupplierId,
|
||||
principalTable: "Organizations",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_PurchaseInvoiceDetails_Tenants_TenantId",
|
||||
column: x => x.TenantId,
|
||||
principalTable: "Tenants",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
})
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "PurchaseInvoiceStatus",
|
||||
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"),
|
||||
DisplayName = table.Column<string>(type: "longtext", nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
Color = table.Column<string>(type: "longtext", nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
Description = table.Column<string>(type: "longtext", nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4")
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_PurchaseInvoiceStatus", x => x.Id);
|
||||
})
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "PurchaseInvoiceAttachments",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
|
||||
PurchaseInvoiceId = 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"),
|
||||
UploadedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false),
|
||||
UploadedById = 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_PurchaseInvoiceAttachments", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_PurchaseInvoiceAttachments_Documents_DocumentId",
|
||||
column: x => x.DocumentId,
|
||||
principalTable: "Documents",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_PurchaseInvoiceAttachments_Employees_UploadedById",
|
||||
column: x => x.UploadedById,
|
||||
principalTable: "Employees",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_PurchaseInvoiceAttachments_PurchaseInvoiceDetails_PurchaseIn~",
|
||||
column: x => x.PurchaseInvoiceId,
|
||||
principalTable: "PurchaseInvoiceDetails",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_PurchaseInvoiceAttachments_Tenants_TenantId",
|
||||
column: x => x.TenantId,
|
||||
principalTable: "Tenants",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
})
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "PurchaseInvoicePayments",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
|
||||
InvoiceId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
|
||||
PaymentReceivedDate = table.Column<DateTime>(type: "datetime(6)", nullable: false),
|
||||
TransactionId = table.Column<string>(type: "longtext", nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
Amount = table.Column<double>(type: "double", nullable: false),
|
||||
Comment = table.Column<string>(type: "longtext", nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
IsActive = table.Column<bool>(type: "tinyint(1)", nullable: false),
|
||||
PaymentAdjustmentHeadId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
|
||||
CreatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false),
|
||||
CreatedById = 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_PurchaseInvoicePayments", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_PurchaseInvoicePayments_Employees_CreatedById",
|
||||
column: x => x.CreatedById,
|
||||
principalTable: "Employees",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_PurchaseInvoicePayments_PaymentAdjustmentHeads_PaymentAdjust~",
|
||||
column: x => x.PaymentAdjustmentHeadId,
|
||||
principalTable: "PaymentAdjustmentHeads",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_PurchaseInvoicePayments_PurchaseInvoiceDetails_InvoiceId",
|
||||
column: x => x.InvoiceId,
|
||||
principalTable: "PurchaseInvoiceDetails",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_PurchaseInvoicePayments_Tenants_TenantId",
|
||||
column: x => x.TenantId,
|
||||
principalTable: "Tenants",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
})
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "DeliveryChallanDetails",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
|
||||
DeliveryChallanNumber = table.Column<string>(type: "longtext", nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
DeliveryChallanDate = table.Column<DateTime>(type: "datetime(6)", nullable: false),
|
||||
Description = table.Column<string>(type: "longtext", nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
PurchaseInvoiceId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
|
||||
AttachmentId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
|
||||
CreatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false),
|
||||
CreatedById = 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_DeliveryChallanDetails", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_DeliveryChallanDetails_Employees_CreatedById",
|
||||
column: x => x.CreatedById,
|
||||
principalTable: "Employees",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_DeliveryChallanDetails_PurchaseInvoiceAttachments_Attachment~",
|
||||
column: x => x.AttachmentId,
|
||||
principalTable: "PurchaseInvoiceAttachments",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_DeliveryChallanDetails_PurchaseInvoiceDetails_PurchaseInvoic~",
|
||||
column: x => x.PurchaseInvoiceId,
|
||||
principalTable: "PurchaseInvoiceDetails",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_DeliveryChallanDetails_Tenants_TenantId",
|
||||
column: x => x.TenantId,
|
||||
principalTable: "Tenants",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
})
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.InsertData(
|
||||
table: "InvoiceAttachmentTypes",
|
||||
columns: new[] { "Id", "Description", "Name" },
|
||||
values: new object[,]
|
||||
{
|
||||
{ new Guid("150ddd9b-4b8d-44ac-bae0-2e553c0f069a"), "An E-Way Bill (Electronic Way Bill) is a mandatory digital document generated on the GST portal to evidence and track the movement of goods valued over ₹50,000.", "E Way Bill" },
|
||||
{ new Guid("1fa20cff-b0ee-468e-9ea6-72d5aa144a3f"), "An E-Invoice (Electronic Invoice) is a system where B2B invoices are electronically authenticated by the GST Network (GSTN) to generate a unique Invoice Reference Number (IRN) and QR code.", "E-Invoice" },
|
||||
{ new Guid("3ca08288-0a74-4850-9948-0783aa975b84"), "A Tax Invoice is a mandatory legal document issued by a GST-registered supplier for taxable goods or services, enabling the buyer to claim Input Tax Credit (ITC).", "Tax Invoice" },
|
||||
{ new Guid("ca294108-a586-4207-88c8-163b24305ddc"), "A delivery challan is a formal document accompanying a shipment of goods that lists the items included and serves as proof of delivery upon receipt.", "Delivery Challan" }
|
||||
});
|
||||
|
||||
migrationBuilder.InsertData(
|
||||
table: "PurchaseInvoiceStatus",
|
||||
columns: new[] { "Id", "Color", "Description", "DisplayName", "Name" },
|
||||
values: new object[,]
|
||||
{
|
||||
{ new Guid("16b10201-1651-465c-b2fd-236bdef86f95"), "#696cff", "Review Pending status in a Purchase Invoice indicates that the invoice has been submitted for validation but requires approval from an authorized person (like a manager or auditor) before it can be posted to the ledger or paid.", "Submit for Review", "Review Pending" },
|
||||
{ new Guid("58de9cef-811f-46a4-814d-0069b64d98a9"), "#ff3e1d", "Rejected by Approver status in a Purchase Invoice indicates that the document successfully passed initial verification but was ultimately denied by the final authorizing signatory (such as a Manager or CFO) due to budget or validity concerns.", "Reject", "Rejected by Approver" },
|
||||
{ new Guid("5b393371-dbcf-4a28-88a8-f406fa34e0d0"), "#71dd37", "Approved status indicates that the invoice has successfully cleared all necessary verification and authorization levels and is formally accepted by the company as a valid debt.", "Mark as Approved", "Approved" },
|
||||
{ new Guid("60027a54-3c23-4619-9f4e-6c20549b50a6"), "#03c3ec", "Approval Pending status in a Purchase Invoice indicates that the document has passed initial verification (matching and coding) and is now awaiting final financial authorization from a designated budget holder or signatory.", "Mark as Reviewed", "Approval Pending" },
|
||||
{ new Guid("8a5ef25e-3c9e-45de-add9-6b1c1df54381"), "#8592a3", "Draft Status in a Purchase Invoice indicates a preliminary, unfinalized document that is saved for review but has not yet been posted to the general ledger or affected your accounts/inventory.", "Draft", "Draft" },
|
||||
{ new Guid("a05f5f4a-bd9d-4028-af42-48ee0caa3e40"), "#ff3e1d", "Rejected by Reviewer status indicates that the invoice failed the approval process due to errors, discrepancies, or policy violations and has been returned to the initiator or vendor for correction.", "Reject", "Rejected by Reviewer" }
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_DeliveryChallanDetails_AttachmentId",
|
||||
table: "DeliveryChallanDetails",
|
||||
column: "AttachmentId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_DeliveryChallanDetails_CreatedById",
|
||||
table: "DeliveryChallanDetails",
|
||||
column: "CreatedById");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_DeliveryChallanDetails_PurchaseInvoiceId",
|
||||
table: "DeliveryChallanDetails",
|
||||
column: "PurchaseInvoiceId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_DeliveryChallanDetails_TenantId",
|
||||
table: "DeliveryChallanDetails",
|
||||
column: "TenantId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_PurchaseInvoiceAttachments_DocumentId",
|
||||
table: "PurchaseInvoiceAttachments",
|
||||
column: "DocumentId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_PurchaseInvoiceAttachments_PurchaseInvoiceId",
|
||||
table: "PurchaseInvoiceAttachments",
|
||||
column: "PurchaseInvoiceId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_PurchaseInvoiceAttachments_TenantId",
|
||||
table: "PurchaseInvoiceAttachments",
|
||||
column: "TenantId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_PurchaseInvoiceAttachments_UploadedById",
|
||||
table: "PurchaseInvoiceAttachments",
|
||||
column: "UploadedById");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_PurchaseInvoiceDetails_CreatedById",
|
||||
table: "PurchaseInvoiceDetails",
|
||||
column: "CreatedById");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_PurchaseInvoiceDetails_OrganizationId",
|
||||
table: "PurchaseInvoiceDetails",
|
||||
column: "OrganizationId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_PurchaseInvoiceDetails_SupplierId",
|
||||
table: "PurchaseInvoiceDetails",
|
||||
column: "SupplierId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_PurchaseInvoiceDetails_TenantId",
|
||||
table: "PurchaseInvoiceDetails",
|
||||
column: "TenantId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_PurchaseInvoiceDetails_UpdatedById",
|
||||
table: "PurchaseInvoiceDetails",
|
||||
column: "UpdatedById");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_PurchaseInvoicePayments_CreatedById",
|
||||
table: "PurchaseInvoicePayments",
|
||||
column: "CreatedById");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_PurchaseInvoicePayments_InvoiceId",
|
||||
table: "PurchaseInvoicePayments",
|
||||
column: "InvoiceId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_PurchaseInvoicePayments_PaymentAdjustmentHeadId",
|
||||
table: "PurchaseInvoicePayments",
|
||||
column: "PaymentAdjustmentHeadId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_PurchaseInvoicePayments_TenantId",
|
||||
table: "PurchaseInvoicePayments",
|
||||
column: "TenantId");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "DeliveryChallanDetails");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "InvoiceAttachmentTypes");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "PurchaseInvoicePayments");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "PurchaseInvoiceStatus");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "PurchaseInvoiceAttachments");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "PurchaseInvoiceDetails");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_AdvancePaymentTransactions_ProjectId",
|
||||
table: "AdvancePaymentTransactions",
|
||||
column: "ProjectId");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_AdvancePaymentTransactions_Projects_ProjectId",
|
||||
table: "AdvancePaymentTransactions",
|
||||
column: "ProjectId",
|
||||
principalTable: "Projects",
|
||||
principalColumn: "Id");
|
||||
}
|
||||
}
|
||||
}
|
||||
9411
Marco.Pms.DataAccess/Migrations/20251125143000_Added_Status_In_PurchaseInvoiceDetails_Table.Designer.cs
generated
Normal file
9411
Marco.Pms.DataAccess/Migrations/20251125143000_Added_Status_In_PurchaseInvoiceDetails_Table.Designer.cs
generated
Normal file
File diff suppressed because one or more lines are too long
@ -0,0 +1,84 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Marco.Pms.DataAccess.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class Added_Status_In_PurchaseInvoiceDetails_Table : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<Guid>(
|
||||
name: "StatusId",
|
||||
table: "PurchaseInvoiceDetails",
|
||||
type: "char(36)",
|
||||
nullable: false,
|
||||
defaultValue: new Guid("8a5ef25e-3c9e-45de-add9-6b1c1df54381"),
|
||||
collation: "ascii_general_ci");
|
||||
|
||||
migrationBuilder.AddColumn<Guid>(
|
||||
name: "InvoiceAttachmentTypeId",
|
||||
table: "PurchaseInvoiceAttachments",
|
||||
type: "char(36)",
|
||||
nullable: false,
|
||||
defaultValue: new Guid("3ca08288-0a74-4850-9948-0783aa975b84"),
|
||||
collation: "ascii_general_ci");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_PurchaseInvoiceDetails_StatusId",
|
||||
table: "PurchaseInvoiceDetails",
|
||||
column: "StatusId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_PurchaseInvoiceAttachments_InvoiceAttachmentTypeId",
|
||||
table: "PurchaseInvoiceAttachments",
|
||||
column: "InvoiceAttachmentTypeId");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_PurchaseInvoiceAttachments_InvoiceAttachmentTypes_InvoiceAtt~",
|
||||
table: "PurchaseInvoiceAttachments",
|
||||
column: "InvoiceAttachmentTypeId",
|
||||
principalTable: "InvoiceAttachmentTypes",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_PurchaseInvoiceDetails_PurchaseInvoiceStatus_StatusId",
|
||||
table: "PurchaseInvoiceDetails",
|
||||
column: "StatusId",
|
||||
principalTable: "PurchaseInvoiceStatus",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_PurchaseInvoiceAttachments_InvoiceAttachmentTypes_InvoiceAtt~",
|
||||
table: "PurchaseInvoiceAttachments");
|
||||
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_PurchaseInvoiceDetails_PurchaseInvoiceStatus_StatusId",
|
||||
table: "PurchaseInvoiceDetails");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_PurchaseInvoiceDetails_StatusId",
|
||||
table: "PurchaseInvoiceDetails");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_PurchaseInvoiceAttachments_InvoiceAttachmentTypeId",
|
||||
table: "PurchaseInvoiceAttachments");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "StatusId",
|
||||
table: "PurchaseInvoiceDetails");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "InvoiceAttachmentTypeId",
|
||||
table: "PurchaseInvoiceAttachments");
|
||||
}
|
||||
}
|
||||
}
|
||||
9466
Marco.Pms.DataAccess/Migrations/20251201060004_Added_Purchase_Invoice_Permissions.Designer.cs
generated
Normal file
9466
Marco.Pms.DataAccess/Migrations/20251201060004_Added_Purchase_Invoice_Permissions.Designer.cs
generated
Normal file
File diff suppressed because one or more lines are too long
@ -0,0 +1,78 @@
|
||||
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_Purchase_Invoice_Permissions : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.InsertData(
|
||||
table: "Modules",
|
||||
columns: new[] { "Id", "Description", "Key", "Name" },
|
||||
values: new object[] { new Guid("74e7af50-d55f-4b59-a724-9847ceb7bc17"), "Inventory Module", "504ec132-e6a9-422f-8f85-050602cfce05", "Inventory" });
|
||||
|
||||
migrationBuilder.InsertData(
|
||||
table: "Features",
|
||||
columns: new[] { "Id", "Description", "IsActive", "ModuleId", "Name" },
|
||||
values: new object[] { new Guid("271cc47f-7b05-46c7-b5ae-ef0177ec3b60"), "Managing all Purchase invoice related rights", true, new Guid("74e7af50-d55f-4b59-a724-9847ceb7bc17"), "Purchase Invoice Management" });
|
||||
|
||||
migrationBuilder.InsertData(
|
||||
table: "FeaturePermissions",
|
||||
columns: new[] { "Id", "Description", "FeatureId", "IsEnabled", "Name" },
|
||||
values: new object[,]
|
||||
{
|
||||
{ new Guid("68ff925d-8ebf-4034-a137-8d3317c56ca1"), "Allows full control to create, edit, and process purchase invoices.", new Guid("271cc47f-7b05-46c7-b5ae-ef0177ec3b60"), true, "Manage Purchase Invoice" },
|
||||
{ new Guid("91e09825-512a-465e-82ad-fa355b305585"), "Allows the user to view only the purchase invoices they created.", new Guid("271cc47f-7b05-46c7-b5ae-ef0177ec3b60"), true, "View Self Purchase Invoice" },
|
||||
{ new Guid("a4b77638-bf31-42bb-afd4-d5bbd15ccadc"), "Allows the user to mark purchase invoices as inactive or void.", new Guid("271cc47f-7b05-46c7-b5ae-ef0177ec3b60"), true, "Delete Purchase Invoice" },
|
||||
{ new Guid("b24eba39-4a92-4f7a-b33b-b5308fbc48b9"), "Allows the user to create delivery challans for purchase invoices.", new Guid("271cc47f-7b05-46c7-b5ae-ef0177ec3b60"), true, "Add Delivery Challan" },
|
||||
{ new Guid("d6ae78d3-a941-4cc4-8d0a-d40479be4211"), "Allows the user to view all purchase invoices across the entire organization.", new Guid("271cc47f-7b05-46c7-b5ae-ef0177ec3b60"), true, "View All Purchase Invoice" }
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DeleteData(
|
||||
table: "FeaturePermissions",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("68ff925d-8ebf-4034-a137-8d3317c56ca1"));
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "FeaturePermissions",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("91e09825-512a-465e-82ad-fa355b305585"));
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "FeaturePermissions",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("a4b77638-bf31-42bb-afd4-d5bbd15ccadc"));
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "FeaturePermissions",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("b24eba39-4a92-4f7a-b33b-b5308fbc48b9"));
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "FeaturePermissions",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("d6ae78d3-a941-4cc4-8d0a-d40479be4211"));
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "Features",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("271cc47f-7b05-46c7-b5ae-ef0177ec3b60"));
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "Modules",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("74e7af50-d55f-4b59-a724-9847ceb7bc17"));
|
||||
}
|
||||
}
|
||||
}
|
||||
9484
Marco.Pms.DataAccess/Migrations/20251204085823_Added_Invoice_Attachment_Type_Table.Designer.cs
generated
Normal file
9484
Marco.Pms.DataAccess/Migrations/20251204085823_Added_Invoice_Attachment_Type_Table.Designer.cs
generated
Normal file
File diff suppressed because one or more lines are too long
@ -0,0 +1,46 @@
|
||||
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_Invoice_Attachment_Type_Table : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.InsertData(
|
||||
table: "InvoiceAttachmentTypes",
|
||||
columns: new[] { "Id", "Description", "Name" },
|
||||
values: new object[,]
|
||||
{
|
||||
{ new Guid("060c79a4-81c7-40a4-8cc3-56362ac9fad6"), "Sales Order", "Sales Order" },
|
||||
{ new Guid("12773c2c-64e7-478c-af17-8471f943a5ed"), "Other", "Other" },
|
||||
{ new Guid("31cd7533-3ffc-4e84-a0b4-db3b94d016b2"), "Proforma Invoice", "Proforma Invoice" }
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DeleteData(
|
||||
table: "InvoiceAttachmentTypes",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("060c79a4-81c7-40a4-8cc3-56362ac9fad6"));
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "InvoiceAttachmentTypes",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("12773c2c-64e7-478c-af17-8471f943a5ed"));
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "InvoiceAttachmentTypes",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("31cd7533-3ffc-4e84-a0b4-db3b94d016b2"));
|
||||
}
|
||||
}
|
||||
}
|
||||
9453
Marco.Pms.DataAccess/Migrations/20251206102239_Removed_MaiLogs_Table.Designer.cs
generated
Normal file
9453
Marco.Pms.DataAccess/Migrations/20251206102239_Removed_MaiLogs_Table.Designer.cs
generated
Normal file
File diff suppressed because one or more lines are too long
@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Marco.Pms.DataAccess.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class Removed_MaiLogs_Table : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "MailLogs");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "MailLogs",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
|
||||
Body = table.Column<string>(type: "longtext", nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
EmailId = table.Column<string>(type: "longtext", nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
EmployeeId = table.Column<Guid>(type: "char(36)", nullable: true, collation: "ascii_general_ci"),
|
||||
ProjectId = 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"),
|
||||
TimeStamp = table.Column<DateTime>(type: "datetime(6)", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_MailLogs", x => x.Id);
|
||||
})
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2042,6 +2042,46 @@ namespace Marco.Pms.DataAccess.Migrations
|
||||
FeatureId = new Guid("6d4c82d6-dbce-48ab-b8b8-f785f4d8c914"),
|
||||
IsEnabled = true,
|
||||
Name = "View Organization"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("91e09825-512a-465e-82ad-fa355b305585"),
|
||||
Description = "Allows the user to view only the purchase invoices they created.",
|
||||
FeatureId = new Guid("271cc47f-7b05-46c7-b5ae-ef0177ec3b60"),
|
||||
IsEnabled = true,
|
||||
Name = "View Self Purchase Invoice"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("d6ae78d3-a941-4cc4-8d0a-d40479be4211"),
|
||||
Description = "Allows the user to view all purchase invoices across the entire organization.",
|
||||
FeatureId = new Guid("271cc47f-7b05-46c7-b5ae-ef0177ec3b60"),
|
||||
IsEnabled = true,
|
||||
Name = "View All Purchase Invoice"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("68ff925d-8ebf-4034-a137-8d3317c56ca1"),
|
||||
Description = "Allows full control to create, edit, and process purchase invoices.",
|
||||
FeatureId = new Guid("271cc47f-7b05-46c7-b5ae-ef0177ec3b60"),
|
||||
IsEnabled = true,
|
||||
Name = "Manage Purchase Invoice"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("a4b77638-bf31-42bb-afd4-d5bbd15ccadc"),
|
||||
Description = "Allows the user to mark purchase invoices as inactive or void.",
|
||||
FeatureId = new Guid("271cc47f-7b05-46c7-b5ae-ef0177ec3b60"),
|
||||
IsEnabled = true,
|
||||
Name = "Delete Purchase Invoice"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("b24eba39-4a92-4f7a-b33b-b5308fbc48b9"),
|
||||
Description = "Allows the user to create delivery challans for purchase invoices.",
|
||||
FeatureId = new Guid("271cc47f-7b05-46c7-b5ae-ef0177ec3b60"),
|
||||
IsEnabled = true,
|
||||
Name = "Add Delivery Challan"
|
||||
});
|
||||
});
|
||||
|
||||
@ -2141,8 +2181,6 @@ namespace Marco.Pms.DataAccess.Migrations
|
||||
|
||||
b.HasIndex("EmployeeId");
|
||||
|
||||
b.HasIndex("ProjectId");
|
||||
|
||||
b.HasIndex("TenantId");
|
||||
|
||||
b.ToTable("AdvancePaymentTransactions");
|
||||
@ -3133,37 +3171,6 @@ namespace Marco.Pms.DataAccess.Migrations
|
||||
b.ToTable("MailDetails");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Marco.Pms.Model.Mail.MailLog", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<string>("Body")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("EmailId")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<Guid?>("EmployeeId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<Guid>("ProjectId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<Guid>("TenantId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<DateTime>("TimeStamp")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("MailLogs");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Marco.Pms.Model.Mail.MailingList", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
@ -3643,6 +3650,14 @@ namespace Marco.Pms.DataAccess.Migrations
|
||||
Name = "Organization Management"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("271cc47f-7b05-46c7-b5ae-ef0177ec3b60"),
|
||||
Description = "Managing all Purchase invoice related rights",
|
||||
IsActive = true,
|
||||
ModuleId = new Guid("74e7af50-d55f-4b59-a724-9847ceb7bc17"),
|
||||
Name = "Purchase Invoice Management"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("2f3509b7-160d-410a-b9b6-daadd96c986d"),
|
||||
Description = "Managing all tenant related rights",
|
||||
@ -3834,6 +3849,13 @@ namespace Marco.Pms.DataAccess.Migrations
|
||||
Name = "Tenant"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("74e7af50-d55f-4b59-a724-9847ceb7bc17"),
|
||||
Description = "Inventory Module",
|
||||
Key = "504ec132-e6a9-422f-8f85-050602cfce05",
|
||||
Name = "Inventory"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("0a79687a-86d7-430d-a2d7-8b8603cc76a1"),
|
||||
Description = "Finance Module",
|
||||
@ -4964,6 +4986,408 @@ namespace Marco.Pms.DataAccess.Migrations
|
||||
b.ToTable("WorkItems");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Marco.Pms.Model.PurchaseInvoice.DeliveryChallanDetails", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<Guid>("AttachmentId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<Guid>("CreatedById")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<DateTime>("DeliveryChallanDate")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<string>("DeliveryChallanNumber")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<Guid>("PurchaseInvoiceId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<Guid>("TenantId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("AttachmentId");
|
||||
|
||||
b.HasIndex("CreatedById");
|
||||
|
||||
b.HasIndex("PurchaseInvoiceId");
|
||||
|
||||
b.HasIndex("TenantId");
|
||||
|
||||
b.ToTable("DeliveryChallanDetails");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Marco.Pms.Model.PurchaseInvoice.InvoiceAttachmentType", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("InvoiceAttachmentTypes");
|
||||
|
||||
b.HasData(
|
||||
new
|
||||
{
|
||||
Id = new Guid("ca294108-a586-4207-88c8-163b24305ddc"),
|
||||
Description = "A delivery challan is a formal document accompanying a shipment of goods that lists the items included and serves as proof of delivery upon receipt.",
|
||||
Name = "Delivery Challan"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("150ddd9b-4b8d-44ac-bae0-2e553c0f069a"),
|
||||
Description = "An E-Way Bill (Electronic Way Bill) is a mandatory digital document generated on the GST portal to evidence and track the movement of goods valued over ₹50,000.",
|
||||
Name = "E Way Bill"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("3ca08288-0a74-4850-9948-0783aa975b84"),
|
||||
Description = "A Tax Invoice is a mandatory legal document issued by a GST-registered supplier for taxable goods or services, enabling the buyer to claim Input Tax Credit (ITC).",
|
||||
Name = "Tax Invoice"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("1fa20cff-b0ee-468e-9ea6-72d5aa144a3f"),
|
||||
Description = "An E-Invoice (Electronic Invoice) is a system where B2B invoices are electronically authenticated by the GST Network (GSTN) to generate a unique Invoice Reference Number (IRN) and QR code.",
|
||||
Name = "E-Invoice"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("31cd7533-3ffc-4e84-a0b4-db3b94d016b2"),
|
||||
Description = "Proforma Invoice",
|
||||
Name = "Proforma Invoice"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("060c79a4-81c7-40a4-8cc3-56362ac9fad6"),
|
||||
Description = "Sales Order",
|
||||
Name = "Sales Order"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("12773c2c-64e7-478c-af17-8471f943a5ed"),
|
||||
Description = "Other",
|
||||
Name = "Other"
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Marco.Pms.Model.PurchaseInvoice.PurchaseInvoiceAttachment", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<Guid>("DocumentId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<Guid>("InvoiceAttachmentTypeId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<Guid>("PurchaseInvoiceId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<Guid>("TenantId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<DateTime>("UploadedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<Guid>("UploadedById")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("DocumentId");
|
||||
|
||||
b.HasIndex("InvoiceAttachmentTypeId");
|
||||
|
||||
b.HasIndex("PurchaseInvoiceId");
|
||||
|
||||
b.HasIndex("TenantId");
|
||||
|
||||
b.HasIndex("UploadedById");
|
||||
|
||||
b.ToTable("PurchaseInvoiceAttachments");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Marco.Pms.Model.PurchaseInvoice.PurchaseInvoiceDetails", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<DateTime?>("AcknowledgmentDate")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<string>("AcknowledgmentNumber")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<double>("BaseAmount")
|
||||
.HasColumnType("double");
|
||||
|
||||
b.Property<string>("BillingAddress")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<Guid>("CreatedById")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<DateTime?>("EWayBillDate")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<string>("EWayBillNumber")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<DateTime?>("InvoiceDate")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<string>("InvoiceNumber")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("InvoiceReferenceNumber")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<bool>("IsActive")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<Guid>("OrganizationId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<DateTime>("PaymentDueDate")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<double?>("ProformaInvoiceAmount")
|
||||
.HasColumnType("double");
|
||||
|
||||
b.Property<DateTime?>("ProformaInvoiceDate")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<string>("ProformaInvoiceNumber")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<Guid>("ProjectId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<DateTime?>("PurchaseOrderDate")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<string>("PurchaseOrderNumber")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("ShippingAddress")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<Guid>("StatusId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<Guid>("SupplierId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<double>("TaxAmount")
|
||||
.HasColumnType("double");
|
||||
|
||||
b.Property<Guid>("TenantId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<double>("TotalAmount")
|
||||
.HasColumnType("double");
|
||||
|
||||
b.Property<double?>("TransportCharges")
|
||||
.HasColumnType("double");
|
||||
|
||||
b.Property<int>("UIDPostfix")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("UIDPrefix")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<DateTime?>("UpdatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<Guid?>("UpdatedById")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("CreatedById");
|
||||
|
||||
b.HasIndex("OrganizationId");
|
||||
|
||||
b.HasIndex("StatusId");
|
||||
|
||||
b.HasIndex("SupplierId");
|
||||
|
||||
b.HasIndex("TenantId");
|
||||
|
||||
b.HasIndex("UpdatedById");
|
||||
|
||||
b.ToTable("PurchaseInvoiceDetails");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Marco.Pms.Model.PurchaseInvoice.PurchaseInvoicePayment", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<double>("Amount")
|
||||
.HasColumnType("double");
|
||||
|
||||
b.Property<string>("Comment")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<Guid>("CreatedById")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<Guid>("InvoiceId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<bool>("IsActive")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<Guid>("PaymentAdjustmentHeadId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<DateTime>("PaymentReceivedDate")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<Guid>("TenantId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<string>("TransactionId")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("CreatedById");
|
||||
|
||||
b.HasIndex("InvoiceId");
|
||||
|
||||
b.HasIndex("PaymentAdjustmentHeadId");
|
||||
|
||||
b.HasIndex("TenantId");
|
||||
|
||||
b.ToTable("PurchaseInvoicePayments");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Marco.Pms.Model.PurchaseInvoice.PurchaseInvoiceStatus", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<string>("Color")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("DisplayName")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("PurchaseInvoiceStatus");
|
||||
|
||||
b.HasData(
|
||||
new
|
||||
{
|
||||
Id = new Guid("8a5ef25e-3c9e-45de-add9-6b1c1df54381"),
|
||||
Color = "#8592a3",
|
||||
Description = "Draft Status in a Purchase Invoice indicates a preliminary, unfinalized document that is saved for review but has not yet been posted to the general ledger or affected your accounts/inventory.",
|
||||
DisplayName = "Draft",
|
||||
Name = "Draft"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("16b10201-1651-465c-b2fd-236bdef86f95"),
|
||||
Color = "#696cff",
|
||||
Description = "Review Pending status in a Purchase Invoice indicates that the invoice has been submitted for validation but requires approval from an authorized person (like a manager or auditor) before it can be posted to the ledger or paid.",
|
||||
DisplayName = "Submit for Review",
|
||||
Name = "Review Pending"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("a05f5f4a-bd9d-4028-af42-48ee0caa3e40"),
|
||||
Color = "#ff3e1d",
|
||||
Description = "Rejected by Reviewer status indicates that the invoice failed the approval process due to errors, discrepancies, or policy violations and has been returned to the initiator or vendor for correction.",
|
||||
DisplayName = "Reject",
|
||||
Name = "Rejected by Reviewer"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("60027a54-3c23-4619-9f4e-6c20549b50a6"),
|
||||
Color = "#03c3ec",
|
||||
Description = "Approval Pending status in a Purchase Invoice indicates that the document has passed initial verification (matching and coding) and is now awaiting final financial authorization from a designated budget holder or signatory.",
|
||||
DisplayName = "Mark as Reviewed",
|
||||
Name = "Approval Pending"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("58de9cef-811f-46a4-814d-0069b64d98a9"),
|
||||
Color = "#ff3e1d",
|
||||
Description = "Rejected by Approver status in a Purchase Invoice indicates that the document successfully passed initial verification but was ultimately denied by the final authorizing signatory (such as a Manager or CFO) due to budget or validity concerns.",
|
||||
DisplayName = "Reject",
|
||||
Name = "Rejected by Approver"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("5b393371-dbcf-4a28-88a8-f406fa34e0d0"),
|
||||
Color = "#71dd37",
|
||||
Description = "Approved status indicates that the invoice has successfully cleared all necessary verification and authorization levels and is formally accepted by the company as a valid debt.",
|
||||
DisplayName = "Mark as Approved",
|
||||
Name = "Approved"
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Marco.Pms.Model.Roles.ApplicationRole", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
@ -7245,10 +7669,6 @@ namespace Marco.Pms.DataAccess.Migrations
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Marco.Pms.Model.Projects.Project", "Project")
|
||||
.WithMany()
|
||||
.HasForeignKey("ProjectId");
|
||||
|
||||
b.HasOne("Marco.Pms.Model.TenantModels.Tenant", "Tenant")
|
||||
.WithMany()
|
||||
.HasForeignKey("TenantId")
|
||||
@ -7259,8 +7679,6 @@ namespace Marco.Pms.DataAccess.Migrations
|
||||
|
||||
b.Navigation("Employee");
|
||||
|
||||
b.Navigation("Project");
|
||||
|
||||
b.Navigation("Tenant");
|
||||
});
|
||||
|
||||
@ -8190,6 +8608,168 @@ namespace Marco.Pms.DataAccess.Migrations
|
||||
b.Navigation("WorkCategoryMaster");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Marco.Pms.Model.PurchaseInvoice.DeliveryChallanDetails", b =>
|
||||
{
|
||||
b.HasOne("Marco.Pms.Model.PurchaseInvoice.PurchaseInvoiceAttachment", "Attachment")
|
||||
.WithMany()
|
||||
.HasForeignKey("AttachmentId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Marco.Pms.Model.Employees.Employee", "CreatedBy")
|
||||
.WithMany()
|
||||
.HasForeignKey("CreatedById")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Marco.Pms.Model.PurchaseInvoice.PurchaseInvoiceDetails", "PurchaseInvoice")
|
||||
.WithMany()
|
||||
.HasForeignKey("PurchaseInvoiceId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Marco.Pms.Model.TenantModels.Tenant", "Tenant")
|
||||
.WithMany()
|
||||
.HasForeignKey("TenantId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Attachment");
|
||||
|
||||
b.Navigation("CreatedBy");
|
||||
|
||||
b.Navigation("PurchaseInvoice");
|
||||
|
||||
b.Navigation("Tenant");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Marco.Pms.Model.PurchaseInvoice.PurchaseInvoiceAttachment", b =>
|
||||
{
|
||||
b.HasOne("Marco.Pms.Model.DocumentManager.Document", "Document")
|
||||
.WithMany()
|
||||
.HasForeignKey("DocumentId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Marco.Pms.Model.PurchaseInvoice.InvoiceAttachmentType", "InvoiceAttachmentType")
|
||||
.WithMany()
|
||||
.HasForeignKey("InvoiceAttachmentTypeId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Marco.Pms.Model.PurchaseInvoice.PurchaseInvoiceDetails", "PurchaseInvoice")
|
||||
.WithMany()
|
||||
.HasForeignKey("PurchaseInvoiceId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Marco.Pms.Model.TenantModels.Tenant", "Tenant")
|
||||
.WithMany()
|
||||
.HasForeignKey("TenantId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Marco.Pms.Model.Employees.Employee", "UploadedBy")
|
||||
.WithMany()
|
||||
.HasForeignKey("UploadedById")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Document");
|
||||
|
||||
b.Navigation("InvoiceAttachmentType");
|
||||
|
||||
b.Navigation("PurchaseInvoice");
|
||||
|
||||
b.Navigation("Tenant");
|
||||
|
||||
b.Navigation("UploadedBy");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Marco.Pms.Model.PurchaseInvoice.PurchaseInvoiceDetails", b =>
|
||||
{
|
||||
b.HasOne("Marco.Pms.Model.Employees.Employee", "CreatedBy")
|
||||
.WithMany()
|
||||
.HasForeignKey("CreatedById")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Marco.Pms.Model.OrganizationModel.Organization", "Organization")
|
||||
.WithMany()
|
||||
.HasForeignKey("OrganizationId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Marco.Pms.Model.PurchaseInvoice.PurchaseInvoiceStatus", "Status")
|
||||
.WithMany()
|
||||
.HasForeignKey("StatusId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Marco.Pms.Model.OrganizationModel.Organization", "Supplier")
|
||||
.WithMany()
|
||||
.HasForeignKey("SupplierId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Marco.Pms.Model.TenantModels.Tenant", "Tenant")
|
||||
.WithMany()
|
||||
.HasForeignKey("TenantId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Marco.Pms.Model.Employees.Employee", "UpdatedBy")
|
||||
.WithMany()
|
||||
.HasForeignKey("UpdatedById");
|
||||
|
||||
b.Navigation("CreatedBy");
|
||||
|
||||
b.Navigation("Organization");
|
||||
|
||||
b.Navigation("Status");
|
||||
|
||||
b.Navigation("Supplier");
|
||||
|
||||
b.Navigation("Tenant");
|
||||
|
||||
b.Navigation("UpdatedBy");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Marco.Pms.Model.PurchaseInvoice.PurchaseInvoicePayment", b =>
|
||||
{
|
||||
b.HasOne("Marco.Pms.Model.Employees.Employee", "CreatedBy")
|
||||
.WithMany()
|
||||
.HasForeignKey("CreatedById")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Marco.Pms.Model.PurchaseInvoice.PurchaseInvoiceDetails", "Invoice")
|
||||
.WithMany()
|
||||
.HasForeignKey("InvoiceId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Marco.Pms.Model.Collection.PaymentAdjustmentHead", "PaymentAdjustmentHead")
|
||||
.WithMany()
|
||||
.HasForeignKey("PaymentAdjustmentHeadId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Marco.Pms.Model.TenantModels.Tenant", "Tenant")
|
||||
.WithMany()
|
||||
.HasForeignKey("TenantId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("CreatedBy");
|
||||
|
||||
b.Navigation("Invoice");
|
||||
|
||||
b.Navigation("PaymentAdjustmentHead");
|
||||
|
||||
b.Navigation("Tenant");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Marco.Pms.Model.Roles.ApplicationRole", b =>
|
||||
{
|
||||
b.HasOne("Marco.Pms.Model.TenantModels.Tenant", null)
|
||||
|
||||
34
Marco.Pms.Helpers/Utility/MailLogHelper.cs
Normal file
34
Marco.Pms.Helpers/Utility/MailLogHelper.cs
Normal file
@ -0,0 +1,34 @@
|
||||
using Marco.Pms.Model.Mail;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using MongoDB.Driver;
|
||||
|
||||
namespace Marco.Pms.Helpers.Utility
|
||||
{
|
||||
public class MailLogHelper
|
||||
{
|
||||
private readonly IMongoCollection<MailLog> _collection;
|
||||
private readonly ILogger<MailLogHelper> _logger;
|
||||
public MailLogHelper(IConfiguration configuration, ILogger<MailLogHelper> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
var connectionString = configuration["MongoDB:MailConnectionString"];
|
||||
var mongoUrl = new MongoUrl(connectionString);
|
||||
var client = new MongoClient(mongoUrl); // Your MongoDB connection string
|
||||
var mongoDB = client.GetDatabase(mongoUrl.DatabaseName); // Your MongoDB Database name
|
||||
_collection = mongoDB.GetCollection<MailLog>("MailLogs");
|
||||
}
|
||||
|
||||
public async Task AddWebMenuItemAsync(List<MailLog> mailLogs)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _collection.InsertManyAsync(mailLogs);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error occurred while adding Mail Logs.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,14 +1,14 @@
|
||||
using Marco.Pms.Model.AppMenu;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using MongoDB.Bson;
|
||||
using MongoDB.Driver;
|
||||
|
||||
namespace Marco.Pms.CacheHelper
|
||||
{
|
||||
public class SidebarMenuHelper
|
||||
{
|
||||
private readonly IMongoCollection<MenuSection> _collection;
|
||||
private readonly IMongoCollection<WebSideMenuItem> _webCollection;
|
||||
private readonly IMongoCollection<MobileMenu> _mobileCollection;
|
||||
private readonly ILogger<SidebarMenuHelper> _logger;
|
||||
|
||||
public SidebarMenuHelper(IConfiguration configuration, ILogger<SidebarMenuHelper> logger)
|
||||
@ -18,206 +18,94 @@ namespace Marco.Pms.CacheHelper
|
||||
var mongoUrl = new MongoUrl(connectionString);
|
||||
var client = new MongoClient(mongoUrl);
|
||||
var database = client.GetDatabase(mongoUrl.DatabaseName);
|
||||
_collection = database.GetCollection<MenuSection>("Menus");
|
||||
_webCollection = database.GetCollection<WebSideMenuItem>("WebSideMenus");
|
||||
_mobileCollection = database.GetCollection<MobileMenu>("MobileSideMenus");
|
||||
|
||||
}
|
||||
|
||||
public async Task<MenuSection?> CreateMenuSectionAsync(MenuSection section)
|
||||
public async Task<List<WebSideMenuItem>> GetAllWebMenuSectionsAsync(Guid tenantId)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _collection.InsertOneAsync(section);
|
||||
return section;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error occurred while adding MenuSection.");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
var filter = Builders<WebSideMenuItem>.Filter.Eq(e => e.TenantId, tenantId);
|
||||
|
||||
public async Task<MenuSection?> UpdateMenuSectionAsync(Guid sectionId, MenuSection updatedSection)
|
||||
{
|
||||
try
|
||||
{
|
||||
var filter = Builders<MenuSection>.Filter.Eq(s => s.Id, sectionId);
|
||||
|
||||
var update = Builders<MenuSection>.Update
|
||||
.Set(s => s.Header, updatedSection.Header)
|
||||
.Set(s => s.Title, updatedSection.Title)
|
||||
.Set(s => s.Items, updatedSection.Items);
|
||||
|
||||
var result = await _collection.UpdateOneAsync(filter, update);
|
||||
|
||||
if (result.ModifiedCount > 0)
|
||||
var result = await _webCollection
|
||||
.Find(filter)
|
||||
.ToListAsync();
|
||||
if (result.Any())
|
||||
{
|
||||
return await _collection.Find(s => s.Id == sectionId).FirstOrDefaultAsync();
|
||||
return result;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error updating MenuSection.");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
tenantId = Guid.Parse("b3466e83-7e11-464c-b93a-daf047838b26");
|
||||
filter = Builders<WebSideMenuItem>.Filter.Eq(e => e.TenantId, tenantId);
|
||||
|
||||
public async Task<MenuSection?> AddMenuItemAsync(Guid sectionId, MenuItem newItem)
|
||||
{
|
||||
try
|
||||
{
|
||||
newItem.Id = Guid.NewGuid();
|
||||
|
||||
var filter = Builders<MenuSection>.Filter.Eq(s => s.Id, sectionId);
|
||||
|
||||
var update = Builders<MenuSection>.Update.Push(s => s.Items, newItem);
|
||||
|
||||
var result = await _collection.UpdateOneAsync(filter, update);
|
||||
|
||||
if (result.ModifiedCount > 0)
|
||||
{
|
||||
return await _collection.Find(s => s.Id == sectionId).FirstOrDefaultAsync();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error adding menu item.");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<MenuItem?> UpdateMenuItemAsync(Guid sectionId, Guid itemId, MenuItem updatedItem)
|
||||
{
|
||||
try
|
||||
{
|
||||
var filter = Builders<MenuSection>.Filter.And(
|
||||
Builders<MenuSection>.Filter.Eq(s => s.Id, sectionId),
|
||||
Builders<MenuSection>.Filter.ElemMatch(s => s.Items, i => i.Id == itemId)
|
||||
);
|
||||
|
||||
var update = Builders<MenuSection>.Update
|
||||
.Set("Items.$.Text", updatedItem.Text)
|
||||
.Set("Items.$.Icon", updatedItem.Icon)
|
||||
.Set("Items.$.Available", updatedItem.Available)
|
||||
.Set("Items.$.Link", updatedItem.Link)
|
||||
.Set("Items.$.PermissionIds", updatedItem.PermissionIds);
|
||||
|
||||
var result = await _collection.UpdateOneAsync(filter, update);
|
||||
|
||||
if (result.ModifiedCount > 0)
|
||||
{
|
||||
// Re-fetch section and return the updated item
|
||||
var section = await _collection.Find(s => s.Id == sectionId).FirstOrDefaultAsync();
|
||||
return section?.Items.FirstOrDefault(i => i.Id == itemId);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error updating MenuItem.");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<MenuSection?> AddSubMenuItemAsync(Guid sectionId, Guid itemId, SubMenuItem newSubItem)
|
||||
{
|
||||
try
|
||||
{
|
||||
newSubItem.Id = Guid.NewGuid();
|
||||
|
||||
// Match the MenuSection and the specific MenuItem inside it
|
||||
var filter = Builders<MenuSection>.Filter.And(
|
||||
Builders<MenuSection>.Filter.Eq(s => s.Id, sectionId),
|
||||
Builders<MenuSection>.Filter.ElemMatch(s => s.Items, i => i.Id == itemId)
|
||||
);
|
||||
|
||||
// Use positional operator `$` to target matched MenuItem and push into its Submenu
|
||||
var update = Builders<MenuSection>.Update.Push("Items.$.Submenu", newSubItem);
|
||||
|
||||
var result = await _collection.UpdateOneAsync(filter, update);
|
||||
|
||||
if (result.ModifiedCount > 0)
|
||||
{
|
||||
return await _collection.Find(s => s.Id == sectionId).FirstOrDefaultAsync();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error adding submenu item.");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<SubMenuItem?> UpdateSubmenuItemAsync(Guid sectionId, Guid itemId, Guid subItemId, SubMenuItem updatedSub)
|
||||
{
|
||||
try
|
||||
{
|
||||
var filter = Builders<MenuSection>.Filter.Eq(s => s.Id, sectionId);
|
||||
|
||||
var arrayFilters = new List<ArrayFilterDefinition>
|
||||
{
|
||||
new BsonDocumentArrayFilterDefinition<BsonDocument>(
|
||||
new BsonDocument("item._id", itemId.ToString())),
|
||||
new BsonDocumentArrayFilterDefinition<BsonDocument>(
|
||||
new BsonDocument("sub._id", subItemId.ToString()))
|
||||
};
|
||||
|
||||
var update = Builders<MenuSection>.Update
|
||||
.Set("Items.$[item].Submenu.$[sub].Text", updatedSub.Text)
|
||||
.Set("Items.$[item].Submenu.$[sub].Available", updatedSub.Available)
|
||||
.Set("Items.$[item].Submenu.$[sub].Link", updatedSub.Link)
|
||||
.Set("Items.$[item].Submenu.$[sub].PermissionKeys", updatedSub.PermissionIds);
|
||||
|
||||
var options = new UpdateOptions { ArrayFilters = arrayFilters };
|
||||
|
||||
var result = await _collection.UpdateOneAsync(filter, update, options);
|
||||
|
||||
if (result.ModifiedCount == 0)
|
||||
return null;
|
||||
|
||||
var updatedSection = await _collection.Find(x => x.Id == sectionId).FirstOrDefaultAsync();
|
||||
|
||||
var subItem = updatedSection?.Items
|
||||
.FirstOrDefault(i => i.Id == itemId)?
|
||||
.Submenu
|
||||
.FirstOrDefault(s => s.Id == subItemId);
|
||||
|
||||
return subItem;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error updating SubMenuItem.");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public async Task<List<MenuSection>> GetAllMenuSectionsAsync(Guid tenantId)
|
||||
{
|
||||
var filter = Builders<MenuSection>.Filter.Eq(e => e.TenantId, tenantId);
|
||||
|
||||
var result = await _collection
|
||||
.Find(filter)
|
||||
.ToListAsync();
|
||||
if (result.Any())
|
||||
{
|
||||
result = await _webCollection
|
||||
.Find(filter)
|
||||
.ToListAsync();
|
||||
return result;
|
||||
}
|
||||
|
||||
tenantId = Guid.Parse("b3466e83-7e11-464c-b93a-daf047838b26");
|
||||
filter = Builders<MenuSection>.Filter.Eq(e => e.TenantId, tenantId);
|
||||
|
||||
result = await _collection
|
||||
.Find(filter)
|
||||
.ToListAsync();
|
||||
return result;
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error occurred while fetching Web Menu Sections.");
|
||||
return new List<WebSideMenuItem>();
|
||||
}
|
||||
}
|
||||
public async Task<List<WebSideMenuItem>> AddWebMenuItemAsync(List<WebSideMenuItem> newItems)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _webCollection.InsertManyAsync(newItems);
|
||||
return newItems;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error occurred while adding Web Menu Section.");
|
||||
return new List<WebSideMenuItem>();
|
||||
}
|
||||
}
|
||||
public async Task<List<MobileMenu>> GetAllMobileMenuSectionsAsync(Guid tenantId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var filter = Builders<MobileMenu>.Filter.Eq(e => e.TenantId, tenantId);
|
||||
|
||||
var result = await _mobileCollection
|
||||
.Find(filter)
|
||||
.ToListAsync();
|
||||
if (result.Any())
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
tenantId = Guid.Parse("b3466e83-7e11-464c-b93a-daf047838b26");
|
||||
filter = Builders<MobileMenu>.Filter.Eq(e => e.TenantId, tenantId);
|
||||
|
||||
result = await _mobileCollection
|
||||
.Find(filter)
|
||||
.ToListAsync();
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error occurred while fetching Web Menu Sections.");
|
||||
return new List<MobileMenu>();
|
||||
}
|
||||
}
|
||||
public async Task<List<MobileMenu>> AddMobileMenuItemAsync(List<MobileMenu> newItems)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _mobileCollection.InsertManyAsync(newItems);
|
||||
return newItems;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error occurred while adding Mobile Menu Section.");
|
||||
return new List<MobileMenu>();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
21
Marco.Pms.Model/AppMenu/MobileMenu.cs
Normal file
21
Marco.Pms.Model/AppMenu/MobileMenu.cs
Normal file
@ -0,0 +1,21 @@
|
||||
using MongoDB.Bson;
|
||||
using MongoDB.Bson.Serialization.Attributes;
|
||||
|
||||
namespace Marco.Pms.Model.AppMenu
|
||||
{
|
||||
public class MobileMenu
|
||||
{
|
||||
[BsonId]
|
||||
[BsonRepresentation(BsonType.String)]
|
||||
public Guid Id { get; set; }
|
||||
public string? Name { get; set; }
|
||||
public bool Available { get; set; }
|
||||
public string? MobileLink { get; set; }
|
||||
|
||||
[BsonRepresentation(BsonType.String)]
|
||||
public List<Guid> PermissionIds { get; set; } = new List<Guid>();
|
||||
|
||||
[BsonRepresentation(BsonType.String)]
|
||||
public Guid TenantId { get; set; }
|
||||
}
|
||||
}
|
||||
19
Marco.Pms.Model/AppMenu/WebMenuSection.cs
Normal file
19
Marco.Pms.Model/AppMenu/WebMenuSection.cs
Normal file
@ -0,0 +1,19 @@
|
||||
using MongoDB.Bson;
|
||||
using MongoDB.Bson.Serialization.Attributes;
|
||||
|
||||
namespace Marco.Pms.Model.AppMenu
|
||||
{
|
||||
public class WebMenuSection
|
||||
{
|
||||
[BsonId]
|
||||
[BsonRepresentation(BsonType.String)]
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
|
||||
public string? Header { get; set; }
|
||||
public string? Name { get; set; }
|
||||
public List<WebSideMenuItem> Items { get; set; } = new List<WebSideMenuItem>();
|
||||
|
||||
[BsonRepresentation(BsonType.String)]
|
||||
public Guid TenantId { get; set; }
|
||||
}
|
||||
}
|
||||
25
Marco.Pms.Model/AppMenu/WebSideMenuItem.cs
Normal file
25
Marco.Pms.Model/AppMenu/WebSideMenuItem.cs
Normal file
@ -0,0 +1,25 @@
|
||||
using MongoDB.Bson;
|
||||
using MongoDB.Bson.Serialization.Attributes;
|
||||
|
||||
namespace Marco.Pms.Model.AppMenu
|
||||
{
|
||||
public class WebSideMenuItem
|
||||
{
|
||||
[BsonId]
|
||||
[BsonRepresentation(BsonType.String)]
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
|
||||
[BsonRepresentation(BsonType.String)]
|
||||
public Guid? ParentMenuId { get; set; }
|
||||
public string? Name { get; set; }
|
||||
public string? Icon { get; set; }
|
||||
public bool Available { get; set; } = true;
|
||||
public string Link { get; set; } = string.Empty;
|
||||
|
||||
[BsonRepresentation(BsonType.String)]
|
||||
public List<Guid> PermissionIds { get; set; } = new List<Guid>();
|
||||
|
||||
[BsonRepresentation(BsonType.String)]
|
||||
public Guid TenantId { get; set; }
|
||||
}
|
||||
}
|
||||
@ -1,17 +0,0 @@
|
||||
namespace Marco.Pms.Model.Dtos.AppMenu
|
||||
{
|
||||
public class CreateMenuItemDto
|
||||
{
|
||||
public required string Text { get; set; }
|
||||
public required string Icon { get; set; }
|
||||
public bool Available { get; set; } = true;
|
||||
|
||||
public required string Link { get; set; }
|
||||
public string? MobileLink { get; set; }
|
||||
|
||||
// Changed from string → List<string>
|
||||
public List<string> PermissionIds { get; set; } = new List<string>();
|
||||
|
||||
public List<CreateSubMenuItemDto> Submenu { get; set; } = new List<CreateSubMenuItemDto>();
|
||||
}
|
||||
}
|
||||
@ -1,9 +0,0 @@
|
||||
namespace Marco.Pms.Model.Dtos.AppMenu
|
||||
{
|
||||
public class CreateMenuSectionDto
|
||||
{
|
||||
public required string Header { get; set; }
|
||||
public required string Title { get; set; }
|
||||
public List<CreateMenuItemDto> Items { get; set; } = new List<CreateMenuItemDto>();
|
||||
}
|
||||
}
|
||||
13
Marco.Pms.Model/Dtos/AppMenu/CreateMobileSideMenuItemDto.cs
Normal file
13
Marco.Pms.Model/Dtos/AppMenu/CreateMobileSideMenuItemDto.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using MongoDB.Bson;
|
||||
using MongoDB.Bson.Serialization.Attributes;
|
||||
|
||||
namespace Marco.Pms.Model.Dtos.AppMenu
|
||||
{
|
||||
public class CreateMobileSideMenuItemDto
|
||||
{
|
||||
public string? Name { get; set; }
|
||||
public bool Available { get; set; }
|
||||
public string? MobileLink { get; set; }
|
||||
public List<Guid> PermissionIds { get; set; } = new List<Guid>();
|
||||
}
|
||||
}
|
||||
@ -1,13 +0,0 @@
|
||||
namespace Marco.Pms.Model.Dtos.AppMenu
|
||||
{
|
||||
public class CreateSubMenuItemDto
|
||||
{
|
||||
public required string Text { get; set; }
|
||||
public bool Available { get; set; } = true;
|
||||
|
||||
public required string Link { get; set; } = string.Empty;
|
||||
public string? MobileLink { get; set; }
|
||||
// Changed from string → List<string>
|
||||
public List<string> PermissionIds { get; set; } = new List<string>();
|
||||
}
|
||||
}
|
||||
@ -1,9 +1,9 @@
|
||||
namespace Marco.Pms.Model.Dtos.AppMenu
|
||||
{
|
||||
public class UpdateMenuSectionDto
|
||||
public class CreateWebMenuSectionDto
|
||||
{
|
||||
public required Guid Id { get; set; }
|
||||
public required string Header { get; set; }
|
||||
public required string Title { get; set; }
|
||||
public List<CreateWebSideMenuItemDto> Items { get; set; } = new List<CreateWebSideMenuItemDto>();
|
||||
}
|
||||
}
|
||||
15
Marco.Pms.Model/Dtos/AppMenu/CreateWebSideMenuItemDto.cs
Normal file
15
Marco.Pms.Model/Dtos/AppMenu/CreateWebSideMenuItemDto.cs
Normal file
@ -0,0 +1,15 @@
|
||||
namespace Marco.Pms.Model.Dtos.AppMenu
|
||||
{
|
||||
public class CreateWebSideMenuItemDto
|
||||
{
|
||||
public Guid? Id { get; set; }
|
||||
public Guid? ParentMenuId { get; set; }
|
||||
public string? Name { get; set; }
|
||||
public string? Icon { get; set; }
|
||||
public bool Available { get; set; } = true;
|
||||
public string Link { get; set; } = string.Empty;
|
||||
|
||||
// Changed from string → List<string>
|
||||
public List<Guid> PermissionIds { get; set; } = new List<Guid>();
|
||||
}
|
||||
}
|
||||
@ -1,17 +0,0 @@
|
||||
namespace Marco.Pms.Model.Dtos.AppMenu
|
||||
{
|
||||
public class UpdateMenuItemDto
|
||||
{
|
||||
public required Guid Id { get; set; }
|
||||
|
||||
public required string Text { get; set; }
|
||||
public required string Icon { get; set; }
|
||||
public bool Available { get; set; } = true;
|
||||
|
||||
public required string Link { get; set; }
|
||||
public string? MobileLink { get; set; }
|
||||
|
||||
// Changed from string → List<string>
|
||||
public List<string> PermissionIds { get; set; } = new List<string>();
|
||||
}
|
||||
}
|
||||
@ -1,16 +0,0 @@
|
||||
namespace Marco.Pms.Model.Dtos.AppMenu
|
||||
{
|
||||
public class UpdateSubMenuItemDto
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
|
||||
public string? Text { get; set; }
|
||||
public bool Available { get; set; } = true;
|
||||
|
||||
public string Link { get; set; } = string.Empty;
|
||||
public string? MobileLink { get; set; }
|
||||
|
||||
// Changed from string → List<string>
|
||||
public List<string> PermissionIds { get; set; } = new List<string>();
|
||||
}
|
||||
}
|
||||
@ -9,8 +9,8 @@ namespace Marco.Pms.Model.Dtos.Project
|
||||
public Guid WorkAreaID { get; set; }
|
||||
public Guid WorkCategoryId { get; set; }
|
||||
public Guid ActivityID { get; set; }
|
||||
public int PlannedWork { get; set; }
|
||||
public int CompletedWork { get; set; }
|
||||
public double PlannedWork { get; set; }
|
||||
public double CompletedWork { get; set; }
|
||||
public Guid? ParentTaskId { get; set; }
|
||||
public string? Comment { get; set; }
|
||||
}
|
||||
|
||||
11
Marco.Pms.Model/Dtos/PurchaseInvoice/DeliveryChallanDto.cs
Normal file
11
Marco.Pms.Model/Dtos/PurchaseInvoice/DeliveryChallanDto.cs
Normal file
@ -0,0 +1,11 @@
|
||||
namespace Marco.Pms.Model.Dtos.PurchaseInvoice
|
||||
{
|
||||
public class DeliveryChallanDto
|
||||
{
|
||||
public required string DeliveryChallanNumber { get; set; }
|
||||
public required DateTime DeliveryChallanDate { get; set; }
|
||||
public required string Description { get; set; }
|
||||
public required Guid PurchaseInvoiceId { get; set; }
|
||||
public required InvoiceAttachmentDto Attachment { get; set; }
|
||||
}
|
||||
}
|
||||
14
Marco.Pms.Model/Dtos/PurchaseInvoice/InvoiceAttachmentDto.cs
Normal file
14
Marco.Pms.Model/Dtos/PurchaseInvoice/InvoiceAttachmentDto.cs
Normal file
@ -0,0 +1,14 @@
|
||||
namespace Marco.Pms.Model.Dtos.PurchaseInvoice
|
||||
{
|
||||
public class InvoiceAttachmentDto
|
||||
{
|
||||
public Guid? DocumentId { get; set; }
|
||||
public required Guid InvoiceAttachmentTypeId { get; set; }
|
||||
public required string FileName { get; set; } // Name of the file (e.g., "image1.png")
|
||||
public string? Base64Data { get; set; } // Base64-encoded string of the file
|
||||
public string? ContentType { get; set; } // MIME type (e.g., "image/png", "application/pdf")
|
||||
public long FileSize { get; set; } // File size in bytes
|
||||
public string? Description { get; set; } // Optional: Description or purpose of the file
|
||||
public required bool IsActive { get; set; }
|
||||
}
|
||||
}
|
||||
32
Marco.Pms.Model/Dtos/PurchaseInvoice/PurchaseInvoiceDto.cs
Normal file
32
Marco.Pms.Model/Dtos/PurchaseInvoice/PurchaseInvoiceDto.cs
Normal file
@ -0,0 +1,32 @@
|
||||
namespace Marco.Pms.Model.Dtos.PurchaseInvoice
|
||||
{
|
||||
public class PurchaseInvoiceDto
|
||||
{
|
||||
public required string Title { get; set; }
|
||||
public required string Description { get; set; }
|
||||
public required Guid ProjectId { get; set; }
|
||||
public required Guid OrganizationId { get; set; }
|
||||
public required string BillingAddress { get; set; }
|
||||
public required string ShippingAddress { get; set; }
|
||||
public string? PurchaseOrderNumber { get; set; }
|
||||
public DateTime? PurchaseOrderDate { get; set; }
|
||||
public Guid? StatusId { get; set; }
|
||||
public required Guid SupplierId { get; set; }
|
||||
public string? ProformaInvoiceNumber { get; set; }
|
||||
public DateTime? ProformaInvoiceDate { get; set; }
|
||||
public double? ProformaInvoiceAmount { get; set; }
|
||||
public string? InvoiceNumber { get; set; }
|
||||
public DateTime? InvoiceDate { get; set; }
|
||||
public string? EWayBillNumber { get; set; }
|
||||
public DateTime? EWayBillDate { get; set; }
|
||||
public string? InvoiceReferenceNumber { get; set; }
|
||||
public string? AcknowledgmentNumber { get; set; }
|
||||
public DateTime? AcknowledgmentDate { get; set; }
|
||||
public required double BaseAmount { get; set; }
|
||||
public required double TaxAmount { get; set; }
|
||||
public double? TransportCharges { get; set; }
|
||||
public required double TotalAmount { get; set; }
|
||||
public DateTime? PaymentDueDate { get; set; } // Defaults to 40 days from the invoice date
|
||||
public List<InvoiceAttachmentDto> Attachments { get; set; } = new List<InvoiceAttachmentDto>();
|
||||
}
|
||||
}
|
||||
@ -60,6 +60,11 @@
|
||||
public static readonly Guid AddOrganization = Guid.Parse("068cb3c1-49c5-4746-9f29-1fce16e820ac");
|
||||
public static readonly Guid EditOrganization = Guid.Parse("c1ae1363-ab8a-4bd9-a9d1-8c2c6083873a");
|
||||
public static readonly Guid ViewOrganization = Guid.Parse("7a6cf830-0008-4e03-b31d-0d050cb634f4");
|
||||
|
||||
public static readonly Guid ViewSelfPurchaseInvoice = Guid.Parse("91e09825-512a-465e-82ad-fa355b305585");
|
||||
public static readonly Guid ViewAllPurchaseInvoice = Guid.Parse("d6ae78d3-a941-4cc4-8d0a-d40479be4211");
|
||||
public static readonly Guid ManagePurchaseInvoice = Guid.Parse("68ff925d-8ebf-4034-a137-8d3317c56ca1");
|
||||
public static readonly Guid DeletePurchaseInvoice = Guid.Parse("a4b77638-bf31-42bb-afd4-d5bbd15ccadc");
|
||||
public static readonly Guid AddDeliveryChallan = Guid.Parse("b24eba39-4a92-4f7a-b33b-b5308fbc48b9");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
using Marco.Pms.Model.Employees;
|
||||
using Marco.Pms.Model.Projects;
|
||||
using Marco.Pms.Model.Utilities;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
@ -13,10 +12,6 @@ namespace Marco.Pms.Model.Expenses
|
||||
public int FinanceUIdPostfix { get; set; }
|
||||
public string Title { get; set; } = default!;
|
||||
public Guid? ProjectId { get; set; }
|
||||
|
||||
[ValidateNever]
|
||||
[ForeignKey("ProjectId")]
|
||||
public Project? Project { get; set; }
|
||||
public Guid EmployeeId { get; set; }
|
||||
|
||||
[ValidateNever]
|
||||
|
||||
@ -3,6 +3,8 @@
|
||||
public class AdvanceFilter
|
||||
{
|
||||
// The dynamic filters from your JSON
|
||||
public DateDynamicFilter? DateFilter { get; set; }
|
||||
public List<ListDynamicFilter>? Filters { get; set; }
|
||||
public List<SortItem>? SortFilters { get; set; }
|
||||
public List<SearchItem>? SearchFilters { get; set; }
|
||||
public List<AdvanceItem>? AdvanceFilters { get; set; }
|
||||
|
||||
9
Marco.Pms.Model/Filters/DateDynamicFilter.cs
Normal file
9
Marco.Pms.Model/Filters/DateDynamicFilter.cs
Normal file
@ -0,0 +1,9 @@
|
||||
namespace Marco.Pms.Model.Filters
|
||||
{
|
||||
public class DateDynamicFilter
|
||||
{
|
||||
public string Column { get; set; } = string.Empty;
|
||||
public DateTime StartValue { get; set; }
|
||||
public DateTime EndValue { get; set; }
|
||||
}
|
||||
}
|
||||
8
Marco.Pms.Model/Filters/ListDynamicFilter.cs
Normal file
8
Marco.Pms.Model/Filters/ListDynamicFilter.cs
Normal file
@ -0,0 +1,8 @@
|
||||
namespace Marco.Pms.Model.Filters
|
||||
{
|
||||
public class ListDynamicFilter
|
||||
{
|
||||
public string Column { get; set; } = string.Empty;
|
||||
public List<Guid> Values { get; set; } = new List<Guid>();
|
||||
}
|
||||
}
|
||||
75
Marco.Pms.Model/PurchaseInvoice/DeliveryChallanDetails.cs
Normal file
75
Marco.Pms.Model/PurchaseInvoice/DeliveryChallanDetails.cs
Normal file
@ -0,0 +1,75 @@
|
||||
using Marco.Pms.Model.Employees;
|
||||
using Marco.Pms.Model.Utilities;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace Marco.Pms.Model.PurchaseInvoice
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a detail of a delivery challan in the Purchase Invoice system.
|
||||
/// This class inherits from the TenantRelation class and adds specific properties related to a delivery challan.
|
||||
/// </summary>
|
||||
public class DeliveryChallanDetails : TenantRelation
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the unique identifier for the delivery challan.
|
||||
/// </summary>
|
||||
public Guid Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the delivery challan number.
|
||||
/// </summary>
|
||||
public string DeliveryChallanNumber { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the date of the delivery challan.
|
||||
/// </summary>
|
||||
public DateTime DeliveryChallanDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the description of the delivery challan.
|
||||
/// </summary>
|
||||
public string Description { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the unique identifier of the associated PurchaseInvoice.
|
||||
/// </summary>
|
||||
public Guid PurchaseInvoiceId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the associated PurchaseInvoice.
|
||||
/// </summary>
|
||||
[ValidateNever]
|
||||
[ForeignKey("PurchaseInvoiceId")]
|
||||
public PurchaseInvoiceDetails? PurchaseInvoice { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the unique identifier of the associated Attachment.
|
||||
/// </summary>
|
||||
public Guid AttachmentId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the associated Attachment.
|
||||
/// </summary>
|
||||
[ValidateNever]
|
||||
[ForeignKey("AttachmentId")]
|
||||
public PurchaseInvoiceAttachment? Attachment { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the date and time the record was created.
|
||||
/// </summary>
|
||||
public DateTime CreatedAt { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the unique identifier of the user who created the record.
|
||||
/// </summary>
|
||||
public Guid CreatedById { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the user who created the record.
|
||||
/// </summary>
|
||||
[ValidateNever]
|
||||
[ForeignKey("CreatedById")]
|
||||
public Employee? CreatedBy { get; set; }
|
||||
}
|
||||
}
|
||||
9
Marco.Pms.Model/PurchaseInvoice/InvoiceAttachmentType.cs
Normal file
9
Marco.Pms.Model/PurchaseInvoice/InvoiceAttachmentType.cs
Normal file
@ -0,0 +1,9 @@
|
||||
namespace Marco.Pms.Model.PurchaseInvoice
|
||||
{
|
||||
public class InvoiceAttachmentType
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public string Name { get; set; } = default!;
|
||||
public string Description { get; set; } = default!;
|
||||
}
|
||||
}
|
||||
73
Marco.Pms.Model/PurchaseInvoice/PurchaseInvoiceAttachment.cs
Normal file
73
Marco.Pms.Model/PurchaseInvoice/PurchaseInvoiceAttachment.cs
Normal file
@ -0,0 +1,73 @@
|
||||
using Marco.Pms.Model.DocumentManager;
|
||||
using Marco.Pms.Model.Employees;
|
||||
using Marco.Pms.Model.Utilities;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace Marco.Pms.Model.PurchaseInvoice
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an attachment for a purchase invoice.
|
||||
/// </summary>
|
||||
public class PurchaseInvoiceAttachment : TenantRelation
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the unique identifier for the attachment.
|
||||
/// </summary>
|
||||
public Guid Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the unique identifier for the purchase invoice.
|
||||
/// </summary>
|
||||
public Guid PurchaseInvoiceId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the purchase invoice associated with the attachment.
|
||||
/// </summary>
|
||||
[ValidateNever]
|
||||
[ForeignKey("PurchaseInvoiceId")]
|
||||
public PurchaseInvoiceDetails? PurchaseInvoice { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the unique identifier for the type of the invoice attachment.
|
||||
/// </summary>
|
||||
public Guid InvoiceAttachmentTypeId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the type of the invoice attachment.
|
||||
/// </summary>
|
||||
[ValidateNever]
|
||||
[ForeignKey("InvoiceAttachmentTypeId")]
|
||||
public InvoiceAttachmentType? InvoiceAttachmentType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the unique identifier for the document.
|
||||
/// </summary>
|
||||
public Guid DocumentId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the document associated with the attachment.
|
||||
/// </summary>
|
||||
[ValidateNever]
|
||||
[ForeignKey("DocumentId")]
|
||||
public Document? Document { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the date and time when the attachment was uploaded.
|
||||
/// </summary>
|
||||
public DateTime UploadedAt { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the unique identifier of the employee who uploaded the attachment.
|
||||
/// </summary>
|
||||
public Guid UploadedById { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the employee who uploaded the attachment.
|
||||
/// </summary>
|
||||
[ValidateNever]
|
||||
[ForeignKey("UploadedById")]
|
||||
public Employee? UploadedBy { get; set; }
|
||||
}
|
||||
|
||||
}
|
||||
217
Marco.Pms.Model/PurchaseInvoice/PurchaseInvoiceDetails.cs
Normal file
217
Marco.Pms.Model/PurchaseInvoice/PurchaseInvoiceDetails.cs
Normal file
@ -0,0 +1,217 @@
|
||||
using Marco.Pms.Model.Employees;
|
||||
using Marco.Pms.Model.OrganizationModel;
|
||||
using Marco.Pms.Model.Utilities;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace Marco.Pms.Model.PurchaseInvoice
|
||||
{
|
||||
/// <summary>
|
||||
/// The PurchaseInvoiceDetails class represents a detail in a purchase invoice.
|
||||
/// It contains information about the detail such as its unique identifier, title, description, billing and shipping addresses,
|
||||
/// purchase order number and date, supplier information, proforma invoice details, invoice details, e-way bill details,
|
||||
/// invoice reference number, acknowledgment number and date, base amount, tax amount, transport charges, total amount, and payment due date.
|
||||
/// </summary>
|
||||
public class PurchaseInvoiceDetails : TenantRelation
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the unique identifier of the detail.
|
||||
/// </summary>
|
||||
public Guid Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the prefix of the unique identifier for the detail.
|
||||
/// </summary>
|
||||
public string UIDPrefix { get; set; } = default!; // PUR/MMYY/
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the postfix of the unique identifier for the detail.
|
||||
/// </summary>
|
||||
public int UIDPostfix { get; set; } // 00001
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the title of the detail.
|
||||
/// </summary>
|
||||
public string Title { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the description of the detail.
|
||||
/// </summary>
|
||||
public string Description { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the unique identifier of the related project.
|
||||
/// </summary>
|
||||
public Guid ProjectId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the unique identifier of the organization related to the detail.
|
||||
/// </summary>
|
||||
public Guid OrganizationId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the related organization.
|
||||
/// </summary>
|
||||
[ValidateNever]
|
||||
[ForeignKey("OrganizationId")]
|
||||
public Organization? Organization { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the status of the detail.
|
||||
/// </summary>
|
||||
public Guid StatusId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the status of the detail.
|
||||
/// </summary>
|
||||
[ValidateNever]
|
||||
[ForeignKey("StatusId")]
|
||||
public PurchaseInvoiceStatus? Status { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the billing address of the detail.
|
||||
/// </summary>
|
||||
public string BillingAddress { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the shipping address of the detail.
|
||||
/// </summary>
|
||||
public string ShippingAddress { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the purchase order number of the detail.
|
||||
/// </summary>
|
||||
public string? PurchaseOrderNumber { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the purchase order date of the detail.
|
||||
/// </summary>
|
||||
public DateTime? PurchaseOrderDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the unique identifier of the supplier related to the detail.
|
||||
/// </summary>
|
||||
public Guid SupplierId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the related supplier.
|
||||
/// </summary>
|
||||
[ValidateNever]
|
||||
[ForeignKey("SupplierId")]
|
||||
public Organization? Supplier { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the proforma invoice number of the detail.
|
||||
/// </summary>
|
||||
public string? ProformaInvoiceNumber { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the proforma invoice date of the detail.
|
||||
/// </summary>
|
||||
public DateTime? ProformaInvoiceDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the proforma invoice amount of the detail.
|
||||
/// </summary>
|
||||
public double? ProformaInvoiceAmount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the invoice number of the detail.
|
||||
/// </summary>
|
||||
public string? InvoiceNumber { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the invoice date of the detail.
|
||||
/// </summary>
|
||||
public DateTime? InvoiceDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the e-way bill number of the detail.
|
||||
/// </summary>
|
||||
public string? EWayBillNumber { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the e-way bill date of the detail.
|
||||
/// </summary>
|
||||
public DateTime? EWayBillDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the invoice reference number of the detail.
|
||||
/// </summary>
|
||||
public string? InvoiceReferenceNumber { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the acknowledgment number of the detail.
|
||||
/// </summary>
|
||||
public string? AcknowledgmentNumber { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the acknowledgment date of the detail.
|
||||
/// </summary>
|
||||
public DateTime? AcknowledgmentDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the base amount of the detail.
|
||||
/// </summary>
|
||||
public double BaseAmount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the tax amount of the detail.
|
||||
/// </summary>
|
||||
public double TaxAmount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the transport charges of the detail.
|
||||
/// </summary>
|
||||
public double? TransportCharges { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the total amount of the detail.
|
||||
/// </summary>
|
||||
public double TotalAmount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The payment due date of the detail.
|
||||
/// </summary>
|
||||
public DateTime PaymentDueDate { get; set; } // Defaults to 40 days from the invoice date
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the detail is active.
|
||||
/// </summary>
|
||||
public bool IsActive { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the unique identifier of the user who created the detail.
|
||||
/// </summary>
|
||||
public Guid CreatedById { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the user who created the detail.
|
||||
/// </summary>
|
||||
[ValidateNever]
|
||||
[ForeignKey("CreatedById")]
|
||||
public Employee? CreatedBy { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the date and time when the detail was created.
|
||||
/// </summary>
|
||||
public DateTime CreatedAt { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the unique identifier of the user who last updated the detail.
|
||||
/// </summary>
|
||||
public Guid? UpdatedById { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the user who last updated the detail.
|
||||
/// </summary>
|
||||
[ValidateNever]
|
||||
[ForeignKey("UpdatedById")]
|
||||
public Employee? UpdatedBy { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the date and time when the detail was last updated.
|
||||
/// </summary>
|
||||
public DateTime? UpdatedAt { get; set; }
|
||||
|
||||
}
|
||||
}
|
||||
89
Marco.Pms.Model/PurchaseInvoice/PurchaseInvoicePayment.cs
Normal file
89
Marco.Pms.Model/PurchaseInvoice/PurchaseInvoicePayment.cs
Normal file
@ -0,0 +1,89 @@
|
||||
using Marco.Pms.Model.Collection;
|
||||
using Marco.Pms.Model.Employees;
|
||||
using Marco.Pms.Model.Utilities;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace Marco.Pms.Model.PurchaseInvoice
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a payment made against a purchase invoice.
|
||||
/// It is a subclass of TenantRelation, indicating that it is a relation between a tenant and the Marco PMS system.
|
||||
/// </summary>
|
||||
public class PurchaseInvoicePayment : TenantRelation
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the unique identifier of the payment.
|
||||
/// </summary>
|
||||
public Guid Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the unique identifier of the associated purchase invoice.
|
||||
/// </summary>
|
||||
public Guid InvoiceId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the associated purchase invoice.
|
||||
/// This is a navigation property that allows access to the associated PurchaseInvoiceDetails object.
|
||||
/// </summary>
|
||||
[ValidateNever]
|
||||
[ForeignKey("InvoiceId")]
|
||||
public PurchaseInvoiceDetails? Invoice { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the date the payment was received.
|
||||
/// </summary>
|
||||
public DateTime PaymentReceivedDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the transaction ID of the payment.
|
||||
/// </summary>
|
||||
public string TransactionId { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the amount of the payment.
|
||||
/// </summary>
|
||||
public double Amount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a comment about the payment.
|
||||
/// </summary>
|
||||
public string Comment { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the payment is active.
|
||||
/// </summary>
|
||||
public bool IsActive { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the unique identifier of the payment adjustment head.
|
||||
/// </summary>
|
||||
public Guid PaymentAdjustmentHeadId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the associated payment adjustment head.
|
||||
/// This is a navigation property that allows access to the associated PaymentAdjustmentHead object.
|
||||
/// </summary>
|
||||
[ValidateNever]
|
||||
[ForeignKey("PaymentAdjustmentHeadId")]
|
||||
public PaymentAdjustmentHead? PaymentAdjustmentHead { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the date and time the record was created.
|
||||
/// </summary>
|
||||
public DateTime CreatedAt { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the unique identifier of the user who created the record.
|
||||
/// </summary>
|
||||
public Guid CreatedById { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the user who created the record.
|
||||
/// This is a navigation property that allows access to the associated Employee object.
|
||||
/// </summary>
|
||||
[ValidateNever]
|
||||
[ForeignKey("CreatedById")]
|
||||
public Employee? CreatedBy { get; set; }
|
||||
}
|
||||
}
|
||||
11
Marco.Pms.Model/PurchaseInvoice/PurchaseInvoiceStatus.cs
Normal file
11
Marco.Pms.Model/PurchaseInvoice/PurchaseInvoiceStatus.cs
Normal file
@ -0,0 +1,11 @@
|
||||
namespace Marco.Pms.Model.PurchaseInvoice
|
||||
{
|
||||
public class PurchaseInvoiceStatus
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public string Name { get; set; } = default!;
|
||||
public string DisplayName { get; set; } = default!;
|
||||
public string Color { get; set; } = default!;
|
||||
public string Description { get; set; } = default!;
|
||||
}
|
||||
}
|
||||
11
Marco.Pms.Model/ViewModels/AppMenu/WebMenuSectionVM.cs
Normal file
11
Marco.Pms.Model/ViewModels/AppMenu/WebMenuSectionVM.cs
Normal file
@ -0,0 +1,11 @@
|
||||
namespace Marco.Pms.Model.ViewModels.AppMenu
|
||||
{
|
||||
public class WebMenuSectionVM
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
|
||||
public string? Header { get; set; }
|
||||
public string? Name { get; set; }
|
||||
public List<WebSideMenuItemVM> Items { get; set; } = new List<WebSideMenuItemVM>();
|
||||
}
|
||||
}
|
||||
@ -1,13 +1,12 @@
|
||||
namespace Marco.Pms.Model.ViewModels.DocumentManager
|
||||
namespace Marco.Pms.Model.ViewModels.AppMenu
|
||||
{
|
||||
public class MenuItemVM
|
||||
public class WebSideMenuItemVM
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
|
||||
public string? Name { get; set; }
|
||||
public string? Icon { get; set; }
|
||||
public bool Available { get; set; }
|
||||
public string? Link { get; set; }
|
||||
public List<SubMenuItemVM> Submenu { get; set; } = new List<SubMenuItemVM>();
|
||||
public List<WebSideMenuItemVM> Submenu { get; set; } = new List<WebSideMenuItemVM>();
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
namespace Marco.Pms.Model.ViewModels.DashBoard
|
||||
{
|
||||
public class EmployeeAttendanceVM
|
||||
public class DashBoardEmployeeAttendanceVM
|
||||
{
|
||||
public string? FirstName { get; set; }
|
||||
public string? LastName { get; set; }
|
||||
@ -2,7 +2,7 @@
|
||||
{
|
||||
public class ProjectAttendanceVM
|
||||
{
|
||||
public List<EmployeeAttendanceVM>? AttendanceTable { get; set; }
|
||||
public List<DashBoardEmployeeAttendanceVM>? AttendanceTable { get; set; }
|
||||
public int CheckedInEmployee { get; set; }
|
||||
public int AssignedEmployee { get; set; }
|
||||
}
|
||||
|
||||
@ -1,11 +0,0 @@
|
||||
namespace Marco.Pms.Model.ViewModels.DocumentManager
|
||||
{
|
||||
public class MenuSectionVM
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
|
||||
public string? Header { get; set; }
|
||||
public string? Name { get; set; }
|
||||
public List<MenuItemVM> Items { get; set; } = new List<MenuItemVM>();
|
||||
}
|
||||
}
|
||||
@ -1,12 +0,0 @@
|
||||
namespace Marco.Pms.Model.ViewModels.DocumentManager
|
||||
{
|
||||
public class SubMenuItemVM
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
|
||||
public string? Name { get; set; }
|
||||
public bool Available { get; set; }
|
||||
|
||||
public string? Link { get; set; }
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
namespace Marco.Pms.Model.ViewModels.PurchaseInvoice
|
||||
{
|
||||
public class BasicPurchaseInvoiceVM
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public string? Title { get; set; }
|
||||
public string? PurchaseInvoiceUId { get; set; }
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
using Marco.Pms.Model.ViewModels.Activities;
|
||||
|
||||
namespace Marco.Pms.Model.ViewModels.PurchaseInvoice
|
||||
{
|
||||
public class DeliveryChallanVM
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public string? DeliveryChallanNumber { get; set; }
|
||||
public DateTime DeliveryChallanDate { get; set; }
|
||||
public string? Description { get; set; }
|
||||
public BasicPurchaseInvoiceVM? PurchaseInvoice { get; set; }
|
||||
public PurchaseInvoiceAttachmentVM? Attachment { get; set; }
|
||||
public DateTime CreatedAt { get; set; }
|
||||
public BasicEmployeeVM? CreatedBy { get; set; }
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
using Marco.Pms.Model.PurchaseInvoice;
|
||||
|
||||
namespace Marco.Pms.Model.ViewModels.PurchaseInvoice
|
||||
{
|
||||
public class PurchaseInvoiceAttachmentVM
|
||||
{
|
||||
public Guid DocumentId { get; set; }
|
||||
public InvoiceAttachmentType? InvoiceAttachmentType { get; set; }
|
||||
public string? FileName { get; set; }
|
||||
public string? ContentType { get; set; }
|
||||
public string? PreSignedUrl { get; set; }
|
||||
public string? ThumbPreSignedUrl { get; set; }
|
||||
public long FileSize { get; set; }
|
||||
public DateTime UploadedAt { get; set; }
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,44 @@
|
||||
using Marco.Pms.Model.PurchaseInvoice;
|
||||
using Marco.Pms.Model.ViewModels.Activities;
|
||||
using Marco.Pms.Model.ViewModels.Organization;
|
||||
using Marco.Pms.Model.ViewModels.Projects;
|
||||
|
||||
namespace Marco.Pms.Model.ViewModels.PurchaseInvoice
|
||||
{
|
||||
public class PurchaseInvoiceDetailsVM
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public string? PurchaseInvoiceUId { get; set; }
|
||||
public string? Title { get; set; }
|
||||
public string? Description { get; set; }
|
||||
public BasicProjectVM? Project { get; set; }
|
||||
public BasicOrganizationVm? Organization { get; set; }
|
||||
public PurchaseInvoiceStatus? Status { get; set; }
|
||||
public string? BillingAddress { get; set; }
|
||||
public string? ShippingAddress { get; set; }
|
||||
public string? PurchaseOrderNumber { get; set; }
|
||||
public DateTime? PurchaseOrderDate { get; set; }
|
||||
public BasicOrganizationVm? Supplier { get; set; }
|
||||
public string? ProformaInvoiceNumber { get; set; }
|
||||
public DateTime? ProformaInvoiceDate { get; set; }
|
||||
public double? ProformaInvoiceAmount { get; set; }
|
||||
public string? InvoiceNumber { get; set; }
|
||||
public DateTime? InvoiceDate { get; set; }
|
||||
public string? EWayBillNumber { get; set; }
|
||||
public DateTime? EWayBillDate { get; set; }
|
||||
public string? InvoiceReferenceNumber { get; set; }
|
||||
public string? AcknowledgmentNumber { get; set; }
|
||||
public DateTime? AcknowledgmentDate { get; set; }
|
||||
public double BaseAmount { get; set; }
|
||||
public double TaxAmount { get; set; }
|
||||
public double? TransportCharges { get; set; }
|
||||
public double TotalAmount { get; set; }
|
||||
public DateTime PaymentDueDate { get; set; }
|
||||
public bool IsActive { get; set; }
|
||||
public DateTime CreatedAt { get; set; }
|
||||
public BasicEmployeeVM? CreatedBy { get; set; }
|
||||
public DateTime? UpdatedAt { get; set; }
|
||||
public BasicEmployeeVM? UpdatedBy { get; set; }
|
||||
public List<PurchaseInvoiceAttachmentVM>? Attachments { get; set; }
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,21 @@
|
||||
using Marco.Pms.Model.PurchaseInvoice;
|
||||
using Marco.Pms.Model.ViewModels.Organization;
|
||||
using Marco.Pms.Model.ViewModels.Projects;
|
||||
|
||||
namespace Marco.Pms.Model.ViewModels.PurchaseInvoice
|
||||
{
|
||||
public class PurchaseInvoiceListVM
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public string? Title { get; set; }
|
||||
public string? Description { get; set; }
|
||||
public string? PurchaseInvoiceUId { get; set; }
|
||||
public string? ProformaInvoiceNumber { get; set; }
|
||||
public DateTime? ProformaInvoiceDate { get; set; }
|
||||
public double? ProformaInvoiceAmount { get; set; }
|
||||
public BasicProjectVM? Project { get; set; }
|
||||
public BasicOrganizationVm? Supplier { get; set; }
|
||||
public PurchaseInvoiceStatus? Status { get; set; }
|
||||
public double TotalAmount { get; set; }
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -88,7 +88,7 @@ namespace MarcoBMS.Services.Controllers
|
||||
return BadRequest(ApiResponse<object>.ErrorResponse("Employee ID is required and must not be Empty.", "Employee ID is required and must not be empty.", 400));
|
||||
}
|
||||
|
||||
Employee? employee = await _context.Employees.Include(e => e.JobRole).FirstOrDefaultAsync(e => e.Id == employeeId && e.TenantId == tenantId);
|
||||
Employee? employee = await _context.Employees.Include(e => e.JobRole).FirstOrDefaultAsync(e => e.Id == employeeId);
|
||||
if (employee == null)
|
||||
{
|
||||
_logger.LogWarning("Employee {EmployeeId} not found", employeeId);
|
||||
|
||||
@ -1,9 +1,15 @@
|
||||
using Marco.Pms.DataAccess.Data;
|
||||
using AutoMapper;
|
||||
using Marco.Pms.DataAccess.Data;
|
||||
using Marco.Pms.Model.Dtos.Attendance;
|
||||
using Marco.Pms.Model.Entitlements;
|
||||
using Marco.Pms.Model.Expenses;
|
||||
using Marco.Pms.Model.OrganizationModel;
|
||||
using Marco.Pms.Model.Utilities;
|
||||
using Marco.Pms.Model.ViewModels.Activities;
|
||||
using Marco.Pms.Model.ViewModels.AttendanceVM;
|
||||
using Marco.Pms.Model.ViewModels.DashBoard;
|
||||
using Marco.Pms.Model.ViewModels.Organization;
|
||||
using Marco.Pms.Model.ViewModels.Projects;
|
||||
using Marco.Pms.Services.Service;
|
||||
using Marco.Pms.Services.Service.ServiceInterfaces;
|
||||
using MarcoBMS.Services.Helpers;
|
||||
@ -20,12 +26,15 @@ namespace Marco.Pms.Services.Controllers
|
||||
[ApiController]
|
||||
public class DashboardController : ControllerBase
|
||||
{
|
||||
private readonly IDbContextFactory<ApplicationDbContext> _dbContextFactory;
|
||||
private readonly ApplicationDbContext _context;
|
||||
private readonly UserHelper _userHelper;
|
||||
private readonly IProjectServices _projectServices;
|
||||
private readonly ILoggingService _logger;
|
||||
private readonly PermissionServices _permissionServices;
|
||||
private readonly IServiceScopeFactory _serviceScopeFactory;
|
||||
private readonly IMapper _mapper;
|
||||
|
||||
|
||||
public static readonly Guid ActiveId = Guid.Parse("b74da4c2-d07e-46f2-9919-e75e49b12731");
|
||||
private static readonly Guid Draft = Guid.Parse("297e0d8f-f668-41b5-bfea-e03b354251c8");
|
||||
private static readonly Guid Review = Guid.Parse("6537018f-f4e9-4cb3-a210-6c3b2da999d7");
|
||||
@ -40,16 +49,19 @@ namespace Marco.Pms.Services.Controllers
|
||||
IProjectServices projectServices,
|
||||
IServiceScopeFactory serviceScopeFactory,
|
||||
ILoggingService logger,
|
||||
PermissionServices permissionServices)
|
||||
IMapper mapper,
|
||||
IDbContextFactory<ApplicationDbContext> dbContextFactory)
|
||||
{
|
||||
_context = context;
|
||||
_userHelper = userHelper;
|
||||
_projectServices = projectServices;
|
||||
_logger = logger;
|
||||
_serviceScopeFactory = serviceScopeFactory;
|
||||
_permissionServices = permissionServices;
|
||||
_mapper = mapper;
|
||||
tenantId = userHelper.GetTenantId();
|
||||
_dbContextFactory = dbContextFactory;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fetches project progression data (planned and completed tasks) in graph form for a tenant and specified (or all) projects over a date range.
|
||||
/// </summary>
|
||||
@ -254,8 +266,10 @@ namespace Marco.Pms.Services.Controllers
|
||||
|
||||
if (projectId.HasValue)
|
||||
{
|
||||
using var scope = _serviceScopeFactory.CreateScope();
|
||||
var _permission = scope.ServiceProvider.GetRequiredService<PermissionServices>();
|
||||
// Security Check: Ensure the requested project is in the user's accessible list.
|
||||
var hasPermission = await _permissionServices.HasProjectPermission(loggedInEmployee, projectId.Value);
|
||||
var hasPermission = await _permission.HasProjectPermission(loggedInEmployee, projectId.Value);
|
||||
if (!hasPermission)
|
||||
{
|
||||
_logger.LogWarning("Access DENIED for user {UserId} on project {ProjectId} (not active or not accessible).", loggedInEmployee.Id, projectId.Value);
|
||||
@ -340,9 +354,11 @@ namespace Marco.Pms.Services.Controllers
|
||||
if (projectId.HasValue)
|
||||
{
|
||||
// --- Logic for a SINGLE Project ---
|
||||
using var scope = _serviceScopeFactory.CreateScope();
|
||||
var _permission = scope.ServiceProvider.GetRequiredService<PermissionServices>();
|
||||
|
||||
// 2a. Security Check: Verify permission for the specific project.
|
||||
var hasPermission = await _permissionServices.HasProjectPermission(loggedInEmployee, projectId.Value);
|
||||
var hasPermission = await _permission.HasProjectPermission(loggedInEmployee, projectId.Value);
|
||||
if (!hasPermission)
|
||||
{
|
||||
_logger.LogWarning("Access DENIED for user {UserId} on project {ProjectId}.", loggedInEmployee.Id, projectId.Value);
|
||||
@ -499,7 +515,7 @@ namespace Marco.Pms.Services.Controllers
|
||||
return Ok(ApiResponse<object>.SuccessResponse(
|
||||
new ProjectAttendanceVM
|
||||
{
|
||||
AttendanceTable = new List<EmployeeAttendanceVM>(),
|
||||
AttendanceTable = new List<DashBoardEmployeeAttendanceVM>(),
|
||||
CheckedInEmployee = 0,
|
||||
AssignedEmployee = 0
|
||||
},
|
||||
@ -523,7 +539,7 @@ namespace Marco.Pms.Services.Controllers
|
||||
.Join(employees,
|
||||
attendance => attendance.EmployeeId,
|
||||
employee => employee.Id,
|
||||
(attendance, employee) => new EmployeeAttendanceVM
|
||||
(attendance, employee) => new DashBoardEmployeeAttendanceVM
|
||||
{
|
||||
FirstName = employee.FirstName,
|
||||
LastName = employee.LastName,
|
||||
@ -669,7 +685,11 @@ namespace Marco.Pms.Services.Controllers
|
||||
|
||||
// Step 3: Check if logged-in employee has permission for this project
|
||||
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
|
||||
bool hasPermission = await _permissionServices.HasProjectPermission(loggedInEmployee!, projectId);
|
||||
|
||||
using var scope = _serviceScopeFactory.CreateScope();
|
||||
var _permission = scope.ServiceProvider.GetRequiredService<PermissionServices>();
|
||||
|
||||
bool hasPermission = await _permission.HasProjectPermission(loggedInEmployee!, projectId);
|
||||
if (!hasPermission)
|
||||
{
|
||||
_logger.LogWarning("Unauthorized access by EmployeeId: {EmployeeId} to ProjectId: {ProjectId}", loggedInEmployee.Id, projectId);
|
||||
@ -747,7 +767,6 @@ namespace Marco.Pms.Services.Controllers
|
||||
return Ok(ApiResponse<object>.SuccessResponse(sortedResult, $"{sortedResult.Count} records fetched for attendance overview", 200));
|
||||
}
|
||||
|
||||
|
||||
[HttpGet("expense/monthly")]
|
||||
public async Task<IActionResult> GetExpenseReportByProjectsAsync([FromQuery] Guid? projectId, [FromQuery] Guid? categoryId, [FromQuery] int months)
|
||||
{
|
||||
@ -1074,5 +1093,546 @@ namespace Marco.Pms.Services.Controllers
|
||||
ApiResponse<object>.ErrorResponse("An error occurred while fetching pending expenses.", "An error occurred while fetching pending expenses.", 500)); // [Error Response]
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves today's attendance details for a specific employee on a given project,
|
||||
/// defaulting to the currently logged-in employee when no employeeId is provided.
|
||||
/// Includes related project and employee information for UI display.
|
||||
/// </summary>
|
||||
/// <param name="projectId">The project identifier whose attendance is requested.</param>
|
||||
/// <param name="employeeId">
|
||||
/// Optional employee identifier. When null, the currently logged-in employee is used.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// 200 OK with an <see cref="EmployeeAttendanceVM"/> payload on success, or a standardized
|
||||
/// error envelope on validation or processing failure.
|
||||
/// </returns>
|
||||
[HttpGet("get/attendance/employee/{projectId}")]
|
||||
public async Task<IActionResult> GetAttendanceByEmployeeAsync(Guid projectId, [FromQuery] Guid? employeeId)
|
||||
{
|
||||
// TenantId is assumed to come from a base controller, HttpContext, or similar.
|
||||
if (tenantId == Guid.Empty)
|
||||
{
|
||||
_logger.LogWarning("GetAttendanceByEmployeeAsync called with empty TenantId. ProjectId={ProjectId}", projectId);
|
||||
|
||||
return BadRequest(
|
||||
ApiResponse<object>.ErrorResponse("Invalid tenant information.", "TenantId is empty in GetAttendanceByEmployeeAsync.", 400));
|
||||
}
|
||||
|
||||
if (projectId == Guid.Empty)
|
||||
{
|
||||
_logger.LogWarning("GetAttendanceByEmployeeAsync called with empty ProjectId. TenantId={TenantId}", tenantId);
|
||||
|
||||
return BadRequest(
|
||||
ApiResponse<object>.ErrorResponse("Project reference is required.", "ProjectId is empty in GetAttendanceByEmployeeAsync.", 400));
|
||||
}
|
||||
|
||||
// Resolve the currently logged-in employee (e.g., from token or session).
|
||||
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
|
||||
|
||||
var attendanceEmployeeId = employeeId ?? loggedInEmployee.Id;
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
// Step 1: Ensure employee is allocated to the project for this tenant.
|
||||
var projectAllocation = await _context.ProjectAllocations
|
||||
.Include(pa => pa.Employee)!.ThenInclude(e => e!.JobRole)
|
||||
.Include(pa => pa.Employee)!.ThenInclude(e => e!.Organization)
|
||||
.Include(pa => pa.Project)
|
||||
.FirstOrDefaultAsync(pa =>
|
||||
pa.ProjectId == projectId &&
|
||||
pa.EmployeeId == attendanceEmployeeId &&
|
||||
pa.IsActive &&
|
||||
pa.TenantId == tenantId);
|
||||
|
||||
if (projectAllocation == null)
|
||||
{
|
||||
_logger.LogWarning(
|
||||
"GetAttendanceByEmployeeAsync failed: Employee not allocated to project. TenantId={TenantId}, ProjectId={ProjectId}, EmployeeId={EmployeeId}, RequestedById={RequestedById}",
|
||||
tenantId, projectId, attendanceEmployeeId, loggedInEmployee.Id);
|
||||
|
||||
return BadRequest(ApiResponse<object>.ErrorResponse("The employee is not allocated to the selected project.", "Project allocation not found for given ProjectId, EmployeeId, and TenantId.",
|
||||
400));
|
||||
}
|
||||
|
||||
// Step 2: Fetch today's attendance (if any) for the selected employee and project.
|
||||
var today = DateTime.UtcNow.Date; // Prefer UTC for server-side comparisons.
|
||||
|
||||
var attendance = await _context.Attendes
|
||||
.Include(a => a.Approver)
|
||||
.Include(a => a.RequestedBy)
|
||||
.FirstOrDefaultAsync(a =>
|
||||
a.TenantId == tenantId &&
|
||||
a.EmployeeId == attendanceEmployeeId &&
|
||||
a.ProjectID == projectId &&
|
||||
a.AttendanceDate.Date == today);
|
||||
|
||||
// Step 3: Map to view model with defensive null handling.
|
||||
var attendanceVm = new EmployeeAttendanceVM
|
||||
{
|
||||
Id = attendance?.Id ?? Guid.Empty,
|
||||
EmployeeAvatar = null, // Can be filled from a file service or CDN later.
|
||||
EmployeeId = projectAllocation.EmployeeId,
|
||||
FirstName = projectAllocation.Employee?.FirstName,
|
||||
OrganizationName = projectAllocation.Employee?.Organization?.Name,
|
||||
LastName = projectAllocation.Employee?.LastName,
|
||||
JobRoleName = projectAllocation.Employee?.JobRole?.Name,
|
||||
ProjectId = projectId,
|
||||
ProjectName = projectAllocation.Project?.Name,
|
||||
CheckInTime = attendance?.InTime,
|
||||
CheckOutTime = attendance?.OutTime,
|
||||
Activity = attendance?.Activity ?? ATTENDANCE_MARK_TYPE.CHECK_IN,
|
||||
ApprovedAt = attendance?.ApprovedAt,
|
||||
Approver = attendance == null
|
||||
? null
|
||||
: _mapper.Map<BasicEmployeeVM>(attendance.Approver),
|
||||
RequestedAt = attendance?.RequestedAt,
|
||||
RequestedBy = attendance == null
|
||||
? null
|
||||
: _mapper.Map<BasicEmployeeVM>(attendance.RequestedBy)
|
||||
};
|
||||
|
||||
_logger.LogInfo("GetAttendanceByEmployeeAsync completed successfully. TenantId={TenantId}, ProjectId={ProjectId}, EmployeeId={EmployeeId}, HasAttendance={HasAttendance}",
|
||||
tenantId, projectId, attendanceEmployeeId, attendance != null);
|
||||
|
||||
return Ok(ApiResponse<object>.SuccessResponse(attendanceVm, "Attendance fetched successfully.", 200));
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
_logger.LogWarning("GetAttendanceByEmployeeAsync was canceled. TenantId={TenantId}, ProjectId={ProjectId}, EmployeeId={EmployeeId}",
|
||||
tenantId, projectId, attendanceEmployeeId);
|
||||
|
||||
return StatusCode(499, ApiResponse<object>.ErrorResponse("The request was canceled.", "GetAttendanceByEmployeeAsync was canceled by the client.", 499));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "GetAttendanceByEmployeeAsync failed with an unexpected error. TenantId={TenantId}, ProjectId={ProjectId}, EmployeeId={EmployeeId}",
|
||||
tenantId, projectId, attendanceEmployeeId);
|
||||
|
||||
return StatusCode(500, ApiResponse<object>.ErrorResponse("An error occurred while fetching attendance.", "Unhandled exception in GetAttendanceByEmployeeAsync.", 500));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a high-level collection overview (aging buckets, due vs collected, top client)
|
||||
/// for invoices of the current tenant, optionally filtered by project.
|
||||
/// </summary>
|
||||
/// <param name="projectId">Optional project identifier to filter invoices.</param>
|
||||
/// <returns>Standardized API response with collection KPIs.</returns>
|
||||
[HttpGet("collection-overview")]
|
||||
public async Task<IActionResult> GetCollectionOverviewAsync([FromQuery] Guid? projectId)
|
||||
{
|
||||
// Correlation ID pattern for distributed tracing (if you use one)
|
||||
var correlationId = HttpContext.TraceIdentifier;
|
||||
if (tenantId == Guid.Empty)
|
||||
{
|
||||
_logger.LogWarning("Invalid request: TenantId is empty on progression endpoint");
|
||||
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid TenantId", "Provided Invalid TenantId", 400));
|
||||
}
|
||||
_logger.LogInfo("Started GetCollectionOverviewAsync. CorrelationId: {CorrelationId}, ProjectId: {ProjectId}", correlationId, projectId ?? Guid.Empty);
|
||||
|
||||
try
|
||||
{
|
||||
// Validate and identify current employee/tenant context
|
||||
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
|
||||
|
||||
// Base invoice query for this tenant; AsNoTracking for read-only performance [web:1][web:5]
|
||||
var invoiceQuery = _context.Invoices
|
||||
.Where(i => i.TenantId == tenantId && i.IsActive)
|
||||
.Include(i => i.BilledTo)
|
||||
.AsNoTracking();
|
||||
|
||||
// Fetch infra and service projects in parallel using factory-created contexts
|
||||
// NOTE: Avoid Task.Run over async IO where possible. Here each uses its own context instance. [web:6][web:15]
|
||||
var infraProjectTask = GetInfraProjectsAsync(tenantId);
|
||||
var serviceProjectTask = GetServiceProjectsAsync(tenantId);
|
||||
|
||||
await Task.WhenAll(infraProjectTask, serviceProjectTask);
|
||||
|
||||
var projects = infraProjectTask.Result
|
||||
.Union(serviceProjectTask.Result)
|
||||
.ToList();
|
||||
|
||||
// Optional project filter: validate existence in cached list first
|
||||
if (projectId.HasValue)
|
||||
{
|
||||
var project = projects.FirstOrDefault(p => p.Id == projectId.Value);
|
||||
if (project == null)
|
||||
{
|
||||
_logger.LogWarning(
|
||||
"Project {ProjectId} not found for tenant {TenantId} in GetCollectionOverviewAsync. CorrelationId: {CorrelationId}",
|
||||
projectId, tenantId, correlationId);
|
||||
|
||||
return StatusCode(
|
||||
StatusCodes.Status404NotFound,
|
||||
ApiResponse<object>.ErrorResponse(
|
||||
"Project Not Found",
|
||||
"The requested project does not exist or is not associated with the current tenant.",
|
||||
StatusCodes.Status404NotFound));
|
||||
}
|
||||
|
||||
invoiceQuery = invoiceQuery.Where(i => i.ProjectId == projectId.Value);
|
||||
}
|
||||
|
||||
var invoices = await invoiceQuery.ToListAsync();
|
||||
|
||||
if (invoices.Count == 0)
|
||||
{
|
||||
_logger.LogInfo(
|
||||
"No invoices found for tenant {TenantId} in GetCollectionOverviewAsync. CorrelationId: {CorrelationId}",
|
||||
tenantId, correlationId);
|
||||
|
||||
// Return an empty but valid overview instead of 404 – endpoint is conceptually valid
|
||||
var emptyResponse = new
|
||||
{
|
||||
TotalDueAmount = 0d,
|
||||
TotalCollectedAmount = 0d,
|
||||
TotalValue = 0d,
|
||||
PendingPercentage = 0d,
|
||||
CollectedPercentage = 0d,
|
||||
Bucket0To30Invoices = 0,
|
||||
Bucket30To60Invoices = 0,
|
||||
Bucket60To90Invoices = 0,
|
||||
Bucket90PlusInvoices = 0,
|
||||
Bucket0To30Amount = 0d,
|
||||
Bucket30To60Amount = 0d,
|
||||
Bucket60To90Amount = 0d,
|
||||
Bucket90PlusAmount = 0d,
|
||||
TopClientBalance = 0d,
|
||||
TopClient = new BasicOrganizationVm()
|
||||
};
|
||||
|
||||
return Ok(ApiResponse<object>.SuccessResponse(emptyResponse, "No invoices found for the current tenant and filters; returning empty collection overview.", 200));
|
||||
}
|
||||
|
||||
var invoiceIds = invoices.Select(i => i.Id).ToList();
|
||||
|
||||
// Pre-aggregate payments per invoice in the DB where possible [web:1][web:17]
|
||||
var paymentGroups = await _context.ReceivedInvoicePayments
|
||||
.AsNoTracking()
|
||||
.Where(p => invoiceIds.Contains(p.InvoiceId) && p.TenantId == tenantId)
|
||||
.GroupBy(p => p.InvoiceId)
|
||||
.Select(g => new
|
||||
{
|
||||
InvoiceId = g.Key,
|
||||
PaidAmount = g.Sum(p => p.Amount)
|
||||
})
|
||||
.ToListAsync();
|
||||
|
||||
// Create a lookup to avoid repeated LINQ Where on each iteration
|
||||
var paymentsLookup = paymentGroups.ToDictionary(p => p.InvoiceId, p => p.PaidAmount);
|
||||
|
||||
double totalDueAmount = 0;
|
||||
var today = DateTime.UtcNow.Date; // use UTC for consistency [web:17]
|
||||
|
||||
var bucketOneInvoices = 0;
|
||||
double bucketOneAmount = 0;
|
||||
var bucketTwoInvoices = 0;
|
||||
double bucketTwoAmount = 0;
|
||||
var bucketThreeInvoices = 0;
|
||||
double bucketThreeAmount = 0;
|
||||
var bucketFourInvoices = 0;
|
||||
double bucketFourAmount = 0;
|
||||
|
||||
// Main aging calculation loop
|
||||
foreach (var invoice in invoices)
|
||||
{
|
||||
var total = invoice.BasicAmount + invoice.TaxAmount;
|
||||
var paid = paymentsLookup.TryGetValue(invoice.Id, out var paidAmount)
|
||||
? paidAmount
|
||||
: 0d;
|
||||
var balance = total - paid;
|
||||
|
||||
// Skip fully paid or explicitly completed invoices
|
||||
if (balance <= 0 || invoice.MarkAsCompleted)
|
||||
continue;
|
||||
|
||||
totalDueAmount += balance;
|
||||
|
||||
// Only consider invoices with expected payment date up to today for aging
|
||||
var expectedDate = invoice.ExceptedPaymentDate.Date;
|
||||
if (expectedDate > today)
|
||||
continue;
|
||||
|
||||
var days = (today - expectedDate).Days;
|
||||
|
||||
if (days <= 30 && days > 0)
|
||||
{
|
||||
bucketOneInvoices++;
|
||||
bucketOneAmount += balance;
|
||||
}
|
||||
else if (days > 30 && days <= 60)
|
||||
{
|
||||
bucketTwoInvoices++;
|
||||
bucketTwoAmount += balance;
|
||||
}
|
||||
else if (days > 60 && days <= 90)
|
||||
{
|
||||
bucketThreeInvoices++;
|
||||
bucketThreeAmount += balance;
|
||||
}
|
||||
else if (days > 90)
|
||||
{
|
||||
bucketFourInvoices++;
|
||||
bucketFourAmount += balance;
|
||||
}
|
||||
}
|
||||
|
||||
var totalCollectedAmount = paymentGroups.Sum(p => p.PaidAmount);
|
||||
var totalValue = totalDueAmount + totalCollectedAmount;
|
||||
var pendingPercentage = totalValue > 0 ? (totalDueAmount / totalValue) * 100 : 0;
|
||||
var collectedPercentage = totalValue > 0 ? (totalCollectedAmount / totalValue) * 100 : 0;
|
||||
|
||||
// Determine top client by outstanding balance
|
||||
double topClientBalance = 0;
|
||||
Organization topClient = new Organization();
|
||||
|
||||
var groupedByClient = invoices
|
||||
.Where(i => i.BilledToId.HasValue && i.BilledTo != null)
|
||||
.GroupBy(i => i.BilledToId);
|
||||
|
||||
foreach (var group in groupedByClient)
|
||||
{
|
||||
var clientInvoiceIds = group.Select(i => i.Id).ToList();
|
||||
var totalForClient = group.Sum(i => i.BasicAmount + i.TaxAmount);
|
||||
var paidForClient = paymentGroups
|
||||
.Where(pg => clientInvoiceIds.Contains(pg.InvoiceId))
|
||||
.Sum(pg => pg.PaidAmount);
|
||||
|
||||
var clientBalance = totalForClient - paidForClient;
|
||||
if (clientBalance > topClientBalance)
|
||||
{
|
||||
topClientBalance = clientBalance;
|
||||
topClient = group.First()!.BilledTo!;
|
||||
}
|
||||
}
|
||||
|
||||
BasicOrganizationVm topClientVm = new BasicOrganizationVm();
|
||||
if (topClient != null)
|
||||
{
|
||||
topClientVm = new BasicOrganizationVm
|
||||
{
|
||||
Id = topClient.Id,
|
||||
Name = topClient.Name,
|
||||
Email = topClient.Email,
|
||||
ContactPerson = topClient.ContactPerson,
|
||||
ContactNumber = topClient.ContactNumber,
|
||||
Address = topClient.Address,
|
||||
GSTNumber = topClient.GSTNumber,
|
||||
SPRID = topClient.SPRID
|
||||
};
|
||||
}
|
||||
|
||||
var response = new
|
||||
{
|
||||
TotalDueAmount = totalDueAmount,
|
||||
TotalCollectedAmount = totalCollectedAmount,
|
||||
TotalValue = totalValue,
|
||||
PendingPercentage = Math.Round(pendingPercentage, 2),
|
||||
CollectedPercentage = Math.Round(collectedPercentage, 2),
|
||||
Bucket0To30Invoices = bucketOneInvoices,
|
||||
Bucket30To60Invoices = bucketTwoInvoices,
|
||||
Bucket60To90Invoices = bucketThreeInvoices,
|
||||
Bucket90PlusInvoices = bucketFourInvoices,
|
||||
Bucket0To30Amount = bucketOneAmount,
|
||||
Bucket30To60Amount = bucketTwoAmount,
|
||||
Bucket60To90Amount = bucketThreeAmount,
|
||||
Bucket90PlusAmount = bucketFourAmount,
|
||||
TopClientBalance = topClientBalance,
|
||||
TopClient = topClientVm
|
||||
};
|
||||
|
||||
_logger.LogInfo("Successfully completed GetCollectionOverviewAsync for tenant {TenantId}. CorrelationId: {CorrelationId}, TotalInvoices: {InvoiceCount}, TotalValue: {TotalValue}",
|
||||
tenantId, correlationId, invoices.Count, totalValue);
|
||||
|
||||
return Ok(ApiResponse<object>.SuccessResponse(response, "Collection overview fetched successfully.", 200));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Centralized logging for unhandled exceptions with context, no sensitive data [web:1][web:5][web:10]
|
||||
_logger.LogError(ex, "Unhandled exception in GetCollectionOverviewAsync. CorrelationId: {CorrelationId}", correlationId);
|
||||
|
||||
// Generic but consistent error payload; let global exception handler standardize if you use ProblemDetails [web:10][web:13][web:16]
|
||||
return StatusCode(500, ApiResponse<object>.ErrorResponse("Internal Server Error",
|
||||
"An unexpected error occurred while generating the collection overview. Please try again or contact support with the correlation identifier.", 500));
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("purchase-invoice-overview")]
|
||||
public async Task<IActionResult> GetPurchaseInvoiceOverview()
|
||||
{
|
||||
// Correlation id for tracing this request across services/logs.
|
||||
var correlationId = HttpContext.TraceIdentifier;
|
||||
|
||||
_logger.LogInfo("GetPurchaseInvoiceOverview started. TenantId: {TenantId}, CorrelationId: {CorrelationId}", tenantId, correlationId);
|
||||
|
||||
// Basic guard: invalid tenant.
|
||||
if (tenantId == Guid.Empty)
|
||||
{
|
||||
_logger.LogWarning("GetPurchaseInvoiceOverview rejected due to empty TenantId. CorrelationId: {CorrelationId}", correlationId);
|
||||
|
||||
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid TenantId", "The tenant identifier provided is invalid or missing.", 400));
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Fetch current employee context (if needed for authorization/audit).
|
||||
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
|
||||
|
||||
// Run project queries in parallel to reduce latency.
|
||||
var infraProjectTask = GetInfraProjectsAsync(tenantId);
|
||||
var serviceProjectTask = GetServiceProjectsAsync(tenantId);
|
||||
|
||||
await Task.WhenAll(infraProjectTask, serviceProjectTask);
|
||||
|
||||
var projects = infraProjectTask.Result
|
||||
.Union(serviceProjectTask.Result)
|
||||
.ToList();
|
||||
|
||||
_logger.LogDebug("GetPurchaseInvoiceOverview loaded projects. Count: {ProjectCount}, TenantId: {TenantId}, CorrelationId: {CorrelationId}", projects.Count, tenantId, correlationId);
|
||||
|
||||
// Query purchase invoices for the tenant.
|
||||
var purchaseInvoices = await _context.PurchaseInvoiceDetails
|
||||
.Include(pid => pid.Supplier)
|
||||
.Include(pid => pid.Status)
|
||||
.AsNoTracking()
|
||||
.Where(pid => pid.TenantId == tenantId && pid.IsActive)
|
||||
.ToListAsync();
|
||||
|
||||
_logger.LogInfo("GetPurchaseInvoiceOverview loaded invoices. InvoiceCount: {InvoiceCount}, TenantId: {TenantId}, CorrelationId: {CorrelationId}",
|
||||
purchaseInvoices.Count, tenantId, correlationId);
|
||||
|
||||
if (!purchaseInvoices.Any())
|
||||
{
|
||||
// No invoices is not an error; return an empty but well-structured overview.
|
||||
_logger.LogInfo("GetPurchaseInvoiceOverview: No active purchase invoices found. TenantId: {TenantId}, CorrelationId: {CorrelationId}", tenantId, correlationId);
|
||||
|
||||
var emptyResponse = new
|
||||
{
|
||||
TotalInvoices = 0,
|
||||
TotalValue = 0m,
|
||||
AverageValue = 0m,
|
||||
StatusBreakdown = Array.Empty<object>(),
|
||||
ProjectBreakdown = Array.Empty<object>(),
|
||||
TopSupplier = (object?)null
|
||||
};
|
||||
|
||||
return Ok(ApiResponse<object>.SuccessResponse(
|
||||
emptyResponse,
|
||||
"No active purchase invoices found for the specified tenant.",
|
||||
StatusCodes.Status200OK));
|
||||
}
|
||||
|
||||
var totalInvoices = purchaseInvoices.Count;
|
||||
var totalValue = purchaseInvoices.Sum(pid => pid.BaseAmount);
|
||||
|
||||
// Guard against divide-by-zero (in case BaseAmount is all zero).
|
||||
var averageValue = totalInvoices > 0
|
||||
? totalValue / totalInvoices
|
||||
: 0;
|
||||
|
||||
// Status-wise aggregation
|
||||
var statusBreakdown = purchaseInvoices
|
||||
.Where(pid => pid.Status != null)
|
||||
.GroupBy(pid => pid.StatusId)
|
||||
.Select(g => new
|
||||
{
|
||||
Id = g.Key,
|
||||
Name = g.First().Status!.DisplayName,
|
||||
Count = g.Count(),
|
||||
TotalValue = g.Sum(pid => pid.BaseAmount),
|
||||
Percentage = totalValue > 0
|
||||
? Math.Round(g.Sum(pid => pid.BaseAmount) / totalValue * 100, 2)
|
||||
: 0
|
||||
})
|
||||
.OrderByDescending(x => x.TotalValue)
|
||||
.ToList();
|
||||
|
||||
// Project-wise aggregation (top 3 by value)
|
||||
var projectBreakdown = purchaseInvoices
|
||||
.GroupBy(pid => pid.ProjectId)
|
||||
.Select(g => new
|
||||
{
|
||||
Id = g.Key,
|
||||
Name = projects.FirstOrDefault(p => p.Id == g.Key)?.Name ?? "Unknown Project",
|
||||
Count = g.Count(),
|
||||
TotalValue = g.Sum(pid => pid.BaseAmount),
|
||||
Percentage = totalValue > 0
|
||||
? Math.Round(g.Sum(pid => pid.BaseAmount) / totalValue * 100, 2)
|
||||
: 0
|
||||
})
|
||||
.OrderByDescending(pid => pid.TotalValue)
|
||||
.Take(3)
|
||||
.ToList();
|
||||
|
||||
// Top supplier by total value
|
||||
var supplierBreakdown = purchaseInvoices
|
||||
.Where(pid => pid.Supplier != null)
|
||||
.GroupBy(pid => pid.SupplierId)
|
||||
.Select(g => new
|
||||
{
|
||||
Id = g.Key,
|
||||
Name = g.First().Supplier!.Name,
|
||||
Count = g.Count(),
|
||||
TotalValue = g.Sum(pid => pid.BaseAmount),
|
||||
Percentage = totalValue > 0
|
||||
? Math.Round(g.Sum(pid => pid.BaseAmount) / totalValue * 100, 2)
|
||||
: 0
|
||||
})
|
||||
.OrderByDescending(pid => pid.TotalValue)
|
||||
.FirstOrDefault();
|
||||
|
||||
var response = new
|
||||
{
|
||||
TotalInvoices = totalInvoices,
|
||||
TotalValue = Math.Round(totalValue, 2),
|
||||
AverageValue = Math.Round(averageValue, 2),
|
||||
StatusBreakdown = statusBreakdown,
|
||||
ProjectBreakdown = projectBreakdown,
|
||||
TopSupplier = supplierBreakdown
|
||||
};
|
||||
|
||||
_logger.LogInfo("GetPurchaseInvoiceOverview completed successfully. TenantId: {TenantId}, TotalInvoices: {TotalInvoices}, TotalValue: {TotalValue}, CorrelationId: {CorrelationId}",
|
||||
tenantId, totalInvoices, totalValue, correlationId);
|
||||
|
||||
return Ok(ApiResponse<object>.SuccessResponse(response, "Purchase invoice overview retrieved successfully.", 200));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Capture complete context for diagnostics, but ensure no sensitive data is logged.
|
||||
_logger.LogError(ex, "Error occurred while processing GetPurchaseInvoiceOverview. TenantId: {TenantId}, CorrelationId: {CorrelationId}",
|
||||
tenantId, correlationId);
|
||||
|
||||
// Do not expose internal details to clients. Return a generic 500 response.
|
||||
return StatusCode(500, ApiResponse<object>.ErrorResponse("Internal Server Error", "An unexpected error occurred while processing the purchase invoice overview.", 500));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets infrastructure projects for a tenant as a lightweight view model.
|
||||
/// </summary>
|
||||
private async Task<List<BasicProjectVM>> GetInfraProjectsAsync(Guid tenantId)
|
||||
{
|
||||
await using var context = await _dbContextFactory.CreateDbContextAsync();
|
||||
return await context.Projects
|
||||
.AsNoTracking()
|
||||
.Where(p => p.TenantId == tenantId)
|
||||
.Select(p => new BasicProjectVM { Id = p.Id, Name = p.Name })
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets service projects for a tenant as a lightweight view model.
|
||||
/// </summary>
|
||||
private async Task<List<BasicProjectVM>> GetServiceProjectsAsync(Guid tenantId)
|
||||
{
|
||||
await using var context = await _dbContextFactory.CreateDbContextAsync();
|
||||
return await context.ServiceProjects
|
||||
.AsNoTracking()
|
||||
.Where(sp => sp.TenantId == tenantId)
|
||||
.Select(sp => new BasicProjectVM { Id = sp.Id, Name = sp.Name })
|
||||
.ToListAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -70,6 +70,49 @@ namespace Marco.Pms.Services.Controllers
|
||||
|
||||
#endregion
|
||||
|
||||
#region =================================================================== Purchase Invoice Status APIs ===================================================================
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the purchase invoice status.
|
||||
/// </summary>
|
||||
/// <param name="ct">The cancellation token.</param>
|
||||
/// <returns>The HTTP response containing the purchase invoice status.</returns>
|
||||
[HttpGet("purchase-invoice-status/list")]
|
||||
public async Task<IActionResult> GetPurchaseInvoiceStatus(CancellationToken ct)
|
||||
{
|
||||
// Get the currently logged-in employee.
|
||||
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
|
||||
|
||||
// Retrieve the purchase invoice status asynchronously.
|
||||
var response = await _masterService.GetPurchaseInvoiceStatusAsync(loggedInEmployee, ct);
|
||||
|
||||
// Return the HTTP response with the purchase invoice status.
|
||||
return StatusCode(response.StatusCode, response);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region =================================================================== Invoice Attachment Type APIs ===================================================================
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the invoice attachment types.
|
||||
/// </summary>
|
||||
/// <param name="ct">The cancellation token.</param>
|
||||
/// <returns>The HTTP response containing the invoice attachment types.</returns>
|
||||
[HttpGet("invoice-attachment-type/list")]
|
||||
public async Task<IActionResult> GetInvoiceAttachmentType(CancellationToken ct)
|
||||
{
|
||||
// Get the currently logged-in employee.
|
||||
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
|
||||
|
||||
// Retrieve the invoice attachment types asynchronously.
|
||||
var response = await _masterService.GetInvoiceAttachmentTypeAsync(loggedInEmployee, ct);
|
||||
|
||||
// Return the HTTP response with the invoice attachment types.
|
||||
return StatusCode(response.StatusCode, response);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region =================================================================== Currency APIs ===================================================================
|
||||
|
||||
[HttpGet("currencies/list")]
|
||||
|
||||
@ -45,6 +45,14 @@ namespace Marco.Pms.Services.Controllers
|
||||
return StatusCode(response.StatusCode, response);
|
||||
}
|
||||
|
||||
[HttpGet("list/basic")]
|
||||
public async Task<IActionResult> GetOrganizationBasicList([FromQuery] Guid? id, [FromQuery] string? searchString, CancellationToken ct, [FromQuery] int pageNumber = 1, [FromQuery] int pageSize = 20)
|
||||
{
|
||||
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
|
||||
var response = await _organizationService.GetOrganizationBasicListAsync(id, searchString, pageNumber, pageSize, loggedInEmployee, ct);
|
||||
return StatusCode(response.StatusCode, response);
|
||||
}
|
||||
|
||||
[HttpGet("details/{id}")]
|
||||
public async Task<IActionResult> GetOrganizationDetails(Guid id)
|
||||
{
|
||||
|
||||
@ -41,11 +41,11 @@ namespace MarcoBMS.Services.Controllers
|
||||
#region =================================================================== Project Get APIs ===================================================================
|
||||
|
||||
[HttpGet("list/basic/all")]
|
||||
public async Task<IActionResult> GetBothProjectBasicList([FromQuery] string? searchString)
|
||||
public async Task<IActionResult> GetBothProjectBasicList([FromQuery] Guid? id, [FromQuery] string? searchString)
|
||||
{
|
||||
// Get the current user
|
||||
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
|
||||
var response = await _projectServices.GetBothProjectBasicListAsync(searchString, loggedInEmployee, tenantId);
|
||||
var response = await _projectServices.GetBothProjectBasicListAsync(id, searchString, loggedInEmployee, tenantId);
|
||||
return StatusCode(response.StatusCode, response);
|
||||
}
|
||||
|
||||
|
||||
218
Marco.Pms.Services/Controllers/PurchaseInvoiceController.cs
Normal file
218
Marco.Pms.Services/Controllers/PurchaseInvoiceController.cs
Normal file
@ -0,0 +1,218 @@
|
||||
using AutoMapper;
|
||||
using Marco.Pms.Model.Dtos.Collection;
|
||||
using Marco.Pms.Model.Dtos.PurchaseInvoice;
|
||||
using Marco.Pms.Model.Utilities;
|
||||
using Marco.Pms.Services.Service.ServiceInterfaces;
|
||||
using MarcoBMS.Services.Helpers;
|
||||
using Microsoft.AspNetCore.JsonPatch;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Marco.Pms.Services.Controllers
|
||||
{
|
||||
[Route("api/[controller]")]
|
||||
[ApiController]
|
||||
public class PurchaseInvoiceController : ControllerBase
|
||||
{
|
||||
private readonly UserHelper _userHelper;
|
||||
private readonly IPurchaseInvoiceService _purchaseInvoiceService;
|
||||
private readonly ISignalRService _signalR;
|
||||
private readonly IServiceScopeFactory _serviceScopeFactory;
|
||||
private readonly Guid tenantId;
|
||||
|
||||
public PurchaseInvoiceController(UserHelper userHelper, IPurchaseInvoiceService purchaseInvoiceService, ISignalRService signalR, IServiceScopeFactory serviceScopeFactory)
|
||||
{
|
||||
_userHelper = userHelper;
|
||||
_purchaseInvoiceService = purchaseInvoiceService;
|
||||
tenantId = _userHelper.GetTenantId();
|
||||
_signalR = signalR;
|
||||
_serviceScopeFactory = serviceScopeFactory;
|
||||
}
|
||||
|
||||
#region =================================================================== Purchase Invoice Functions ===================================================================
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a list of purchase invoices based on search string, filter, activity status, page size, and page number.
|
||||
/// </summary>
|
||||
/// <param name="searchString">Optional search string to filter invoices by.</param>
|
||||
/// <param name="filter">Optional filter to apply to the invoices.</param>
|
||||
/// <param name="isActive">Optional flag to filter invoices by activity status.</param>
|
||||
/// <param name="pageSize">The number of invoices to display per page.</param>
|
||||
/// <param name="pageNumber">The requested page number (1-based).</param>
|
||||
/// <param name="cancellationToken">Token to propagate notification that operations should be canceled.</param>
|
||||
/// <returns>A HTTP 200 OK response with a list of purchase invoices or an appropriate HTTP error code.</returns>
|
||||
[HttpGet("list")]
|
||||
public async Task<IActionResult> GetPurchaseInvoiceList([FromQuery] string? searchString, [FromQuery] string? filter, CancellationToken cancellationToken, [FromQuery] bool isActive = true,
|
||||
[FromQuery] int pageSize = 20, [FromQuery] int pageNumber = 1)
|
||||
{
|
||||
// Get the currently logged-in employee
|
||||
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
|
||||
|
||||
// Retrieve the purchase invoice list using the service
|
||||
var response = await _purchaseInvoiceService.GetPurchaseInvoiceListAsync(searchString, filter, isActive, pageSize, pageNumber, loggedInEmployee, tenantId, cancellationToken);
|
||||
|
||||
// Return the response with the appropriate HTTP status code
|
||||
return StatusCode(response.StatusCode, response);
|
||||
}
|
||||
|
||||
[HttpGet("details/{id}")]
|
||||
public async Task<IActionResult> GetPurchaseInvoiceDetails(Guid id, CancellationToken cancellationToken)
|
||||
{
|
||||
// Get the currently logged-in employee
|
||||
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
|
||||
|
||||
// Retrieve the purchase invoice details using the service
|
||||
var response = await _purchaseInvoiceService.GetPurchaseInvoiceDetailsAsync(id, loggedInEmployee, tenantId, cancellationToken);
|
||||
|
||||
// Return the response with the appropriate HTTP status code
|
||||
return StatusCode(response.StatusCode, response);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Creates a purchase invoice.
|
||||
/// </summary>
|
||||
/// <param name="model">The purchase invoice model.</param>
|
||||
/// <param name="ct">The cancellation token.</param>
|
||||
/// <returns>The HTTP response for the creation of the purchase invoice.</returns>
|
||||
[HttpPost("create")]
|
||||
public async Task<IActionResult> CreatePurchaseInvoice([FromBody] PurchaseInvoiceDto model, CancellationToken ct)
|
||||
{
|
||||
// Get the currently logged-in employee
|
||||
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
|
||||
|
||||
// Create a purchase invoice using the purchase invoice service
|
||||
var response = await _purchaseInvoiceService.CreatePurchaseInvoiceAsync(model, loggedInEmployee, tenantId, ct);
|
||||
|
||||
// If the creation is successful, send a notification to the SignalR service
|
||||
if (response.Success)
|
||||
{
|
||||
var notification = new
|
||||
{
|
||||
LoggedInUserId = loggedInEmployee.Id,
|
||||
Keyword = "Purchase_Invoice",
|
||||
Response = response.Data
|
||||
};
|
||||
await _signalR.SendNotificationAsync(notification);
|
||||
}
|
||||
|
||||
// Return the HTTP response
|
||||
return StatusCode(response.StatusCode, response);
|
||||
}
|
||||
|
||||
[HttpPatch("edit/{id}")]
|
||||
public async Task<IActionResult> EditPurchaseInvoice(Guid id, [FromBody] JsonPatchDocument<PurchaseInvoiceDto> patchDoc, CancellationToken ct)
|
||||
{
|
||||
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
|
||||
var existingPurchaseInvoice = await _purchaseInvoiceService.GetPurchaseInvoiceByIdAsync(id, tenantId, ct);
|
||||
if (existingPurchaseInvoice == null)
|
||||
return NotFound(ApiResponse<object>.ErrorResponse("Invalid purchase invoice ID", "Invalid purchase invoice ID", 404));
|
||||
|
||||
using var scope = _serviceScopeFactory.CreateScope();
|
||||
var mapper = scope.ServiceProvider.GetRequiredService<IMapper>();
|
||||
var modelToPatch = mapper.Map<PurchaseInvoiceDto>(existingPurchaseInvoice);
|
||||
|
||||
// Apply the JSON Patch document to the DTO and check model state validity
|
||||
patchDoc.ApplyTo(modelToPatch, ModelState);
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(ApiResponse<object>.ErrorResponse("Validation failed", "Provided patch document values are invalid", 400));
|
||||
}
|
||||
|
||||
var response = await _purchaseInvoiceService.UpdatePurchaseInvoiceAsync(id, existingPurchaseInvoice, modelToPatch, loggedInEmployee, tenantId, ct);
|
||||
return StatusCode(response.StatusCode, response);
|
||||
}
|
||||
|
||||
[HttpDelete("delete/{id}")]
|
||||
public async Task<IActionResult> DeletePurchaseInvoice(Guid id, CancellationToken ct, [FromQuery] bool isActive = false)
|
||||
{
|
||||
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
|
||||
var response = await _purchaseInvoiceService.DeletePurchaseInvoiceAsync(id, isActive, loggedInEmployee, tenantId, ct);
|
||||
if (response.Success)
|
||||
{
|
||||
var notification = new
|
||||
{
|
||||
LoggedInUserId = loggedInEmployee.Id,
|
||||
Keyword = "Purchase_Invoice",
|
||||
Response = response.Data
|
||||
};
|
||||
await _signalR.SendNotificationAsync(notification);
|
||||
}
|
||||
return StatusCode(response.StatusCode, response);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region =================================================================== Delivery Challan Functions ===================================================================
|
||||
|
||||
[HttpGet("delivery-challan/list/{purchaseInvoiceId}")]
|
||||
public async Task<IActionResult> GetDeliveryChallans(Guid purchaseInvoiceId, CancellationToken cancellationToken)
|
||||
{
|
||||
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
|
||||
var response = await _purchaseInvoiceService.GetDeliveryChallansAsync(purchaseInvoiceId, loggedInEmployee, tenantId, cancellationToken);
|
||||
return StatusCode(response.StatusCode, response);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a delivery challan.
|
||||
/// </summary>
|
||||
/// <param name="model">The delivery challan model.</param>
|
||||
/// <param name="ct">The cancellation token.</param>
|
||||
/// <returns>The HTTP response for adding the delivery challan.</returns>
|
||||
[HttpPost("delivery-challan/create")]
|
||||
public async Task<IActionResult> AddDeliveryChallan([FromBody] DeliveryChallanDto model, CancellationToken ct)
|
||||
{
|
||||
// Get the currently logged-in employee
|
||||
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
|
||||
|
||||
// Add the delivery challan using the purchase invoice service
|
||||
var response = await _purchaseInvoiceService.AddDeliveryChallanAsync(model, loggedInEmployee, tenantId, ct);
|
||||
|
||||
// If the addition is successful, send a notification to the SignalR service
|
||||
if (response.Success)
|
||||
{
|
||||
var notification = new
|
||||
{
|
||||
LoggedInUserId = loggedInEmployee.Id,
|
||||
Keyword = "Delivery_Challan",
|
||||
Response = response.Data
|
||||
};
|
||||
await _signalR.SendNotificationAsync(notification);
|
||||
}
|
||||
|
||||
// Return the HTTP response
|
||||
return StatusCode(response.StatusCode, response);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region =================================================================== Purchase Invoice History Functions ===================================================================
|
||||
[HttpGet("payment-history/list/{purchaseInvoiceId}")]
|
||||
public async Task<IActionResult> GetPurchaseInvoiceHistoryList(Guid purchaseInvoiceId, CancellationToken cancellationToken)
|
||||
{
|
||||
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
|
||||
var response = await _purchaseInvoiceService.GetPurchaseInvoiceHistoryListAsync(purchaseInvoiceId, loggedInEmployee, tenantId, cancellationToken);
|
||||
return StatusCode(response.StatusCode, response);
|
||||
}
|
||||
|
||||
[HttpPost("add/payment")]
|
||||
public async Task<IActionResult> AddPurchaseInvoicePayment([FromBody] ReceivedInvoicePaymentDto model, CancellationToken ct)
|
||||
{
|
||||
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
|
||||
var response = await _purchaseInvoiceService.AddPurchaseInvoicePaymentAsync(model, loggedInEmployee, tenantId, ct);
|
||||
if (response.Success)
|
||||
{
|
||||
var notification = new
|
||||
{
|
||||
LoggedInUserId = loggedInEmployee.Id,
|
||||
Keyword = "Purchase_Invoice_Payment",
|
||||
Response = response.Data
|
||||
};
|
||||
await _signalR.SendNotificationAsync(notification);
|
||||
}
|
||||
|
||||
// Return the HTTP response
|
||||
return StatusCode(response.StatusCode, response);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -384,7 +384,10 @@ namespace Marco.Pms.Services.Controllers
|
||||
response.CreatedBy = createdBy;
|
||||
|
||||
response.CurrentPlan = _mapper.Map<SubscriptionPlanDetailsVM>(currentPlan);
|
||||
response.CurrentPlan.PaymentDetail = paymentsDetails.FirstOrDefault(pd => currentPlan != null && pd.Id == currentPlan.PaymentDetailId);
|
||||
if (currentPlan != null)
|
||||
{
|
||||
response.CurrentPlan.PaymentDetail = paymentsDetails.FirstOrDefault(pd => pd.Id == currentPlan.PaymentDetailId);
|
||||
}
|
||||
|
||||
response.CurrentPlanFeatures = await _featureDetailsHelper.GetFeatureDetails(currentPlan?.Plan?.FeaturesId ?? Guid.Empty);
|
||||
// Map subscription history plans to DTO
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
using Marco.Pms.Model.Filters;
|
||||
using System.Data;
|
||||
using System.Linq.Dynamic.Core;
|
||||
|
||||
namespace Marco.Pms.Services.Extensions
|
||||
@ -69,9 +70,16 @@ namespace Marco.Pms.Services.Extensions
|
||||
return query;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies search filters to the given IQueryable.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the elements in the IQueryable.</typeparam>
|
||||
/// <param name="query">The IQueryable to apply the filters to.</param>
|
||||
/// <param name="searchFilters">The list of search filters to apply.</param>
|
||||
/// <returns>The filtered IQueryable.</returns>
|
||||
public static IQueryable<T> ApplySearchFilters<T>(this IQueryable<T> query, List<SearchItem> searchFilters)
|
||||
{
|
||||
// 1. Apply Search Filters (Contains/Text search)
|
||||
// Apply search filters to the query
|
||||
if (searchFilters.Any())
|
||||
{
|
||||
foreach (var search in searchFilters)
|
||||
@ -86,10 +94,70 @@ namespace Marco.Pms.Services.Extensions
|
||||
return query;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies group by filters to the given IQueryable.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the elements in the IQueryable.</typeparam>
|
||||
/// <param name="query">The IQueryable to apply the filters to.</param>
|
||||
/// <param name="groupByColumn">The column to group by.</param>
|
||||
/// <returns>The grouped IQueryable.</returns>
|
||||
public static IQueryable<T> ApplyGroupByFilters<T>(this IQueryable<T> query, string groupByColumn)
|
||||
{
|
||||
// Group the query by the specified column and reshape the result to { Key: "Value", Items: [...] }
|
||||
query.GroupBy(groupByColumn, "it")
|
||||
.Select("new (Key, it as Items)"); // Reshape to { Key: "Value", Items: [...] }
|
||||
.Select("new (Key, it as Items)");
|
||||
return query;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies list filters to the given IQueryable.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the elements in the IQueryable.</typeparam>
|
||||
/// <param name="query">The IQueryable to apply the filters to.</param>
|
||||
/// <param name="filters">The list of filters to apply.</param>
|
||||
/// <returns>The filtered IQueryable.</returns>
|
||||
public static IQueryable<T> ApplyListFilters<T>(this IQueryable<T> query, List<ListDynamicFilter> filters)
|
||||
{
|
||||
// Check if there are any filters
|
||||
if (filters == null || !filters.Any()) return query;
|
||||
|
||||
// Apply filters to the query
|
||||
foreach (var filter in filters)
|
||||
{
|
||||
// Skip if column is empty or values are null or empty
|
||||
if (string.IsNullOrWhiteSpace(filter.Column) || filter.Values == null || !filter.Values.Any()) continue;
|
||||
|
||||
// Apply filter to the query
|
||||
query = query.Where($"@0.Contains({filter.Column})", filter.Values);
|
||||
}
|
||||
|
||||
// Return the filtered query
|
||||
return query;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies date filters to the given IQueryable.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the elements in the IQueryable.</typeparam>
|
||||
/// <param name="query">The IQueryable to apply the filters to.</param>
|
||||
/// <param name="dateFilter">The date filter to apply.</param>
|
||||
/// <returns>The filtered IQueryable.</returns>
|
||||
public static IQueryable<T> ApplyDateFilter<T>(this IQueryable<T> query, DateDynamicFilter dateFilter)
|
||||
{
|
||||
// Check if date filter is null or column is empty
|
||||
if (dateFilter == null || string.IsNullOrWhiteSpace(dateFilter.Column)) return query;
|
||||
|
||||
// Convert start and end values to date
|
||||
var startValue = dateFilter.StartValue.Date;
|
||||
var endValue = dateFilter.EndValue.Date.AddDays(1);
|
||||
|
||||
// Apply a filter to include items with a date greater than or equal to the start value
|
||||
query = query.Where($"{dateFilter.Column} >= @0", startValue);
|
||||
|
||||
// Apply a filter to include items with a date less than or equal to the end value
|
||||
query = query.Where($"{dateFilter.Column} < @0", endValue);
|
||||
|
||||
// Return the filtered IQueryable
|
||||
return query;
|
||||
}
|
||||
}
|
||||
|
||||
@ -116,7 +116,7 @@ namespace Marco.Pms.Services.Helpers
|
||||
using var context = _dbContextFactory.CreateDbContext();
|
||||
return await context.ProjectAllocations
|
||||
.AsNoTracking()
|
||||
.CountAsync(pa => pa.ProjectId == project.Id && pa.IsActive); // Server-side count is efficient
|
||||
.CountAsync(pa => pa.ProjectId == project.Id && pa.TenantId == project.TenantId && pa.IsActive); // Server-side count is efficient
|
||||
});
|
||||
|
||||
// This task fetches the entire infrastructure hierarchy and performs aggregations in the database.
|
||||
@ -127,26 +127,26 @@ namespace Marco.Pms.Services.Helpers
|
||||
// 1. Fetch all hierarchical data using projections.
|
||||
// This is still a chain, but it's inside one task and much faster due to projections.
|
||||
var buildings = await context.Buildings.AsNoTracking()
|
||||
.Where(b => b.ProjectId == project.Id)
|
||||
.Where(b => b.ProjectId == project.Id && b.TenantId == project.TenantId)
|
||||
.Select(b => new { b.Id, b.ProjectId, b.Name, b.Description })
|
||||
.ToListAsync();
|
||||
var buildingIds = buildings.Select(b => b.Id).ToList();
|
||||
|
||||
var floors = await context.Floor.AsNoTracking()
|
||||
.Where(f => buildingIds.Contains(f.BuildingId))
|
||||
.Where(f => buildingIds.Contains(f.BuildingId) && f.TenantId == project.TenantId)
|
||||
.Select(f => new { f.Id, f.BuildingId, f.FloorName })
|
||||
.ToListAsync();
|
||||
var floorIds = floors.Select(f => f.Id).ToList();
|
||||
|
||||
var workAreas = await context.WorkAreas.AsNoTracking()
|
||||
.Where(wa => floorIds.Contains(wa.FloorId))
|
||||
.Where(wa => floorIds.Contains(wa.FloorId) && wa.TenantId == project.TenantId)
|
||||
.Select(wa => new { wa.Id, wa.FloorId, wa.AreaName })
|
||||
.ToListAsync();
|
||||
var workAreaIds = workAreas.Select(wa => wa.Id).ToList();
|
||||
|
||||
// 2. THE KEY OPTIMIZATION: Aggregate work items in the database.
|
||||
var workSummaries = await context.WorkItems.AsNoTracking()
|
||||
.Where(wi => workAreaIds.Contains(wi.WorkAreaId))
|
||||
.Where(wi => workAreaIds.Contains(wi.WorkAreaId) && wi.TenantId == project.TenantId)
|
||||
.GroupBy(wi => wi.WorkAreaId) // Group by parent on the DB server
|
||||
.Select(g => new // Let the DB do the SUM
|
||||
{
|
||||
@ -281,6 +281,7 @@ namespace Marco.Pms.Services.Helpers
|
||||
var projectStatusIds = projects.Select(p => p.ProjectStatusId).Distinct().ToList();
|
||||
var promotorIds = projects.Select(p => p.PromoterId).Distinct().ToList();
|
||||
var pmcsIds = projects.Select(p => p.PMCId).Distinct().ToList();
|
||||
var tenantIds = projects.Select(p => p.TenantId).Distinct().ToList();
|
||||
|
||||
// --- Step 1: Fetch all required data in maximum parallel ---
|
||||
// Each task uses its own DbContext and selects only the required columns (projection).
|
||||
@ -320,7 +321,7 @@ namespace Marco.Pms.Services.Helpers
|
||||
// Server-side aggregation and projection into a dictionary
|
||||
return await context.ProjectAllocations
|
||||
.AsNoTracking()
|
||||
.Where(pa => projectIds.Contains(pa.ProjectId) && pa.IsActive)
|
||||
.Where(pa => projectIds.Contains(pa.ProjectId) && tenantIds.Contains(pa.TenantId) && pa.IsActive)
|
||||
.GroupBy(pa => pa.ProjectId)
|
||||
.Select(g => new { ProjectId = g.Key, Count = g.Count() })
|
||||
.ToDictionaryAsync(x => x.ProjectId, x => x.Count);
|
||||
@ -331,7 +332,7 @@ namespace Marco.Pms.Services.Helpers
|
||||
using var context = _dbContextFactory.CreateDbContext();
|
||||
return await context.Buildings
|
||||
.AsNoTracking()
|
||||
.Where(b => projectIds.Contains(b.ProjectId))
|
||||
.Where(b => projectIds.Contains(b.ProjectId) && tenantIds.Contains(b.TenantId))
|
||||
.Select(b => new { b.Id, b.ProjectId, b.Name, b.Description }) // Projection
|
||||
.ToListAsync();
|
||||
});
|
||||
@ -345,7 +346,7 @@ namespace Marco.Pms.Services.Helpers
|
||||
using var context = _dbContextFactory.CreateDbContext();
|
||||
return await context.Floor
|
||||
.AsNoTracking()
|
||||
.Where(f => buildingIds.Contains(f.BuildingId))
|
||||
.Where(f => buildingIds.Contains(f.BuildingId) && tenantIds.Contains(f.TenantId))
|
||||
.Select(f => new { f.Id, f.BuildingId, f.FloorName }) // Projection
|
||||
.ToListAsync();
|
||||
});
|
||||
@ -359,7 +360,7 @@ namespace Marco.Pms.Services.Helpers
|
||||
using var context = _dbContextFactory.CreateDbContext();
|
||||
return await context.WorkAreas
|
||||
.AsNoTracking()
|
||||
.Where(wa => floorIds.Contains(wa.FloorId))
|
||||
.Where(wa => floorIds.Contains(wa.FloorId) && tenantIds.Contains(wa.TenantId))
|
||||
.Select(wa => new { wa.Id, wa.FloorId, wa.AreaName }) // Projection
|
||||
.ToListAsync();
|
||||
});
|
||||
@ -376,7 +377,7 @@ namespace Marco.Pms.Services.Helpers
|
||||
// Let the DB do the SUM. This is much faster and transfers less data.
|
||||
return await context.WorkItems
|
||||
.AsNoTracking()
|
||||
.Where(wi => workAreaIds.Contains(wi.WorkAreaId))
|
||||
.Where(wi => workAreaIds.Contains(wi.WorkAreaId) && tenantIds.Contains(wi.TenantId))
|
||||
.GroupBy(wi => wi.WorkAreaId)
|
||||
.Select(g => new
|
||||
{
|
||||
@ -808,7 +809,7 @@ namespace Marco.Pms.Services.Helpers
|
||||
}
|
||||
Task<List<string>> getPermissionIdsTask = Task.Run(async () =>
|
||||
{
|
||||
using var context = _dbContextFactory.CreateDbContext();
|
||||
await using var context = await _dbContextFactory.CreateDbContextAsync();
|
||||
|
||||
return await context.RolePermissionMappings
|
||||
.Where(rp => roleIds.Contains(rp.ApplicationRoleId))
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
using Marco.Pms.DataAccess.Data;
|
||||
using Marco.Pms.Helpers.Utility;
|
||||
using Marco.Pms.Model.Dtos.Attendance;
|
||||
using Marco.Pms.Model.Employees;
|
||||
using Marco.Pms.Model.Mail;
|
||||
@ -17,12 +18,14 @@ namespace Marco.Pms.Services.Helpers
|
||||
private readonly IEmailSender _emailSender;
|
||||
private readonly ILoggingService _logger;
|
||||
private readonly CacheUpdateHelper _cache;
|
||||
public ReportHelper(ApplicationDbContext context, IEmailSender emailSender, ILoggingService logger, CacheUpdateHelper cache)
|
||||
private readonly MailLogHelper _mailLogger;
|
||||
public ReportHelper(ApplicationDbContext context, IEmailSender emailSender, ILoggingService logger, CacheUpdateHelper cache, MailLogHelper mailLogger)
|
||||
{
|
||||
_context = context;
|
||||
_emailSender = emailSender;
|
||||
_logger = logger;
|
||||
_cache = cache;
|
||||
_mailLogger = mailLogger;
|
||||
}
|
||||
|
||||
public async Task<ProjectStatisticReport?> GetDailyProjectReportWithOutTenant(Guid projectId, DateTime reportDate)
|
||||
@ -599,7 +602,8 @@ namespace Marco.Pms.Services.Helpers
|
||||
TenantId = tenantId
|
||||
}).ToList();
|
||||
|
||||
_context.MailLogs.AddRange(mailLogs);
|
||||
//_context.MailLogs.AddRange(mailLogs);
|
||||
await _mailLogger.AddWebMenuItemAsync(mailLogs);
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
@ -14,6 +14,7 @@ using Marco.Pms.Model.Dtos.Expenses.Masters;
|
||||
using Marco.Pms.Model.Dtos.Master;
|
||||
using Marco.Pms.Model.Dtos.Organization;
|
||||
using Marco.Pms.Model.Dtos.Project;
|
||||
using Marco.Pms.Model.Dtos.PurchaseInvoice;
|
||||
using Marco.Pms.Model.Dtos.ServiceProject;
|
||||
using Marco.Pms.Model.Dtos.Tenant;
|
||||
using Marco.Pms.Model.Employees;
|
||||
@ -28,10 +29,12 @@ using Marco.Pms.Model.MongoDBModels.Masters;
|
||||
using Marco.Pms.Model.MongoDBModels.Project;
|
||||
using Marco.Pms.Model.OrganizationModel;
|
||||
using Marco.Pms.Model.Projects;
|
||||
using Marco.Pms.Model.PurchaseInvoice;
|
||||
using Marco.Pms.Model.ServiceProject;
|
||||
using Marco.Pms.Model.TenantModels;
|
||||
using Marco.Pms.Model.TenantModels.MongoDBModel;
|
||||
using Marco.Pms.Model.ViewModels.Activities;
|
||||
using Marco.Pms.Model.ViewModels.AppMenu;
|
||||
using Marco.Pms.Model.ViewModels.Collection;
|
||||
using Marco.Pms.Model.ViewModels.Directory;
|
||||
using Marco.Pms.Model.ViewModels.DocumentManager;
|
||||
@ -42,6 +45,7 @@ using Marco.Pms.Model.ViewModels.Expenses.Masters;
|
||||
using Marco.Pms.Model.ViewModels.Master;
|
||||
using Marco.Pms.Model.ViewModels.Organization;
|
||||
using Marco.Pms.Model.ViewModels.Projects;
|
||||
using Marco.Pms.Model.ViewModels.PurchaseInvoice;
|
||||
using Marco.Pms.Model.ViewModels.ServiceProject;
|
||||
using Marco.Pms.Model.ViewModels.Tenant;
|
||||
|
||||
@ -560,26 +564,25 @@ namespace Marco.Pms.Services.MappingProfiles
|
||||
#endregion
|
||||
|
||||
#region ======================================================= AppMenu =======================================================
|
||||
CreateMap<CreateMenuSectionDto, MenuSection>();
|
||||
CreateMap<UpdateMenuSectionDto, MenuSection>();
|
||||
CreateMap<MenuSection, MenuSectionVM>()
|
||||
.ForMember(
|
||||
dest => dest.Name,
|
||||
opt => opt.MapFrom(src => src.Title));
|
||||
CreateMap<CreateWebMenuSectionDto, WebMenuSection>();
|
||||
|
||||
CreateMap<CreateMenuItemDto, MenuItem>();
|
||||
CreateMap<UpdateMenuItemDto, MenuItem>();
|
||||
CreateMap<MenuItem, MenuItemVM>()
|
||||
CreateMap<WebMenuSection, WebMenuSectionVM>()
|
||||
.ForMember(
|
||||
dest => dest.Name,
|
||||
opt => opt.MapFrom(src => src.Text));
|
||||
dest => dest.Items,
|
||||
opt => opt.MapFrom(src => new List<WebSideMenuItem>()));
|
||||
|
||||
CreateMap<CreateSubMenuItemDto, SubMenuItem>();
|
||||
CreateMap<UpdateSubMenuItemDto, SubMenuItem>();
|
||||
CreateMap<SubMenuItem, SubMenuItemVM>()
|
||||
|
||||
CreateMap<CreateWebSideMenuItemDto, WebSideMenuItem>()
|
||||
.ForMember(
|
||||
dest => dest.Name,
|
||||
opt => opt.MapFrom(src => src.Text));
|
||||
dest => dest.Id,
|
||||
opt => opt.MapFrom(src => src.Id.HasValue ? src.Id.Value : Guid.NewGuid()));
|
||||
|
||||
|
||||
CreateMap<WebSideMenuItem, WebSideMenuItemVM>();
|
||||
|
||||
CreateMap<CreateMobileSideMenuItemDto, MobileMenu>();
|
||||
CreateMap<MobileMenu, MenuSectionApplicationVM>();
|
||||
|
||||
#endregion
|
||||
|
||||
#region ======================================================= Directory =======================================================
|
||||
@ -618,6 +621,58 @@ namespace Marco.Pms.Services.MappingProfiles
|
||||
CreateMap<UpdateContactNoteDto, ContactNote>();
|
||||
|
||||
#endregion
|
||||
|
||||
#region ======================================================= Purchase Invoice =======================================================
|
||||
|
||||
CreateMap<PurchaseInvoiceDto, PurchaseInvoiceDetails>()
|
||||
.ForMember(
|
||||
dest => dest.PaymentDueDate,
|
||||
opt => opt.MapFrom(src => src.PaymentDueDate.HasValue ? src.PaymentDueDate : DateTime.UtcNow.AddDays(40)));
|
||||
|
||||
CreateMap<PurchaseInvoiceDetails, PurchaseInvoiceDto>();
|
||||
|
||||
CreateMap<PurchaseInvoiceDetails, PurchaseInvoiceListVM>()
|
||||
.ForMember(
|
||||
dest => dest.PurchaseInvoiceUId,
|
||||
opt => opt.MapFrom(src => $"{src.UIDPrefix}/{src.UIDPostfix:D5}"));
|
||||
|
||||
CreateMap<PurchaseInvoiceDetails, BasicPurchaseInvoiceVM>()
|
||||
.ForMember(
|
||||
dest => dest.PurchaseInvoiceUId,
|
||||
opt => opt.MapFrom(src => $"{src.UIDPrefix}/{src.UIDPostfix:D5}"));
|
||||
CreateMap<PurchaseInvoiceDetails, PurchaseInvoiceDetailsVM>()
|
||||
.ForMember(
|
||||
dest => dest.PurchaseInvoiceUId,
|
||||
opt => opt.MapFrom(src => $"{src.UIDPrefix}/{src.UIDPostfix:D5}"));
|
||||
|
||||
CreateMap<PurchaseInvoiceAttachment, PurchaseInvoiceAttachmentVM>()
|
||||
.ForMember(
|
||||
dest => dest.DocumentId,
|
||||
opt => opt.MapFrom(src => src.DocumentId))
|
||||
.ForMember(
|
||||
dest => dest.FileName,
|
||||
opt => opt.MapFrom(src => src.Document != null ? src.Document.FileName : null))
|
||||
.ForMember(
|
||||
dest => dest.ContentType,
|
||||
opt => opt.MapFrom(src => src.Document != null ? src.Document.ContentType : null))
|
||||
.ForMember(
|
||||
dest => dest.FileSize,
|
||||
opt => opt.MapFrom(src => src.Document != null ? src.Document.FileSize : 0))
|
||||
.ForMember(
|
||||
dest => dest.UploadedAt,
|
||||
opt => opt.MapFrom(src => src.Document != null ? src.Document.UploadedAt : DateTime.UtcNow));
|
||||
|
||||
CreateMap<DeliveryChallanDto, DeliveryChallanDetails>()
|
||||
.ForMember(
|
||||
dest => dest.Attachment,
|
||||
opt => opt.Ignore());
|
||||
|
||||
CreateMap<DeliveryChallanDetails, DeliveryChallanVM>();
|
||||
|
||||
CreateMap<ReceivedInvoicePaymentDto, PurchaseInvoicePayment>();
|
||||
CreateMap<PurchaseInvoicePayment, ReceivedInvoicePaymentVM>();
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -192,6 +192,7 @@ builder.Services.AddScoped<IAesEncryption, AesEncryption>();
|
||||
builder.Services.AddScoped<IOrganizationService, OrganizationService>();
|
||||
builder.Services.AddScoped<ITenantService, TenantService>();
|
||||
builder.Services.AddScoped<IServiceProject, ServiceProjectService>();
|
||||
builder.Services.AddScoped<IPurchaseInvoiceService, PurchaseInvoiceService>();
|
||||
#endregion
|
||||
|
||||
#region Helpers
|
||||
@ -211,6 +212,7 @@ builder.Services.AddScoped<EmployeeCache>();
|
||||
builder.Services.AddScoped<ReportCache>();
|
||||
builder.Services.AddScoped<ExpenseCache>();
|
||||
builder.Services.AddScoped<SidebarMenuHelper>();
|
||||
builder.Services.AddScoped<MailLogHelper>();
|
||||
#endregion
|
||||
|
||||
// Singleton services (one instance for the app's lifetime)
|
||||
|
||||
@ -3644,7 +3644,6 @@ namespace Marco.Pms.Services.Service
|
||||
{
|
||||
// Fetch advance payment transactions for specified employee, including related navigation properties
|
||||
var transactions = await _context.AdvancePaymentTransactions
|
||||
.Include(apt => apt.Project)
|
||||
.Include(apt => apt.Employee).ThenInclude(e => e!.JobRole)
|
||||
.Include(apt => apt.CreatedBy).ThenInclude(e => e!.JobRole)
|
||||
.Where(apt => apt.EmployeeId == employeeId && apt.TenantId == tenantId && apt.IsActive)
|
||||
@ -3658,11 +3657,31 @@ namespace Marco.Pms.Services.Service
|
||||
return ApiResponse<object>.ErrorResponse("No advance payment transactions found.", null, 404);
|
||||
}
|
||||
|
||||
var projectIds = transactions.Select(pr => pr.ProjectId).ToList();
|
||||
|
||||
var infraProjectTask = Task.Run(async () =>
|
||||
{
|
||||
await using var context = await _dbContextFactory.CreateDbContextAsync();
|
||||
return await context.Projects.Where(p => projectIds.Contains(p.Id) && p.TenantId == tenantId).Select(p => _mapper.Map<BasicProjectVM>(p)).ToListAsync();
|
||||
});
|
||||
|
||||
var serviceProjectTask = Task.Run(async () =>
|
||||
{
|
||||
await using var context = await _dbContextFactory.CreateDbContextAsync();
|
||||
return await context.ServiceProjects.Where(sp => projectIds.Contains(sp.Id) && sp.TenantId == tenantId).Select(sp => _mapper.Map<BasicProjectVM>(sp)).ToListAsync();
|
||||
});
|
||||
|
||||
await Task.WhenAll(infraProjectTask, serviceProjectTask);
|
||||
|
||||
var projects = infraProjectTask.Result;
|
||||
projects.AddRange(serviceProjectTask.Result);
|
||||
|
||||
// Map transactions to view model with formatted FinanceUId
|
||||
var response = transactions.Select(transaction =>
|
||||
{
|
||||
var result = _mapper.Map<AdvancePaymentTransactionVM>(transaction);
|
||||
result.FinanceUId = $"{transaction.FinanceUIdPrefix}/{transaction.FinanceUIdPostfix:D5}";
|
||||
result.Project = projects.Where(p => p.Id == transaction.ProjectId).Select(p => _mapper.Map<BasicProjectVM>(p)).FirstOrDefault();
|
||||
return result;
|
||||
}).ToList();
|
||||
|
||||
|
||||
@ -187,6 +187,118 @@ namespace Marco.Pms.Services.Service
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region =================================================================== Purchase Invoice Status APIs ===================================================================
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously retrieves a list of all available Purchase Invoice Statuses.
|
||||
/// </summary>
|
||||
/// <param name="loggedInEmployee">The employee context (used for logging or future permission checks).</param>
|
||||
/// <param name="cancellationToken">Token to observe while waiting for the task to complete.</param>
|
||||
/// <returns>A unified API response containing the list of status DTOs.</returns>
|
||||
public async Task<ApiResponse<object>> GetPurchaseInvoiceStatusAsync(Employee loggedInEmployee, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// 1. Structural Logging: Capture the context of who is performing the action
|
||||
_logger.LogInfo("Initiating fetch of Purchase Invoice Statuses for User ID: {UserId}", loggedInEmployee.Id);
|
||||
|
||||
try
|
||||
{
|
||||
// 2. Performance & Security: Use AsNoTracking, Select specifically (Projection), and handle CancellationToken
|
||||
var invoiceStatuses = await _context.PurchaseInvoiceStatus
|
||||
.AsNoTracking() // Critical for read-only queries to avoid overhead of change tracker
|
||||
.OrderBy(status => status.Name)
|
||||
.ToListAsync(cancellationToken); // Respect request cancellation
|
||||
|
||||
// 3. Logging Success: Log the count for monitoring purposes
|
||||
_logger.LogInfo("Successfully fetched {Count} Purchase Invoice Status records.", invoiceStatuses.Count);
|
||||
|
||||
// 4. Strongly Typed Return: Return specific DTOs, not 'object'
|
||||
return ApiResponse<object>.SuccessResponse(
|
||||
invoiceStatuses,
|
||||
$"{invoiceStatuses.Count} record(s) of Purchase invoice status fetched successfully",
|
||||
200
|
||||
);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// Handle cases where the user cancels the request (browser closed/timeout)
|
||||
_logger.LogWarning("Purchase Invoice Status fetch was cancelled by the user.");
|
||||
return ApiResponse<object>.ErrorResponse(
|
||||
"Request was cancelled",
|
||||
"Operation cancelled.",
|
||||
499 // Client Closed Request
|
||||
);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 5. Security & Error Handling: Log full stack trace securely, return generic message to client
|
||||
_logger.LogError(ex, "Critical error occurred while fetching Purchase Invoice Statuses for User ID: {UserId}", loggedInEmployee.Id);
|
||||
|
||||
// NEVER return 'ex.Message' directly to the client in production (security risk)
|
||||
return ApiResponse<object>.ErrorResponse(
|
||||
"An internal error occurred while processing your request. Please contact support.",
|
||||
"Internal Server Error",
|
||||
500
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region =================================================================== Invoice Attachment Type APIs ===================================================================
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously retrieves the list of valid Invoice Attachment Types.
|
||||
/// </summary>
|
||||
/// <param name="loggedInEmployee">The current user context for audit logging.</param>
|
||||
/// <param name="cancellationToken">Token to propagate notification that operations should be canceled.</param>
|
||||
/// <returns>A standardized API response containing a list of attachment type DTOs.</returns>
|
||||
public async Task<ApiResponse<object>> GetInvoiceAttachmentTypeAsync(Employee loggedInEmployee, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// 1. Structured Logging: Audit WHO is requesting the data
|
||||
_logger.LogInfo("Initiating fetch of Invoice Attachment Types. Requested by User ID: {UserId}", loggedInEmployee.Id);
|
||||
|
||||
try
|
||||
{
|
||||
// 2. Database Optimization:
|
||||
// - AsNoTracking(): Crucial for read-only lists. Bypasses change tracking overhead.
|
||||
// - Select(): Projects directly to DTO. Fetches ONLY needed columns (SQL optimization).
|
||||
var attachmentTypes = await _context.InvoiceAttachmentTypes
|
||||
.AsNoTracking()
|
||||
.OrderBy(type => type.Name)
|
||||
.ToListAsync(cancellationToken); // 3. Pass the token to EF Core
|
||||
|
||||
_logger.LogInfo("Successfully retrieved {Count} Invoice Attachment Types.", attachmentTypes.Count);
|
||||
|
||||
// 4. Strong Typing: Return IEnumerable<Dto> instead of 'object'
|
||||
return ApiResponse<object>.SuccessResponse(
|
||||
attachmentTypes,
|
||||
$"{attachmentTypes.Count} record(s) of invoice attachment type fetched successfully",
|
||||
200
|
||||
);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// Handle request cancellation (e.g., user navigated away)
|
||||
_logger.LogWarning("Invoice Attachment Type fetch operation was canceled by the client.");
|
||||
return ApiResponse<object>.ErrorResponse("Operation canceled", "Request canceled by user", 499);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 5. Security & Error Handling:
|
||||
// Log the real error with stack trace internally.
|
||||
_logger.LogError(ex, "Critical error fetching Invoice Attachment Types for User ID: {UserId}", loggedInEmployee.Id);
|
||||
|
||||
// Return a sanitized message to the client. Never expose raw SQL errors or Stack Traces.
|
||||
return ApiResponse<object>.ErrorResponse(
|
||||
"An unexpected error occurred while processing your request.",
|
||||
"Internal Server Error",
|
||||
500
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region =================================================================== Currency APIs ===================================================================
|
||||
|
||||
public async Task<ApiResponse<object>> GetCurrencyAsync(Employee loggedInEmployee, Guid tenantId)
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
using AutoMapper;
|
||||
using AutoMapper.QueryableExtensions;
|
||||
using Marco.Pms.DataAccess.Data;
|
||||
using Marco.Pms.Model.Dtos.Organization;
|
||||
using Marco.Pms.Model.Employees;
|
||||
@ -13,6 +14,7 @@ using Marco.Pms.Services.Helpers;
|
||||
using Marco.Pms.Services.Service.ServiceInterfaces;
|
||||
using MarcoBMS.Services.Service;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Linq.Dynamic.Core;
|
||||
|
||||
namespace Marco.Pms.Services.Service
|
||||
{
|
||||
@ -150,6 +152,92 @@ namespace Marco.Pms.Services.Service
|
||||
|
||||
return ApiResponse<object>.SuccessResponse(response, "Successfully fetched the organization list", 200);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a paginated, searchable list of organizations.
|
||||
/// Optimized for performance using DB Projections and AsNoTracking.
|
||||
/// </summary>
|
||||
/// <param name="searchString">Optional keyword to filter organizations by name.</param>
|
||||
/// <param name="pageNumber">The requested page number (1-based).</param>
|
||||
/// <param name="pageSize">The number of records per page (Max 50).</param>
|
||||
/// <param name="loggedInEmployee">The current user context for security filtering.</param>
|
||||
/// <param name="ct">Cancellation token to cancel operations if the client disconnects.</param>
|
||||
/// <returns>A paginated list of BasicOrganizationVm.</returns>
|
||||
public async Task<ApiResponse<object>> GetOrganizationBasicListAsync(Guid? id, string? searchString, int pageNumber, int pageSize, Employee loggedInEmployee, CancellationToken ct = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 1. VALIDATION
|
||||
_logger.LogInfo("Fetching Organization list. Page: {Page}, Size: {Size}, Search: {Search}, User: {UserId}",
|
||||
pageNumber, pageSize, searchString ?? "<empty>", loggedInEmployee.Id);
|
||||
|
||||
// 2. QUERY BUILDING
|
||||
// Use AsNoTracking() for read-only scenarios to reduce overhead.
|
||||
var query = _context.Organizations.AsNoTracking()
|
||||
.Where(o => o.IsActive);
|
||||
|
||||
// 3. SECURITY FILTER (Multi-Tenancy)
|
||||
// Enterprise Rule: Always filter by the logged-in user's Tenant/Permissions.
|
||||
// Assuming loggedInEmployee has a TenantId or OrganizationId
|
||||
// query = query.Where(o => o.TenantId == loggedInEmployee.TenantId);
|
||||
|
||||
// 4. DYNAMIC FILTERING
|
||||
if (!string.IsNullOrWhiteSpace(searchString))
|
||||
{
|
||||
var searchTrimmed = searchString.Trim();
|
||||
query = query.Where(o => o.Name.Contains(searchTrimmed));
|
||||
}
|
||||
|
||||
if (id.HasValue)
|
||||
{
|
||||
query = query.Where(o => o.Id == id.Value);
|
||||
}
|
||||
|
||||
// 5. COUNT TOTALS (Efficiently)
|
||||
// Count the total records matching the filter BEFORE applying pagination
|
||||
var totalCount = await query.CountAsync(ct);
|
||||
|
||||
// 6. FETCH DATA (With Projection)
|
||||
// CRITICAL OPTIMIZATION: Use .ProjectTo or .Select BEFORE .ToListAsync.
|
||||
// This ensures SQL only fetches the columns needed for BasicOrganizationVm,
|
||||
// rather than fetching the whole Entity and discarding data in memory.
|
||||
var items = await query
|
||||
.OrderBy(o => o.Name)
|
||||
.Skip((pageNumber - 1) * pageSize)
|
||||
.Take(pageSize)
|
||||
.ProjectTo<BasicOrganizationVm>(_mapper.ConfigurationProvider) // Requires AutoMapper.QueryableExtensions
|
||||
.ToListAsync(ct);
|
||||
|
||||
// 7. PREPARE RESPONSE
|
||||
var totalPages = (int)Math.Ceiling((double)totalCount / pageSize);
|
||||
|
||||
var pagedResult = new
|
||||
{
|
||||
CurrentPage = pageNumber,
|
||||
PageSize = pageSize,
|
||||
TotalPages = totalPages,
|
||||
TotalCount = totalCount,
|
||||
HasPrevious = pageNumber > 1,
|
||||
HasNext = pageNumber < totalPages,
|
||||
Data = items
|
||||
};
|
||||
|
||||
_logger.LogInfo("Successfully fetched {Count} organizations out of {Total}.", items.Count, totalCount);
|
||||
|
||||
return ApiResponse<object>.SuccessResponse(pagedResult, "Organization list fetched successfully.", 200);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// Handle client disconnection gracefully
|
||||
_logger.LogWarning("Organization list fetch was cancelled by the client.");
|
||||
return ApiResponse<object>.ErrorResponse("Request Cancelled", "The operation was cancelled.", 499);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to fetch organization list. User: {UserId}, Error: {Message}", loggedInEmployee.Id, ex.Message);
|
||||
return ApiResponse<object>.ErrorResponse("Data Fetch Failed", "An unexpected error occurred while retrieving the organization list.", 500);
|
||||
}
|
||||
}
|
||||
public async Task<ApiResponse<object>> GetOrganizationDetailsAsync(Guid id, Employee loggedInEmployee, Guid tenantId, Guid loggedOrganizationId)
|
||||
{
|
||||
_logger.LogDebug("Started fetching details for OrganizationId: {OrganizationId}", id);
|
||||
|
||||
@ -4,6 +4,7 @@ using Marco.Pms.Model.Entitlements;
|
||||
using Marco.Pms.Services.Helpers;
|
||||
using MarcoBMS.Services.Helpers;
|
||||
using MarcoBMS.Services.Service;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Marco.Pms.Services.Service
|
||||
@ -11,15 +12,13 @@ namespace Marco.Pms.Services.Service
|
||||
public class PermissionServices
|
||||
{
|
||||
private readonly ApplicationDbContext _context;
|
||||
private readonly RolesHelper _rolesHelper;
|
||||
private readonly CacheUpdateHelper _cache;
|
||||
private readonly ILoggingService _logger;
|
||||
private readonly Guid tenantId;
|
||||
|
||||
public PermissionServices(ApplicationDbContext context, RolesHelper rolesHelper, CacheUpdateHelper cache, ILoggingService logger, UserHelper userHelper)
|
||||
public PermissionServices(ApplicationDbContext context, CacheUpdateHelper cache, ILoggingService logger, UserHelper userHelper)
|
||||
{
|
||||
_context = context;
|
||||
_rolesHelper = rolesHelper;
|
||||
_cache = cache;
|
||||
_logger = logger;
|
||||
tenantId = userHelper.GetTenantId();
|
||||
@ -34,72 +33,23 @@ namespace Marco.Pms.Services.Service
|
||||
/// <returns>True if the user has the permission, otherwise false.</returns>
|
||||
public async Task<bool> HasPermission(Guid featurePermissionId, Guid employeeId, Guid? projectId = null)
|
||||
{
|
||||
// 1. Try fetching permissions from cache (fast-path lookup).
|
||||
var featurePermissionIds = await _cache.GetPermissions(employeeId, tenantId);
|
||||
var featurePermissionIds = await GetPermissionIdsByEmployeeId(employeeId, projectId);
|
||||
|
||||
// If not found in cache, fallback to database (slower).
|
||||
if (featurePermissionIds == null)
|
||||
{
|
||||
var featurePermissions = await _rolesHelper.GetFeaturePermissionByEmployeeId(employeeId, tenantId);
|
||||
featurePermissionIds = featurePermissions.Select(fp => fp.Id).ToList();
|
||||
}
|
||||
|
||||
// 2. Handle project-level permission overrides if a project is specified.
|
||||
if (projectId.HasValue)
|
||||
{
|
||||
// Fetch permissions explicitly assigned to this employee in the project.
|
||||
var projectLevelPermissionIds = await _context.ProjectLevelPermissionMappings
|
||||
.AsNoTracking()
|
||||
.Where(pl => pl.ProjectId == projectId.Value && pl.EmployeeId == employeeId)
|
||||
.Select(pl => pl.PermissionId)
|
||||
.ToListAsync();
|
||||
|
||||
if (projectLevelPermissionIds?.Any() ?? false)
|
||||
{
|
||||
|
||||
// Define modules where project-level overrides apply.
|
||||
var projectLevelModuleIds = new HashSet<Guid>
|
||||
{
|
||||
Guid.Parse("53176ebf-c75d-42e5-839f-4508ffac3def"),
|
||||
Guid.Parse("9d4b5489-2079-40b9-bd77-6e1bf90bc19f"),
|
||||
Guid.Parse("52c9cf54-1eb2-44d2-81bb-524cf29c0a94"),
|
||||
Guid.Parse("a8cf4331-8f04-4961-8360-a3f7c3cc7462")
|
||||
};
|
||||
|
||||
// Get all feature permissions under those modules where the user didn't have explicit project-level grants.
|
||||
var allOverriddenPermissions = await _context.FeaturePermissions
|
||||
.AsNoTracking()
|
||||
.Where(fp => projectLevelModuleIds.Contains(fp.FeatureId) &&
|
||||
!projectLevelPermissionIds.Contains(fp.Id))
|
||||
.Select(fp => fp.Id)
|
||||
.ToListAsync();
|
||||
|
||||
// Apply overrides:
|
||||
// - Remove global permissions overridden by project-level rules.
|
||||
// - Add explicit project-level permissions.
|
||||
featurePermissionIds = featurePermissionIds
|
||||
.Except(allOverriddenPermissions) // Remove overridden
|
||||
.Concat(projectLevelPermissionIds) // Add project-level
|
||||
.Distinct() // Ensure no duplicates
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Final check: does the employee have the requested permission?
|
||||
return featurePermissionIds.Contains(featurePermissionId);
|
||||
}
|
||||
public async Task<bool> HasPermissionAny(List<Guid> featurePermissionIds, Guid employeeId)
|
||||
{
|
||||
var allFeaturePermissionIds = await _cache.GetPermissions(employeeId, tenantId);
|
||||
if (allFeaturePermissionIds == null)
|
||||
{
|
||||
List<FeaturePermission> featurePermission = await _rolesHelper.GetFeaturePermissionByEmployeeId(employeeId, tenantId);
|
||||
allFeaturePermissionIds = featurePermission.Select(fp => fp.Id).ToList();
|
||||
}
|
||||
var allFeaturePermissionIds = await GetPermissionIdsByEmployeeId(employeeId);
|
||||
|
||||
var hasPermission = featurePermissionIds.Any(f => allFeaturePermissionIds.Contains(f));
|
||||
|
||||
return hasPermission;
|
||||
}
|
||||
public bool HasPermissionAny(List<Guid> realPermissionIds, List<Guid> toCheckPermissionIds, Guid employeeId)
|
||||
{
|
||||
var hasPermission = toCheckPermissionIds.Any(f => realPermissionIds.Contains(f));
|
||||
return hasPermission;
|
||||
}
|
||||
public async Task<bool> HasProjectPermission(Employee LoggedInEmployee, Guid projectId)
|
||||
{
|
||||
var employeeId = LoggedInEmployee.Id;
|
||||
@ -199,5 +149,164 @@ namespace Marco.Pms.Services.Service
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves permission IDs for an employee, supporting both global role-based permissions
|
||||
/// and project-specific overrides with cache-first strategy.
|
||||
/// </summary>
|
||||
/// <param name="employeeId">The ID of the employee to fetch permissions for.</param>
|
||||
/// <param name="projectId">Optional project ID for project-level permission overrides.</param>
|
||||
/// <returns>List of unique permission IDs the employee has access to.</returns>
|
||||
/// <exception cref="ArgumentException">Thrown when employeeId or tenantId is empty.</exception>
|
||||
public async Task<List<Guid>> GetPermissionIdsByEmployeeId(Guid employeeId, Guid? projectId = null)
|
||||
{
|
||||
// Input validation
|
||||
if (employeeId == Guid.Empty)
|
||||
{
|
||||
_logger.LogWarning("EmployeeId cannot be empty.");
|
||||
return new List<Guid>();
|
||||
}
|
||||
|
||||
if (tenantId == Guid.Empty)
|
||||
{
|
||||
_logger.LogWarning("TenantId cannot be empty.");
|
||||
return new List<Guid>();
|
||||
}
|
||||
_logger.LogDebug(
|
||||
"GetPermissionIdsByEmployeeId started. EmployeeId: {EmployeeId}, ProjectId: {ProjectId}, TenantId: {TenantId}",
|
||||
employeeId, projectId ?? Guid.Empty, tenantId);
|
||||
|
||||
try
|
||||
{
|
||||
// Phase 1: Cache-first lookup for role-based permissions (fast path)
|
||||
var featurePermissionIds = await _cache.GetPermissions(employeeId, tenantId);
|
||||
var permissionsFromCache = featurePermissionIds != null;
|
||||
|
||||
_logger.LogDebug(
|
||||
"Permission lookup from cache: {CacheHit}, InitialPermissions: {PermissionCount}, EmployeeId: {EmployeeId}",
|
||||
permissionsFromCache, featurePermissionIds?.Count ?? 0, employeeId);
|
||||
|
||||
// Phase 2: Database fallback if cache miss
|
||||
if (featurePermissionIds == null)
|
||||
{
|
||||
_logger.LogDebug(
|
||||
"Cache miss detected, falling back to database lookup. EmployeeId: {EmployeeId}, TenantId: {TenantId}",
|
||||
employeeId, tenantId);
|
||||
|
||||
var roleIds = await _context.EmployeeRoleMappings
|
||||
.Where(erm => erm.EmployeeId == employeeId && erm.TenantId == tenantId)
|
||||
.Select(erm => erm.RoleId)
|
||||
.ToListAsync();
|
||||
|
||||
if (!roleIds.Any())
|
||||
{
|
||||
_logger.LogDebug(
|
||||
"No roles found for employee. EmployeeId: {EmployeeId}, TenantId: {TenantId}",
|
||||
employeeId, tenantId);
|
||||
return new List<Guid>();
|
||||
}
|
||||
|
||||
featurePermissionIds = await _context.RolePermissionMappings
|
||||
.Where(rpm => roleIds.Contains(rpm.ApplicationRoleId))
|
||||
.Select(rpm => rpm.FeaturePermissionId)
|
||||
.Distinct()
|
||||
.ToListAsync();
|
||||
|
||||
// The cache service might also need its own context, or you can pass the data directly.
|
||||
// Assuming AddApplicationRole takes the data, not a context.
|
||||
await _cache.AddApplicationRole(employeeId, roleIds, tenantId);
|
||||
_logger.LogInfo("Successfully queued cache update for EmployeeId: {EmployeeId}", employeeId);
|
||||
|
||||
_logger.LogDebug(
|
||||
"Loaded {RoleCount} roles → {PermissionCount} permissions from database. EmployeeId: {EmployeeId}",
|
||||
roleIds.Count, featurePermissionIds.Count, employeeId);
|
||||
}
|
||||
|
||||
// Early return for global permissions (no project context)
|
||||
if (!projectId.HasValue)
|
||||
{
|
||||
_logger.LogDebug(
|
||||
"Returning global permissions. Count: {PermissionCount}, EmployeeId: {EmployeeId}",
|
||||
featurePermissionIds.Count, employeeId);
|
||||
return featurePermissionIds;
|
||||
}
|
||||
|
||||
// Phase 3: Apply project-level permission overrides
|
||||
_logger.LogDebug(
|
||||
"Applying project-level overrides. ProjectId: {ProjectId}, EmployeeId: {EmployeeId}",
|
||||
projectId.Value, employeeId);
|
||||
|
||||
var projectLevelPermissionIds = await _context.ProjectLevelPermissionMappings
|
||||
.AsNoTracking()
|
||||
.Where(pl => pl.ProjectId == projectId.Value &&
|
||||
pl.EmployeeId == employeeId)
|
||||
.Select(pl => pl.PermissionId)
|
||||
.Distinct()
|
||||
.ToListAsync();
|
||||
|
||||
if (!projectLevelPermissionIds.Any())
|
||||
{
|
||||
_logger.LogDebug(
|
||||
"No project-level permissions found. ProjectId: {ProjectId}, EmployeeId: {EmployeeId}",
|
||||
projectId.Value, employeeId);
|
||||
return featurePermissionIds;
|
||||
}
|
||||
|
||||
// Phase 4: Override logic for specific project modules
|
||||
var projectOverrideModules = new HashSet<Guid>
|
||||
{
|
||||
// Hard-coded module IDs for project-level override scope
|
||||
// TODO: Consider moving to configuration or database lookup
|
||||
Guid.Parse("53176ebf-c75d-42e5-839f-4508ffac3def"), // Module: Projects
|
||||
Guid.Parse("9d4b5489-2079-40b9-bd77-6e1bf90bc19f"), // Module: Expenses
|
||||
Guid.Parse("52c9cf54-1eb2-44d2-81bb-524cf29c0a94"), // Module: Invoices
|
||||
Guid.Parse("a8cf4331-8f04-4961-8360-a3f7c3cc7462") // Module: Documents
|
||||
};
|
||||
|
||||
// Find permissions in override modules that employee lacks at project level
|
||||
var overriddenPermissions = await _context.FeaturePermissions
|
||||
.AsNoTracking()
|
||||
.Where(fp => projectOverrideModules.Contains(fp.FeatureId) &&
|
||||
!projectLevelPermissionIds.Contains(fp.Id))
|
||||
.Select(fp => fp.Id)
|
||||
.ToListAsync();
|
||||
|
||||
// Apply override rules:
|
||||
// 1. Remove global permissions overridden by project context
|
||||
// 2. Add explicit project-level grants
|
||||
// 3. Ensure uniqueness
|
||||
var finalPermissions = featurePermissionIds
|
||||
.Except(overriddenPermissions) // Remove overridden global perms
|
||||
.Concat(projectLevelPermissionIds) // Add project-specific grants
|
||||
.Distinct() // Deduplicate
|
||||
.ToList();
|
||||
|
||||
_logger.LogDebug(
|
||||
"Project override applied. Before: {BeforeCount}, After: {AfterCount}, Added: {AddedCount}, Removed: {RemovedCount}, EmployeeId: {EmployeeId}",
|
||||
featurePermissionIds.Count, finalPermissions.Count,
|
||||
projectLevelPermissionIds.Count, overriddenPermissions.Count, employeeId);
|
||||
|
||||
return finalPermissions;
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
_logger.LogWarning("GetPermissionIdsByEmployeeId cancelled. EmployeeId: {EmployeeId}", employeeId);
|
||||
|
||||
return new List<Guid>();
|
||||
}
|
||||
catch (DbUpdateException ex)
|
||||
{
|
||||
_logger.LogError(ex, "Database error in GetPermissionIdsByEmployeeId. EmployeeId: {EmployeeId}, TenantId: {TenantId}", employeeId, tenantId);
|
||||
|
||||
return new List<Guid>();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Unexpected error in GetPermissionIdsByEmployeeId. EmployeeId: {EmployeeId}, TenantId: {TenantId}", employeeId, tenantId);
|
||||
|
||||
return new List<Guid>();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -61,7 +61,7 @@ namespace Marco.Pms.Services.Service
|
||||
/// <param name="loggedInEmployee">Authenticated employee requesting the data.</param>
|
||||
/// <param name="tenantId">Tenant identifier to ensure multi-tenant data isolation.</param>
|
||||
/// <returns>Returns an ApiResponse containing the distinct combined list of basic project view models or an error response.</returns>
|
||||
public async Task<ApiResponse<object>> GetBothProjectBasicListAsync(string? searchString, Employee loggedInEmployee, Guid tenantId)
|
||||
public async Task<ApiResponse<object>> GetBothProjectBasicListAsync(Guid? id, string? searchString, Employee loggedInEmployee, Guid tenantId)
|
||||
{
|
||||
if (tenantId == Guid.Empty)
|
||||
{
|
||||
@ -88,6 +88,10 @@ namespace Marco.Pms.Services.Service
|
||||
.Where(p => p.Name.ToLower().Contains(normalized) ||
|
||||
(!string.IsNullOrWhiteSpace(p.ShortName) && p.ShortName.ToLower().Contains(normalized)));
|
||||
}
|
||||
if (id.HasValue)
|
||||
{
|
||||
infraProjectsQuery = infraProjectsQuery.Where(p => p.Id == id.Value);
|
||||
}
|
||||
|
||||
var infraProjects = await infraProjectsQuery.ToListAsync();
|
||||
return infraProjects.Select(p => _mapper.Map<BasicProjectVM>(p)).ToList();
|
||||
@ -108,6 +112,11 @@ namespace Marco.Pms.Services.Service
|
||||
(!string.IsNullOrWhiteSpace(sp.ShortName) && sp.ShortName.ToLower().Contains(normalized)));
|
||||
}
|
||||
|
||||
if (id.HasValue)
|
||||
{
|
||||
serviceProjectsQuery = serviceProjectsQuery.Where(sp => sp.Id == id.Value);
|
||||
}
|
||||
|
||||
var serviceProjects = await serviceProjectsQuery.ToListAsync();
|
||||
return serviceProjects.Select(sp => _mapper.Map<BasicProjectVM>(sp)).ToList();
|
||||
});
|
||||
|
||||
1741
Marco.Pms.Services/Service/PurchaseInvoiceService.cs
Normal file
1741
Marco.Pms.Services/Service/PurchaseInvoiceService.cs
Normal file
File diff suppressed because it is too large
Load Diff
@ -22,6 +22,14 @@ namespace Marco.Pms.Services.Service.ServiceInterfaces
|
||||
Task<ApiResponse<object>> GetRecurringPaymentStatusAsync(Employee loggedInEmployee, Guid tenantId);
|
||||
#endregion
|
||||
|
||||
#region =================================================================== Purchase Invoice Status APIs ===================================================================
|
||||
Task<ApiResponse<object>> GetPurchaseInvoiceStatusAsync(Employee loggedInEmployee, CancellationToken cancellationToken);
|
||||
|
||||
#endregion
|
||||
#region =================================================================== Invoice Attachment Type APIs ===================================================================
|
||||
Task<ApiResponse<object>> GetInvoiceAttachmentTypeAsync(Employee loggedInEmployee, CancellationToken cancellationToken);
|
||||
|
||||
#endregion
|
||||
#region =================================================================== Currency APIs ===================================================================
|
||||
Task<ApiResponse<object>> GetCurrencyAsync(Employee loggedInEmployee, Guid tenantId);
|
||||
|
||||
|
||||
@ -8,6 +8,7 @@ namespace Marco.Pms.Services.Service.ServiceInterfaces
|
||||
{
|
||||
#region =================================================================== Get Functions ===================================================================
|
||||
Task<ApiResponse<object>> GetOrganizarionListAsync(string? searchString, long? sprid, bool active, int pageNumber, int pageSize, Employee loggedInEmployee, Guid tenantId, Guid loggedOrganizationId);
|
||||
Task<ApiResponse<object>> GetOrganizationBasicListAsync(Guid? id, string? searchString, int pageNumber, int pageSize, Employee loggedInEmployee, CancellationToken ct);
|
||||
Task<ApiResponse<object>> GetOrganizationDetailsAsync(Guid id, Employee loggedInEmployee, Guid tenantId, Guid loggedOrganizationId);
|
||||
Task<ApiResponse<object>> GetOrganizationHierarchyListAsync(Guid employeeId, Employee loggedInEmployee, Guid tenantId, Guid loggedOrganizationId);
|
||||
#endregion
|
||||
|
||||
@ -10,7 +10,7 @@ namespace Marco.Pms.Services.Service.ServiceInterfaces
|
||||
{
|
||||
public interface IProjectServices
|
||||
{
|
||||
Task<ApiResponse<object>> GetBothProjectBasicListAsync(string? searchString, Employee loggedInEmployee, Guid tenantId);
|
||||
Task<ApiResponse<object>> GetBothProjectBasicListAsync(Guid? id, string? searchString, Employee loggedInEmployee, Guid tenantId);
|
||||
Task<ApiResponse<object>> GetAllProjectsBasicAsync(bool provideAll, Employee loggedInEmployee, Guid tenantId);
|
||||
Task<ApiResponse<object>> GetAllProjectsAsync(string? searchString, int pageNumber, int pageSize, Employee loggedInEmployee, Guid tenantId);
|
||||
Task<ApiResponse<object>> GetProjectAsync(Guid id, Employee loggedInEmployee, Guid tenantId);
|
||||
|
||||
@ -0,0 +1,36 @@
|
||||
using Marco.Pms.Model.Dtos.Collection;
|
||||
using Marco.Pms.Model.Dtos.PurchaseInvoice;
|
||||
using Marco.Pms.Model.Employees;
|
||||
using Marco.Pms.Model.PurchaseInvoice;
|
||||
using Marco.Pms.Model.Utilities;
|
||||
using Marco.Pms.Model.ViewModels.PurchaseInvoice;
|
||||
|
||||
namespace Marco.Pms.Services.Service.ServiceInterfaces
|
||||
{
|
||||
public interface IPurchaseInvoiceService
|
||||
{
|
||||
#region =================================================================== Purchase Invoice Functions ===================================================================
|
||||
Task<ApiResponse<object>> GetPurchaseInvoiceListAsync(string? searchString, string? filter, bool isActive, int pageSize, int pageNumber,
|
||||
Employee loggedInEmployee, Guid tenantId, CancellationToken ct);
|
||||
Task<ApiResponse<PurchaseInvoiceDetailsVM>> GetPurchaseInvoiceDetailsAsync(Guid id, Employee loggedInEmployee, Guid tenantId, CancellationToken ct);
|
||||
Task<ApiResponse<PurchaseInvoiceListVM>> CreatePurchaseInvoiceAsync(PurchaseInvoiceDto model, Employee loggedInEmployee, Guid tenantId, CancellationToken ct);
|
||||
Task<ApiResponse<object>> UpdatePurchaseInvoiceAsync(Guid id, PurchaseInvoiceDetails purchaseInvoice, PurchaseInvoiceDto model, Employee loggedInEmployee, Guid tenantId, CancellationToken ct);
|
||||
Task<ApiResponse<object>> DeletePurchaseInvoiceAsync(Guid id, bool isActive, Employee loggedInEmployee, Guid tenantId, CancellationToken ct);
|
||||
|
||||
#endregion
|
||||
|
||||
#region =================================================================== Delivery Challan Functions ===================================================================
|
||||
Task<ApiResponse<List<DeliveryChallanVM>>> GetDeliveryChallansAsync(Guid purchaseInvoiceId, Employee loggedInEmployee, Guid tenantId, CancellationToken ct);
|
||||
Task<ApiResponse<DeliveryChallanVM>> AddDeliveryChallanAsync(DeliveryChallanDto model, Employee loggedInEmployee, Guid tenantId, CancellationToken ct);
|
||||
#endregion
|
||||
|
||||
#region =================================================================== Purchase Invoice History Functions ===================================================================
|
||||
Task<ApiResponse<object>> GetPurchaseInvoiceHistoryListAsync(Guid purchaseInvoiceId, Employee loggedInEmployee, Guid tenantId, CancellationToken ct);
|
||||
Task<ApiResponse<object>> AddPurchaseInvoicePaymentAsync(ReceivedInvoicePaymentDto model, Employee loggedInEmployee, Guid tenantId, CancellationToken ct);
|
||||
#endregion
|
||||
|
||||
#region =================================================================== Helper Functions ===================================================================
|
||||
Task<PurchaseInvoiceDetails?> GetPurchaseInvoiceByIdAsync(Guid id, Guid tenantId, CancellationToken ct);
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -42,7 +42,8 @@
|
||||
"MongoDB": {
|
||||
"SerilogDatabaseUrl": "mongodb://devuser:DevPass123@147.93.98.152:27017/DotNetLogsLocalDev?authSource=admin&eplicaSet=rs01&directConnection=true",
|
||||
"ConnectionString": "mongodb://devuser:DevPass123@147.93.98.152:27017/MarcoBMSLocalCache?authSource=admin&eplicaSet=rs01&directConnection=true&socketTimeoutMS=500&serverSelectionTimeoutMS=500&connectTimeoutMS=500",
|
||||
"ModificationConnectionString": "mongodb://devuser:DevPass123@147.93.98.152:27017/MarcoBMSLocalDev?authSource=admin&eplicaSet=rs01&directConnection=true"
|
||||
"ModificationConnectionString": "mongodb://devuser:DevPass123@147.93.98.152:27017/MarcoBMSLocalDev?authSource=admin&eplicaSet=rs01&directConnection=true",
|
||||
"MailConnectionString": "mongodb://devuser:DevPass123@147.93.98.152:27017/MailLogsLocalDev?authSource=admin&eplicaSet=rs01&directConnection=true"
|
||||
},
|
||||
"Razorpay": {
|
||||
"Key": "rzp_test_RXCzgEcXucbuAi",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user