Added the payment related APIs
This commit is contained in:
parent
7e634e3a47
commit
c3b93022fd
@ -11,6 +11,7 @@ using Marco.Pms.Model.Forum;
|
||||
using Marco.Pms.Model.Mail;
|
||||
using Marco.Pms.Model.Master;
|
||||
using Marco.Pms.Model.OrganizationModel;
|
||||
using Marco.Pms.Model.PaymentGetway;
|
||||
using Marco.Pms.Model.Projects;
|
||||
using Marco.Pms.Model.Roles;
|
||||
using Marco.Pms.Model.TenantModels;
|
||||
@ -141,6 +142,9 @@ namespace Marco.Pms.DataAccess.Data
|
||||
public DbSet<ReceivedInvoicePayment> ReceivedInvoicePayments { get; set; }
|
||||
public DbSet<PaymentAdjustmentHead> PaymentAdjustmentHeads { get; set; }
|
||||
|
||||
public DbSet<PaymentDetail> PaymentDetails { get; set; }
|
||||
public DbSet<TenantEnquire> TenantEnquires { get; set; }
|
||||
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
|
||||
6783
Marco.Pms.DataAccess/Migrations/20251029065544_Added_Payment_Related_Tables.Designer.cs
generated
Normal file
6783
Marco.Pms.DataAccess/Migrations/20251029065544_Added_Payment_Related_Tables.Designer.cs
generated
Normal file
File diff suppressed because one or more lines are too long
@ -0,0 +1,469 @@
|
||||
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_Payment_Related_Tables : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<Guid>(
|
||||
name: "PaymentDetailId",
|
||||
table: "TenantSubscriptions",
|
||||
type: "char(36)",
|
||||
nullable: true,
|
||||
collation: "ascii_general_ci");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Invoices",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
|
||||
Title = table.Column<string>(type: "longtext", nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
Description = table.Column<string>(type: "longtext", nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
InvoiceNumber = table.Column<string>(type: "longtext", nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
EInvoiceNumber = table.Column<string>(type: "longtext", nullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
ProjectId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
|
||||
InvoiceDate = table.Column<DateTime>(type: "datetime(6)", nullable: false),
|
||||
ClientSubmitedDate = table.Column<DateTime>(type: "datetime(6)", nullable: false),
|
||||
ExceptedPaymentDate = table.Column<DateTime>(type: "datetime(6)", nullable: false),
|
||||
BasicAmount = table.Column<double>(type: "double", nullable: false),
|
||||
TaxAmount = table.Column<double>(type: "double", nullable: false),
|
||||
IsActive = table.Column<bool>(type: "tinyint(1)", nullable: false),
|
||||
MarkAsCompleted = table.Column<bool>(type: "tinyint(1)", nullable: false),
|
||||
CreatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false),
|
||||
CreatedById = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
|
||||
UpdatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: true),
|
||||
UpdatedById = table.Column<Guid>(type: "char(36)", nullable: true, collation: "ascii_general_ci"),
|
||||
TenantId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci")
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Invoices", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_Invoices_Employees_CreatedById",
|
||||
column: x => x.CreatedById,
|
||||
principalTable: "Employees",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_Invoices_Employees_UpdatedById",
|
||||
column: x => x.UpdatedById,
|
||||
principalTable: "Employees",
|
||||
principalColumn: "Id");
|
||||
table.ForeignKey(
|
||||
name: "FK_Invoices_Projects_ProjectId",
|
||||
column: x => x.ProjectId,
|
||||
principalTable: "Projects",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_Invoices_Tenants_TenantId",
|
||||
column: x => x.TenantId,
|
||||
principalTable: "Tenants",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
})
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "PaymentAdjustmentHeads",
|
||||
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: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
IsActive = table.Column<bool>(type: "tinyint(1)", nullable: false),
|
||||
TenantId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci")
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_PaymentAdjustmentHeads", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_PaymentAdjustmentHeads_Tenants_TenantId",
|
||||
column: x => x.TenantId,
|
||||
principalTable: "Tenants",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
})
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "PaymentDetails",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
|
||||
PaymentId = table.Column<string>(type: "longtext", nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
OrderId = table.Column<string>(type: "longtext", nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
Status = table.Column<string>(type: "longtext", nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
Method = table.Column<string>(type: "longtext", nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
EncryptedDetails = table.Column<byte[]>(type: "longblob", nullable: true),
|
||||
Nonce = table.Column<byte[]>(type: "longblob", nullable: true),
|
||||
Tag = table.Column<byte[]>(type: "longblob", nullable: true),
|
||||
CreatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_PaymentDetails", x => x.Id);
|
||||
})
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "TenantEnquires",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
|
||||
FirstName = table.Column<string>(type: "longtext", nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
LastName = table.Column<string>(type: "longtext", nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
OrganizationName = table.Column<string>(type: "longtext", nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
Email = table.Column<string>(type: "longtext", nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
ContactNumber = table.Column<string>(type: "longtext", nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
BillingAddress = table.Column<string>(type: "longtext", nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
OrganizationSize = table.Column<string>(type: "longtext", nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
IndustryId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
|
||||
Reference = table.Column<string>(type: "longtext", nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4")
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_TenantEnquires", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_TenantEnquires_Industries_IndustryId",
|
||||
column: x => x.IndustryId,
|
||||
principalTable: "Industries",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
})
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "InvoiceAttachments",
|
||||
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"),
|
||||
DocumentId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
|
||||
TenantId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci")
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_InvoiceAttachments", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_InvoiceAttachments_Documents_DocumentId",
|
||||
column: x => x.DocumentId,
|
||||
principalTable: "Documents",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_InvoiceAttachments_Invoices_InvoiceId",
|
||||
column: x => x.InvoiceId,
|
||||
principalTable: "Invoices",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_InvoiceAttachments_Tenants_TenantId",
|
||||
column: x => x.TenantId,
|
||||
principalTable: "Tenants",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
})
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "InvoiceComments",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
|
||||
Comment = table.Column<string>(type: "longtext", nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
CreatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false),
|
||||
CreatedById = 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"),
|
||||
TenantId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci")
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_InvoiceComments", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_InvoiceComments_Employees_CreatedById",
|
||||
column: x => x.CreatedById,
|
||||
principalTable: "Employees",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_InvoiceComments_Invoices_InvoiceId",
|
||||
column: x => x.InvoiceId,
|
||||
principalTable: "Invoices",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_InvoiceComments_Tenants_TenantId",
|
||||
column: x => x.TenantId,
|
||||
principalTable: "Tenants",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
})
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ReceivedInvoicePayments",
|
||||
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_ReceivedInvoicePayments", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_ReceivedInvoicePayments_Employees_CreatedById",
|
||||
column: x => x.CreatedById,
|
||||
principalTable: "Employees",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_ReceivedInvoicePayments_Invoices_InvoiceId",
|
||||
column: x => x.InvoiceId,
|
||||
principalTable: "Invoices",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_ReceivedInvoicePayments_PaymentAdjustmentHeads_PaymentAdjust~",
|
||||
column: x => x.PaymentAdjustmentHeadId,
|
||||
principalTable: "PaymentAdjustmentHeads",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_ReceivedInvoicePayments_Tenants_TenantId",
|
||||
column: x => x.TenantId,
|
||||
principalTable: "Tenants",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
})
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.InsertData(
|
||||
table: "Features",
|
||||
columns: new[] { "Id", "Description", "IsActive", "ModuleId", "Name" },
|
||||
values: new object[] { new Guid("fc586e7d-ed1a-45e5-bb51-9f34af98ec13"), "Collection Management is a feature that enables organizations to track, organize, and manage the status and recovery of receivables or assets efficiently throughout their lifecycle, supporting systematic follow-up and resolution of outstanding accounts.", true, new Guid("bf59fd88-b57a-4d67-bf01-3780f385896b"), "Collection Management" });
|
||||
|
||||
migrationBuilder.InsertData(
|
||||
table: "PaymentAdjustmentHeads",
|
||||
columns: new[] { "Id", "Description", "IsActive", "Name", "TenantId" },
|
||||
values: new object[,]
|
||||
{
|
||||
{ new Guid("0d70cb2e-827e-44fc-90a5-c2c55ba51ba9"), "TDS, or Tax Deducted at Source, is a system under the Indian Income Tax Act where tax is deducted at the point of income generation—such as salary, interest, or rent—and remitted to the government to prevent tax evasion and ensure timely collection.", true, "Tax Deducted at Source (TDS)", new Guid("b3466e83-7e11-464c-b93a-daf047838b26") },
|
||||
{ new Guid("3f09b19a-8d45-4cf2-be27-f4f09b38b9f7"), "Tax is a mandatory financial charge imposed by a government on individuals or entities to fund public services and government operations, without direct benefit to the taxpayer.", true, "Tax", new Guid("b3466e83-7e11-464c-b93a-daf047838b26") },
|
||||
{ new Guid("50584332-1cb7-4359-9721-c8ea35040881"), "Utility fees are recurring charges for essential services such as electricity, water, gas, sewage, waste disposal, internet, and telecommunications, typically based on usage and necessary for operating a home or business.", true, "Utility fees", new Guid("b3466e83-7e11-464c-b93a-daf047838b26") },
|
||||
{ new Guid("66c3c241-8b52-4327-a5ad-c1faf102583e"), "The base amount refers to the principal sum or original value used as a reference in financial calculations, excluding taxes, fees, or additional charges.", true, "Base Amount", new Guid("b3466e83-7e11-464c-b93a-daf047838b26") },
|
||||
{ new Guid("95f35acd-d979-4177-91ea-fd03a00e49ff"), "Retention refers to a company's ability to keep customers, employees, or profits over time, commonly measured as a percentage and critical for long-term business sustainability and growth.", true, "Retention", new Guid("b3466e83-7e11-464c-b93a-daf047838b26") },
|
||||
{ new Guid("dbdc047f-a2d2-4db0-b0e6-b9d9f923a0f1"), "An advance payment is a sum paid before receiving goods or services, often to secure a transaction or cover initial costs.", true, "Advance payment", new Guid("b3466e83-7e11-464c-b93a-daf047838b26") },
|
||||
{ new Guid("ec5e6a5f-ce62-44e5-8911-8426bbb4dde8"), "A penalty in the context of taxation is a financial sanction imposed by the government on individuals or entities for non-compliance with tax laws, such as late filing, underreporting income, or failure to pay taxes, and is typically calculated as a percentage of the tax due or a fixed amount.", true, "Penalty", new Guid("b3466e83-7e11-464c-b93a-daf047838b26") }
|
||||
});
|
||||
|
||||
migrationBuilder.InsertData(
|
||||
table: "FeaturePermissions",
|
||||
columns: new[] { "Id", "Description", "FeatureId", "IsEnabled", "Name" },
|
||||
values: new object[,]
|
||||
{
|
||||
{ new Guid("061d9ccd-85b4-4cb0-be06-2f9f32cebb72"), " Enables entry and processing of payment transactions.", new Guid("fc586e7d-ed1a-45e5-bb51-9f34af98ec13"), true, "Add Payment" },
|
||||
{ new Guid("455187b4-fef1-41f9-b3d0-025d0b6302c3"), "Ability to modify collection properties, content, and access rights.", new Guid("fc586e7d-ed1a-45e5-bb51-9f34af98ec13"), true, "Edit Collection" },
|
||||
{ new Guid("b93141fd-dbd3-4051-8f57-bf25d18e3555"), "Authorizes users to create new collections for organizing related resources and managing access", new Guid("fc586e7d-ed1a-45e5-bb51-9f34af98ec13"), true, "Create Collection" },
|
||||
{ new Guid("c8d7eea5-4033-4aad-9ebe-76de49896830"), "View Collection is a permission that allows users to see and browse assets or items within a collection without making any modifications or edits to its contents.", new Guid("fc586e7d-ed1a-45e5-bb51-9f34af98ec13"), true, "View Collection" },
|
||||
{ new Guid("dbf17591-09fe-4c93-9e1a-12db8f5cc5de"), "Collection Admin is a permission that grants a user full administrative control over collections, including creating, editing, managing access, and deleting collections within a system.", new Guid("fc586e7d-ed1a-45e5-bb51-9f34af98ec13"), true, "Collection Admin" }
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_TenantSubscriptions_PaymentDetailId",
|
||||
table: "TenantSubscriptions",
|
||||
column: "PaymentDetailId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_InvoiceAttachments_DocumentId",
|
||||
table: "InvoiceAttachments",
|
||||
column: "DocumentId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_InvoiceAttachments_InvoiceId",
|
||||
table: "InvoiceAttachments",
|
||||
column: "InvoiceId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_InvoiceAttachments_TenantId",
|
||||
table: "InvoiceAttachments",
|
||||
column: "TenantId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_InvoiceComments_CreatedById",
|
||||
table: "InvoiceComments",
|
||||
column: "CreatedById");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_InvoiceComments_InvoiceId",
|
||||
table: "InvoiceComments",
|
||||
column: "InvoiceId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_InvoiceComments_TenantId",
|
||||
table: "InvoiceComments",
|
||||
column: "TenantId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Invoices_CreatedById",
|
||||
table: "Invoices",
|
||||
column: "CreatedById");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Invoices_ProjectId",
|
||||
table: "Invoices",
|
||||
column: "ProjectId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Invoices_TenantId",
|
||||
table: "Invoices",
|
||||
column: "TenantId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Invoices_UpdatedById",
|
||||
table: "Invoices",
|
||||
column: "UpdatedById");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_PaymentAdjustmentHeads_TenantId",
|
||||
table: "PaymentAdjustmentHeads",
|
||||
column: "TenantId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ReceivedInvoicePayments_CreatedById",
|
||||
table: "ReceivedInvoicePayments",
|
||||
column: "CreatedById");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ReceivedInvoicePayments_InvoiceId",
|
||||
table: "ReceivedInvoicePayments",
|
||||
column: "InvoiceId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ReceivedInvoicePayments_PaymentAdjustmentHeadId",
|
||||
table: "ReceivedInvoicePayments",
|
||||
column: "PaymentAdjustmentHeadId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ReceivedInvoicePayments_TenantId",
|
||||
table: "ReceivedInvoicePayments",
|
||||
column: "TenantId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_TenantEnquires_IndustryId",
|
||||
table: "TenantEnquires",
|
||||
column: "IndustryId");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_TenantSubscriptions_PaymentDetails_PaymentDetailId",
|
||||
table: "TenantSubscriptions",
|
||||
column: "PaymentDetailId",
|
||||
principalTable: "PaymentDetails",
|
||||
principalColumn: "Id");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_TenantSubscriptions_PaymentDetails_PaymentDetailId",
|
||||
table: "TenantSubscriptions");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "InvoiceAttachments");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "InvoiceComments");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "PaymentDetails");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "ReceivedInvoicePayments");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "TenantEnquires");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Invoices");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "PaymentAdjustmentHeads");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_TenantSubscriptions_PaymentDetailId",
|
||||
table: "TenantSubscriptions");
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "FeaturePermissions",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("061d9ccd-85b4-4cb0-be06-2f9f32cebb72"));
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "FeaturePermissions",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("455187b4-fef1-41f9-b3d0-025d0b6302c3"));
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "FeaturePermissions",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("b93141fd-dbd3-4051-8f57-bf25d18e3555"));
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "FeaturePermissions",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("c8d7eea5-4033-4aad-9ebe-76de49896830"));
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "FeaturePermissions",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("dbf17591-09fe-4c93-9e1a-12db8f5cc5de"));
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "Features",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("fc586e7d-ed1a-45e5-bb51-9f34af98ec13"));
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "PaymentDetailId",
|
||||
table: "TenantSubscriptions");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -369,6 +369,273 @@ namespace Marco.Pms.DataAccess.Migrations
|
||||
b.ToTable("RefreshTokens");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Marco.Pms.Model.Collection.Invoice", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<double>("BasicAmount")
|
||||
.HasColumnType("double");
|
||||
|
||||
b.Property<DateTime>("ClientSubmitedDate")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<Guid>("CreatedById")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("EInvoiceNumber")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<DateTime>("ExceptedPaymentDate")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<DateTime>("InvoiceDate")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<string>("InvoiceNumber")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<bool>("IsActive")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<bool>("MarkAsCompleted")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<Guid>("ProjectId")
|
||||
.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<DateTime?>("UpdatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<Guid?>("UpdatedById")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("CreatedById");
|
||||
|
||||
b.HasIndex("ProjectId");
|
||||
|
||||
b.HasIndex("TenantId");
|
||||
|
||||
b.HasIndex("UpdatedById");
|
||||
|
||||
b.ToTable("Invoices");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Marco.Pms.Model.Collection.InvoiceAttachment", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<Guid>("DocumentId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<Guid>("InvoiceId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<Guid>("TenantId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("DocumentId");
|
||||
|
||||
b.HasIndex("InvoiceId");
|
||||
|
||||
b.HasIndex("TenantId");
|
||||
|
||||
b.ToTable("InvoiceAttachments");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Marco.Pms.Model.Collection.InvoiceComment", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
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<Guid>("TenantId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("CreatedById");
|
||||
|
||||
b.HasIndex("InvoiceId");
|
||||
|
||||
b.HasIndex("TenantId");
|
||||
|
||||
b.ToTable("InvoiceComments");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Marco.Pms.Model.Collection.PaymentAdjustmentHead", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<bool>("IsActive")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<Guid>("TenantId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("TenantId");
|
||||
|
||||
b.ToTable("PaymentAdjustmentHeads");
|
||||
|
||||
b.HasData(
|
||||
new
|
||||
{
|
||||
Id = new Guid("dbdc047f-a2d2-4db0-b0e6-b9d9f923a0f1"),
|
||||
Description = "An advance payment is a sum paid before receiving goods or services, often to secure a transaction or cover initial costs.",
|
||||
IsActive = true,
|
||||
Name = "Advance payment",
|
||||
TenantId = new Guid("b3466e83-7e11-464c-b93a-daf047838b26")
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("66c3c241-8b52-4327-a5ad-c1faf102583e"),
|
||||
Description = "The base amount refers to the principal sum or original value used as a reference in financial calculations, excluding taxes, fees, or additional charges.",
|
||||
IsActive = true,
|
||||
Name = "Base Amount",
|
||||
TenantId = new Guid("b3466e83-7e11-464c-b93a-daf047838b26")
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("0d70cb2e-827e-44fc-90a5-c2c55ba51ba9"),
|
||||
Description = "TDS, or Tax Deducted at Source, is a system under the Indian Income Tax Act where tax is deducted at the point of income generation—such as salary, interest, or rent—and remitted to the government to prevent tax evasion and ensure timely collection.",
|
||||
IsActive = true,
|
||||
Name = "Tax Deducted at Source (TDS)",
|
||||
TenantId = new Guid("b3466e83-7e11-464c-b93a-daf047838b26")
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("95f35acd-d979-4177-91ea-fd03a00e49ff"),
|
||||
Description = "Retention refers to a company's ability to keep customers, employees, or profits over time, commonly measured as a percentage and critical for long-term business sustainability and growth.",
|
||||
IsActive = true,
|
||||
Name = "Retention",
|
||||
TenantId = new Guid("b3466e83-7e11-464c-b93a-daf047838b26")
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("3f09b19a-8d45-4cf2-be27-f4f09b38b9f7"),
|
||||
Description = "Tax is a mandatory financial charge imposed by a government on individuals or entities to fund public services and government operations, without direct benefit to the taxpayer.",
|
||||
IsActive = true,
|
||||
Name = "Tax",
|
||||
TenantId = new Guid("b3466e83-7e11-464c-b93a-daf047838b26")
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("ec5e6a5f-ce62-44e5-8911-8426bbb4dde8"),
|
||||
Description = "A penalty in the context of taxation is a financial sanction imposed by the government on individuals or entities for non-compliance with tax laws, such as late filing, underreporting income, or failure to pay taxes, and is typically calculated as a percentage of the tax due or a fixed amount.",
|
||||
IsActive = true,
|
||||
Name = "Penalty",
|
||||
TenantId = new Guid("b3466e83-7e11-464c-b93a-daf047838b26")
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("50584332-1cb7-4359-9721-c8ea35040881"),
|
||||
Description = "Utility fees are recurring charges for essential services such as electricity, water, gas, sewage, waste disposal, internet, and telecommunications, typically based on usage and necessary for operating a home or business.",
|
||||
IsActive = true,
|
||||
Name = "Utility fees",
|
||||
TenantId = new Guid("b3466e83-7e11-464c-b93a-daf047838b26")
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Marco.Pms.Model.Collection.ReceivedInvoicePayment", 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("ReceivedInvoicePayments");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Marco.Pms.Model.Directory.Bucket", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
@ -1688,6 +1955,46 @@ namespace Marco.Pms.DataAccess.Migrations
|
||||
Name = "Manage"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("dbf17591-09fe-4c93-9e1a-12db8f5cc5de"),
|
||||
Description = "Collection Admin is a permission that grants a user full administrative control over collections, including creating, editing, managing access, and deleting collections within a system.",
|
||||
FeatureId = new Guid("fc586e7d-ed1a-45e5-bb51-9f34af98ec13"),
|
||||
IsEnabled = true,
|
||||
Name = "Collection Admin"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("c8d7eea5-4033-4aad-9ebe-76de49896830"),
|
||||
Description = "View Collection is a permission that allows users to see and browse assets or items within a collection without making any modifications or edits to its contents.",
|
||||
FeatureId = new Guid("fc586e7d-ed1a-45e5-bb51-9f34af98ec13"),
|
||||
IsEnabled = true,
|
||||
Name = "View Collection"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("b93141fd-dbd3-4051-8f57-bf25d18e3555"),
|
||||
Description = "Authorizes users to create new collections for organizing related resources and managing access",
|
||||
FeatureId = new Guid("fc586e7d-ed1a-45e5-bb51-9f34af98ec13"),
|
||||
IsEnabled = true,
|
||||
Name = "Create Collection"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("455187b4-fef1-41f9-b3d0-025d0b6302c3"),
|
||||
Description = "Ability to modify collection properties, content, and access rights.",
|
||||
FeatureId = new Guid("fc586e7d-ed1a-45e5-bb51-9f34af98ec13"),
|
||||
IsEnabled = true,
|
||||
Name = "Edit Collection"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("061d9ccd-85b4-4cb0-be06-2f9f32cebb72"),
|
||||
Description = " Enables entry and processing of payment transactions.",
|
||||
FeatureId = new Guid("fc586e7d-ed1a-45e5-bb51-9f34af98ec13"),
|
||||
IsEnabled = true,
|
||||
Name = "Add Payment"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("068cb3c1-49c5-4746-9f29-1fce16e820ac"),
|
||||
Description = "Allow user to create new organization",
|
||||
@ -2829,6 +3136,14 @@ namespace Marco.Pms.DataAccess.Migrations
|
||||
Name = "Expense Management"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("fc586e7d-ed1a-45e5-bb51-9f34af98ec13"),
|
||||
Description = "Collection Management is a feature that enables organizations to track, organize, and manage the status and recovery of receivables or assets efficiently throughout their lifecycle, supporting systematic follow-up and resolution of outstanding accounts.",
|
||||
IsActive = true,
|
||||
ModuleId = new Guid("bf59fd88-b57a-4d67-bf01-3780f385896b"),
|
||||
Name = "Collection Management"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("9d4b5489-2079-40b9-bd77-6e1bf90bc19f"),
|
||||
Description = "Manage Tasks",
|
||||
@ -3809,6 +4124,45 @@ namespace Marco.Pms.DataAccess.Migrations
|
||||
b.ToTable("TenantOrgMappings");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Marco.Pms.Model.PaymentGetway.PaymentDetail", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<byte[]>("EncryptedDetails")
|
||||
.HasColumnType("longblob");
|
||||
|
||||
b.Property<string>("Method")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<byte[]>("Nonce")
|
||||
.HasColumnType("longblob");
|
||||
|
||||
b.Property<string>("OrderId")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("PaymentId")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("Status")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<byte[]>("Tag")
|
||||
.HasColumnType("longblob");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("PaymentDetails");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Marco.Pms.Model.Projects.Building", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
@ -4268,6 +4622,54 @@ namespace Marco.Pms.DataAccess.Migrations
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Marco.Pms.Model.TenantModels.TenantEnquire", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<string>("BillingAddress")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("ContactNumber")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("FirstName")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<Guid>("IndustryId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<string>("LastName")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("OrganizationName")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("OrganizationSize")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("Reference")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("IndustryId");
|
||||
|
||||
b.ToTable("TenantEnquires");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Marco.Pms.Model.TenantModels.TenantSubscriptions", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
@ -4304,6 +4706,9 @@ namespace Marco.Pms.DataAccess.Migrations
|
||||
b.Property<DateTime>("NextBillingDate")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<Guid?>("PaymentDetailId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<Guid>("PlanId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
@ -4328,6 +4733,8 @@ namespace Marco.Pms.DataAccess.Migrations
|
||||
|
||||
b.HasIndex("CurrencyId");
|
||||
|
||||
b.HasIndex("PaymentDetailId");
|
||||
|
||||
b.HasIndex("PlanId");
|
||||
|
||||
b.HasIndex("StatusId");
|
||||
@ -4794,6 +5201,139 @@ namespace Marco.Pms.DataAccess.Migrations
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Marco.Pms.Model.Collection.Invoice", b =>
|
||||
{
|
||||
b.HasOne("Marco.Pms.Model.Employees.Employee", "CreatedBy")
|
||||
.WithMany()
|
||||
.HasForeignKey("CreatedById")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Marco.Pms.Model.Projects.Project", "Project")
|
||||
.WithMany()
|
||||
.HasForeignKey("ProjectId")
|
||||
.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("Project");
|
||||
|
||||
b.Navigation("Tenant");
|
||||
|
||||
b.Navigation("UpdatedBy");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Marco.Pms.Model.Collection.InvoiceAttachment", b =>
|
||||
{
|
||||
b.HasOne("Marco.Pms.Model.DocumentManager.Document", "Document")
|
||||
.WithMany()
|
||||
.HasForeignKey("DocumentId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Marco.Pms.Model.Collection.Invoice", "Invoice")
|
||||
.WithMany()
|
||||
.HasForeignKey("InvoiceId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Marco.Pms.Model.TenantModels.Tenant", "Tenant")
|
||||
.WithMany()
|
||||
.HasForeignKey("TenantId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Document");
|
||||
|
||||
b.Navigation("Invoice");
|
||||
|
||||
b.Navigation("Tenant");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Marco.Pms.Model.Collection.InvoiceComment", b =>
|
||||
{
|
||||
b.HasOne("Marco.Pms.Model.Employees.Employee", "CreatedBy")
|
||||
.WithMany()
|
||||
.HasForeignKey("CreatedById")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Marco.Pms.Model.Collection.Invoice", "Invoice")
|
||||
.WithMany()
|
||||
.HasForeignKey("InvoiceId")
|
||||
.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("Tenant");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Marco.Pms.Model.Collection.PaymentAdjustmentHead", b =>
|
||||
{
|
||||
b.HasOne("Marco.Pms.Model.TenantModels.Tenant", "Tenant")
|
||||
.WithMany()
|
||||
.HasForeignKey("TenantId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Tenant");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Marco.Pms.Model.Collection.ReceivedInvoicePayment", b =>
|
||||
{
|
||||
b.HasOne("Marco.Pms.Model.Employees.Employee", "CreatedBy")
|
||||
.WithMany()
|
||||
.HasForeignKey("CreatedById")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Marco.Pms.Model.Collection.Invoice", "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.Directory.Bucket", b =>
|
||||
{
|
||||
b.HasOne("Marco.Pms.Model.Employees.Employee", "CreatedBy")
|
||||
@ -6097,6 +6637,17 @@ namespace Marco.Pms.DataAccess.Migrations
|
||||
b.Navigation("TenantStatus");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Marco.Pms.Model.TenantModels.TenantEnquire", b =>
|
||||
{
|
||||
b.HasOne("Marco.Pms.Model.Master.Industry", "Industry")
|
||||
.WithMany()
|
||||
.HasForeignKey("IndustryId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Industry");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Marco.Pms.Model.TenantModels.TenantSubscriptions", b =>
|
||||
{
|
||||
b.HasOne("Marco.Pms.Model.Employees.Employee", "CreatedBy")
|
||||
@ -6111,6 +6662,10 @@ namespace Marco.Pms.DataAccess.Migrations
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Marco.Pms.Model.PaymentGetway.PaymentDetail", "PaymentDetail")
|
||||
.WithMany()
|
||||
.HasForeignKey("PaymentDetailId");
|
||||
|
||||
b.HasOne("Marco.Pms.Model.TenantModels.SubscriptionPlanDetails", "Plan")
|
||||
.WithMany()
|
||||
.HasForeignKey("PlanId")
|
||||
@ -6137,6 +6692,8 @@ namespace Marco.Pms.DataAccess.Migrations
|
||||
|
||||
b.Navigation("Currency");
|
||||
|
||||
b.Navigation("PaymentDetail");
|
||||
|
||||
b.Navigation("Plan");
|
||||
|
||||
b.Navigation("Status");
|
||||
|
||||
@ -234,6 +234,27 @@ namespace Marco.Pms.Helpers.CacheHelper
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public async Task<bool> ClearAllEmployeesFromCacheByTenantId(Guid tenantId)
|
||||
{
|
||||
var tenantIdString = tenantId.ToString();
|
||||
|
||||
try
|
||||
{
|
||||
var filter = Builders<EmployeePermissionMongoDB>.Filter.Eq(e => e.TenantId, tenantIdString);
|
||||
|
||||
var result = await _collection.DeleteManyAsync(filter);
|
||||
|
||||
if (result.DeletedCount == 0)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error occured while deleting employee profile");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// A private method to handle the one-time setup of the collection's indexes.
|
||||
private async Task InitializeCollectionAsync()
|
||||
|
||||
7
Marco.Pms.Model/Dtos/PaymentGetway/CreateOrderDto.cs
Normal file
7
Marco.Pms.Model/Dtos/PaymentGetway/CreateOrderDto.cs
Normal file
@ -0,0 +1,7 @@
|
||||
namespace Marco.Pms.Model.Dtos.PaymentGetway
|
||||
{
|
||||
public class CreateOrderDto
|
||||
{
|
||||
public double Amount { get; set; }
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
namespace Marco.Pms.Model.Dtos.PaymentGetway
|
||||
{
|
||||
public class PaymentVerificationRequest
|
||||
{
|
||||
public required Guid TenantEnquireId { get; set; }
|
||||
public required Guid PlanId { get; set; }
|
||||
public required string OrderId { get; set; }
|
||||
public required string PaymentId { get; set; }
|
||||
public required string Signature { get; set; }
|
||||
}
|
||||
}
|
||||
9
Marco.Pms.Model/Dtos/Tenant/SelfSubscriptionDto.cs
Normal file
9
Marco.Pms.Model/Dtos/Tenant/SelfSubscriptionDto.cs
Normal file
@ -0,0 +1,9 @@
|
||||
namespace Marco.Pms.Model.Dtos.Tenant
|
||||
{
|
||||
public class SelfSubscriptionDto
|
||||
{
|
||||
public Guid TenantEnquireId { get; set; }
|
||||
public Guid PaymentDetailId { get; set; }
|
||||
public Guid PlanId { get; set; }
|
||||
}
|
||||
}
|
||||
15
Marco.Pms.Model/Dtos/Tenant/TenantEnquireDto.cs
Normal file
15
Marco.Pms.Model/Dtos/Tenant/TenantEnquireDto.cs
Normal file
@ -0,0 +1,15 @@
|
||||
namespace Marco.Pms.Model.Dtos.Tenant
|
||||
{
|
||||
public class TenantEnquireDto
|
||||
{
|
||||
public required string FirstName { get; set; }
|
||||
public required string LastName { get; set; }
|
||||
public required string OrganizationName { get; set; }
|
||||
public required string Email { get; set; }
|
||||
public required string ContactNumber { get; set; }
|
||||
public required string BillingAddress { get; set; }
|
||||
public required string OrganizationSize { get; set; }
|
||||
public required Guid IndustryId { get; set; }
|
||||
public required string Reference { get; set; }
|
||||
}
|
||||
}
|
||||
16
Marco.Pms.Model/PaymentGetway/PaymentDetail.cs
Normal file
16
Marco.Pms.Model/PaymentGetway/PaymentDetail.cs
Normal file
@ -0,0 +1,16 @@
|
||||
namespace Marco.Pms.Model.PaymentGetway
|
||||
{
|
||||
public class PaymentDetail
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public string PaymentId { get; set; } = string.Empty;
|
||||
public string OrderId { get; set; } = string.Empty;
|
||||
public string Status { get; set; } = string.Empty; // created, authorized, captured, refunded, failed
|
||||
public string Method { get; set; } = string.Empty;
|
||||
public byte[]? EncryptedDetails { get; set; }
|
||||
public byte[]? Nonce { get; set; }
|
||||
public byte[]? Tag { get; set; }
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
|
||||
}
|
||||
}
|
||||
24
Marco.Pms.Model/TenantModels/TenantEnquire.cs
Normal file
24
Marco.Pms.Model/TenantModels/TenantEnquire.cs
Normal file
@ -0,0 +1,24 @@
|
||||
using Marco.Pms.Model.Master;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace Marco.Pms.Model.TenantModels
|
||||
{
|
||||
public class TenantEnquire
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public string FirstName { get; set; } = default!;
|
||||
public string LastName { get; set; } = default!;
|
||||
public string OrganizationName { get; set; } = default!;
|
||||
public string Email { get; set; } = default!;
|
||||
public string ContactNumber { get; set; } = default!;
|
||||
public string BillingAddress { get; set; } = default!;
|
||||
public string OrganizationSize { get; set; } = default!;
|
||||
public Guid IndustryId { get; set; }
|
||||
|
||||
[ForeignKey("IndustryId")]
|
||||
[ValidateNever]
|
||||
public Industry? Industry { get; set; }
|
||||
public string Reference { get; set; } = default!;
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
using Marco.Pms.Model.Employees;
|
||||
using Marco.Pms.Model.Master;
|
||||
using Marco.Pms.Model.PaymentGetway;
|
||||
using Marco.Pms.Model.Utilities;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
@ -28,6 +29,11 @@ namespace Marco.Pms.Model.TenantModels
|
||||
[ForeignKey("CurrencyId")]
|
||||
[ValidateNever]
|
||||
public CurrencyMaster? Currency { get; set; }
|
||||
public Guid? PaymentDetailId { get; set; }
|
||||
|
||||
[ForeignKey("PaymentDetailId")]
|
||||
[ValidateNever]
|
||||
public PaymentDetail? PaymentDetail { get; set; }
|
||||
public DateTime NextBillingDate { get; set; }
|
||||
public DateTime? CancellationDate { get; set; }
|
||||
public bool AutoRenew { get; set; } = true;
|
||||
|
||||
8
Marco.Pms.Model/ViewModels/PaymentGetway/BankDetails.cs
Normal file
8
Marco.Pms.Model/ViewModels/PaymentGetway/BankDetails.cs
Normal file
@ -0,0 +1,8 @@
|
||||
namespace Marco.Pms.Model.ViewModels.PaymentGetway
|
||||
{
|
||||
public class BankDetails
|
||||
{
|
||||
public string? Bank { get; set; }
|
||||
public string? BankCode { get; set; }
|
||||
}
|
||||
}
|
||||
14
Marco.Pms.Model/ViewModels/PaymentGetway/CardDetails.cs
Normal file
14
Marco.Pms.Model/ViewModels/PaymentGetway/CardDetails.cs
Normal file
@ -0,0 +1,14 @@
|
||||
namespace Marco.Pms.Model.ViewModels.PaymentGetway
|
||||
{
|
||||
public class CardDetails
|
||||
{
|
||||
public string? CardId { get; set; }
|
||||
public string? Last4Digits { get; set; }
|
||||
public string? Network { get; set; } // Visa, MasterCard, Amex, RuPay
|
||||
public string? CardType { get; set; } // credit, debit, prepaid
|
||||
public string? Issuer { get; set; } // Bank name
|
||||
public bool International { get; set; }
|
||||
public bool Emi { get; set; }
|
||||
public string? SubType { get; set; } // consumer, business
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
namespace Marco.Pms.Model.ViewModels.PaymentGetway
|
||||
{
|
||||
public class CreateOrderVM
|
||||
{
|
||||
public string? OrderId { get; set; }
|
||||
public string? Key { get; set; }
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
namespace Marco.Pms.Model.ViewModels.PaymentGetway
|
||||
{
|
||||
public class PaymentDetailsVM
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public RazorpayPaymentDetails? RazorpayPaymentDetails { get; set; }
|
||||
public RazorpayOrderDetails? RazorpayOrderDetails { get; set; }
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,15 @@
|
||||
namespace Marco.Pms.Model.ViewModels.PaymentGetway
|
||||
{
|
||||
public class RazorpayOrderDetails
|
||||
{
|
||||
public string? OrderId { get; set; }
|
||||
public decimal Amount { get; set; }
|
||||
public string? Currency { get; set; }
|
||||
public string? Status { get; set; }
|
||||
public string? Receipt { get; set; }
|
||||
public DateTime CreatedAt { get; set; }
|
||||
public decimal AmountPaid { get; set; }
|
||||
public decimal AmountDue { get; set; }
|
||||
public int Attempts { get; set; }
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,35 @@
|
||||
namespace Marco.Pms.Model.ViewModels.PaymentGetway
|
||||
{
|
||||
public class RazorpayPaymentDetails
|
||||
{
|
||||
public string? PaymentId { get; set; }
|
||||
public string? OrderId { get; set; }
|
||||
public decimal Amount { get; set; }
|
||||
public string? Currency { get; set; }
|
||||
public string? Status { get; set; } // created, authorized, captured, refunded, failed
|
||||
public string? Method { get; set; } // card, netbanking, wallet, upi
|
||||
public string? Email { get; set; }
|
||||
public string? Contact { get; set; }
|
||||
public string? Description { get; set; }
|
||||
public string? CustomerName { get; set; }
|
||||
public DateTime CreatedAt { get; set; }
|
||||
|
||||
// Payment method specific details
|
||||
public CardDetails? CardDetails { get; set; }
|
||||
public BankDetails? BankDetails { get; set; }
|
||||
public UpiDetails? UpiDetails { get; set; }
|
||||
public WalletDetails? WalletDetails { get; set; }
|
||||
|
||||
// Fee and tax
|
||||
public decimal Fee { get; set; }
|
||||
public decimal Tax { get; set; }
|
||||
|
||||
// Error details (if payment failed)
|
||||
public string? ErrorCode { get; set; }
|
||||
public string? ErrorDescription { get; set; }
|
||||
|
||||
// Additional flags
|
||||
public bool InternationalPayment { get; set; }
|
||||
public bool Captured { get; set; }
|
||||
}
|
||||
}
|
||||
8
Marco.Pms.Model/ViewModels/PaymentGetway/UpiDetails.cs
Normal file
8
Marco.Pms.Model/ViewModels/PaymentGetway/UpiDetails.cs
Normal file
@ -0,0 +1,8 @@
|
||||
namespace Marco.Pms.Model.ViewModels.PaymentGetway
|
||||
{
|
||||
public class UpiDetails
|
||||
{
|
||||
public string? Vpa { get; set; } // UPI ID
|
||||
public string? Provider { get; set; }
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
namespace Marco.Pms.Model.ViewModels.PaymentGetway
|
||||
{
|
||||
public class WalletDetails
|
||||
{
|
||||
public string? WalletName { get; set; } // paytm, phonepe, amazonpay, freecharge, jiomoney, olamoney
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,7 @@
|
||||
using Marco.Pms.Model.Master;
|
||||
using Marco.Pms.Model.TenantModels;
|
||||
using Marco.Pms.Model.ViewModels.Activities;
|
||||
using Marco.Pms.Model.ViewModels.PaymentGetway;
|
||||
|
||||
namespace Marco.Pms.Model.ViewModels.Tenant
|
||||
{
|
||||
@ -16,6 +17,7 @@ namespace Marco.Pms.Model.ViewModels.Tenant
|
||||
public DateTime EndDate { get; set; }
|
||||
public DateTime CreatedAt { get; set; }
|
||||
public DateTime? UpdatedAt { get; set; }
|
||||
public PaymentDetailsVM? PaymentDetail { get; set; }
|
||||
public BasicEmployeeVM? CreatedBy { get; set; }
|
||||
public BasicEmployeeVM? updatedBy { get; set; }
|
||||
public CurrencyMaster? Currency { get; set; }
|
||||
|
||||
@ -621,19 +621,19 @@ namespace Marco.Pms.Services.Controllers
|
||||
var featureIds = await generalHelper.GetFeatureIdsByTenentIdAsync(tenantId);
|
||||
_logger.LogInfo("Enabled features for TenantId: {TenantId} -> {FeatureIds}", tenantId, string.Join(",", featureIds));
|
||||
|
||||
if (!(featureIds?.Any() ?? false))
|
||||
{
|
||||
featureIds = new List<Guid>
|
||||
{
|
||||
new Guid("a4e25142-449b-4334-a6e5-22f70e4732d7"), // Expense Management feature
|
||||
new Guid("81ab8a87-8ccd-4015-a917-0627cee6a100"), // Employee Management feature
|
||||
new Guid("52c9cf54-1eb2-44d2-81bb-524cf29c0a94"), // Attendance Management feature
|
||||
new Guid("a8cf4331-8f04-4961-8360-a3f7c3cc7462"), // Document Management feature
|
||||
new Guid("be3b3afc-6ccf-4566-b9b6-aafcb65546be"), // Masters Management feature
|
||||
new Guid("39e66f81-efc6-446c-95bd-46bff6cfb606"), // Directory Management feature
|
||||
new Guid("6d4c82d6-dbce-48ab-b8b8-f785f4d8c914") // Organization Management feature
|
||||
};
|
||||
}
|
||||
//if (!(featureIds?.Any() ?? false))
|
||||
//{
|
||||
// featureIds = new List<Guid>
|
||||
// {
|
||||
// new Guid("a4e25142-449b-4334-a6e5-22f70e4732d7"), // Expense Management feature
|
||||
// new Guid("81ab8a87-8ccd-4015-a917-0627cee6a100"), // Employee Management feature
|
||||
// new Guid("52c9cf54-1eb2-44d2-81bb-524cf29c0a94"), // Attendance Management feature
|
||||
// new Guid("a8cf4331-8f04-4961-8360-a3f7c3cc7462"), // Document Management feature
|
||||
// new Guid("be3b3afc-6ccf-4566-b9b6-aafcb65546be"), // Masters Management feature
|
||||
// new Guid("39e66f81-efc6-446c-95bd-46bff6cfb606"), // Directory Management feature
|
||||
// new Guid("6d4c82d6-dbce-48ab-b8b8-f785f4d8c914") // Organization Management feature
|
||||
// };
|
||||
//}
|
||||
|
||||
// Aggregate menus based on enabled features
|
||||
var response = featureIds
|
||||
|
||||
108
Marco.Pms.Services/Controllers/PaymentController.cs
Normal file
108
Marco.Pms.Services/Controllers/PaymentController.cs
Normal file
@ -0,0 +1,108 @@
|
||||
using Marco.Pms.Model.Dtos.PaymentGetway;
|
||||
using Marco.Pms.Model.Utilities;
|
||||
using Marco.Pms.Services.Service.ServiceInterfaces;
|
||||
using MarcoBMS.Services.Helpers;
|
||||
using MarcoBMS.Services.Service;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Marco.Pms.Services.Controllers
|
||||
{
|
||||
[Route("api/[controller]")]
|
||||
[ApiController]
|
||||
public class PaymentController : ControllerBase
|
||||
{
|
||||
private readonly UserHelper _userHelper;
|
||||
private readonly ILoggingService _logger;
|
||||
private readonly IRazorpayService _razorpayService;
|
||||
private readonly Guid tenantId;
|
||||
private readonly Guid organizaionId;
|
||||
public PaymentController(UserHelper userHelper, ILoggingService logger, IRazorpayService razorpayService)
|
||||
{
|
||||
_userHelper = userHelper;
|
||||
_logger = logger;
|
||||
_razorpayService = razorpayService;
|
||||
tenantId = userHelper.GetTenantId();
|
||||
organizaionId = userHelper.GetCurrentOrganizationId();
|
||||
}
|
||||
|
||||
[HttpPost("create-order")]
|
||||
public async Task<IActionResult> CreateOrder([FromBody] CreateOrderDto model)
|
||||
{
|
||||
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
|
||||
try
|
||||
{
|
||||
var response = _razorpayService.CreateOrder(model.Amount, loggedInEmployee, tenantId);
|
||||
return Ok(ApiResponse<object>.SuccessResponse(response, "Payment created successfully", 200));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, ApiResponse<object>.ErrorResponse("Error occured While creating the payment", new
|
||||
{
|
||||
Message = ex.Message,
|
||||
StackTrace = ex.StackTrace,
|
||||
Source = ex.Source,
|
||||
InnerException = new
|
||||
{
|
||||
Message = ex.InnerException?.Message,
|
||||
StackTrace = ex.InnerException?.StackTrace,
|
||||
Source = ex.InnerException?.Source,
|
||||
}
|
||||
}, 500));
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("verify-payment")]
|
||||
public async Task<IActionResult> VerifyPayment([FromBody] PaymentVerificationRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogInfo("Payment verification started for OrderId: {OrderId}, PaymentId: {PaymentId}",
|
||||
request.OrderId ?? "", request.PaymentId ?? "");
|
||||
|
||||
// Validate request
|
||||
if (string.IsNullOrEmpty(request.OrderId) || string.IsNullOrEmpty(request.PaymentId) || string.IsNullOrEmpty(request.Signature))
|
||||
{
|
||||
_logger.LogWarning("Payment verification failed - Missing required parameters");
|
||||
return BadRequest(ApiResponse<object>.ErrorResponse("Missing required parameters", 400));
|
||||
}
|
||||
|
||||
// Verify signature
|
||||
string payload = request.OrderId + "|" + request.PaymentId;
|
||||
string actualSignature = request.Signature;
|
||||
string expectedSignature = _razorpayService.GetExpectedSignature(payload);
|
||||
|
||||
if (actualSignature == expectedSignature)
|
||||
{
|
||||
_logger.LogInfo("Payment signature verified successfully for OrderId: {OrderId}", request.OrderId);
|
||||
|
||||
// Fetch complete payment details from Razorpay including card details
|
||||
var response = await _razorpayService.GetPaymentDetails(request.PaymentId);
|
||||
|
||||
_logger.LogInfo("Invoice generated and saved for OrderId: {OrderId}", request.OrderId);
|
||||
|
||||
return Ok(ApiResponse<object>.SuccessResponse(response, "Payment verified successfully", 200));
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogWarning("Payment signature verification failed for OrderId: {OrderId}", request.OrderId);
|
||||
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid signature - Payment verification failed", "Invalid signature - Payment verification failed", 400));
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error during payment verification for OrderId: {OrderId}", request.OrderId ?? "");
|
||||
return StatusCode(500, ApiResponse<object>.ErrorResponse("An error occurred during payment verification", "An error occurred during payment verification", 500));
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("get/payment-details/{id}")]
|
||||
public async Task<IActionResult> GetPaymentDetails(Guid id)
|
||||
{
|
||||
var paymentsDetails = await _razorpayService.GetPaymentDetailsFromDataBase(id);
|
||||
return Ok(ApiResponse<object>.SuccessResponse(paymentsDetails, "Payment fetched Successfully", 200));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -16,6 +16,7 @@ using Marco.Pms.Model.ViewModels.Activities;
|
||||
using Marco.Pms.Model.ViewModels.Tenant;
|
||||
using Marco.Pms.Services.Helpers;
|
||||
using Marco.Pms.Services.Service;
|
||||
using Marco.Pms.Services.Service.ServiceInterfaces;
|
||||
using MarcoBMS.Services.Helpers;
|
||||
using MarcoBMS.Services.Service;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
@ -230,6 +231,8 @@ namespace Marco.Pms.Services.Controllers
|
||||
{
|
||||
_logger.LogInfo("GetTenantDetails started for TenantId: {TenantId}", id);
|
||||
|
||||
using var scope = _serviceScopeFactory.CreateScope();
|
||||
|
||||
// Get currently logged-in employee info
|
||||
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
|
||||
if (loggedInEmployee == null)
|
||||
@ -240,32 +243,27 @@ namespace Marco.Pms.Services.Controllers
|
||||
|
||||
// Check permissions using a single service scope to avoid overhead
|
||||
bool hasManagePermission, hasModifyPermission, hasViewPermission;
|
||||
using (var scope = _serviceScopeFactory.CreateScope())
|
||||
var manageTask = Task.Run(async () =>
|
||||
{
|
||||
var permissionService = scope.ServiceProvider.GetRequiredService<PermissionServices>();
|
||||
return await permissionService.HasPermission(PermissionsMaster.ManageTenants, loggedInEmployee.Id);
|
||||
});
|
||||
var modifyTask = Task.Run(async () =>
|
||||
{
|
||||
var permissionService = scope.ServiceProvider.GetRequiredService<PermissionServices>();
|
||||
return await permissionService.HasPermission(PermissionsMaster.ModifyTenant, loggedInEmployee.Id);
|
||||
});
|
||||
var viewTask = Task.Run(async () =>
|
||||
{
|
||||
var permissionService = scope.ServiceProvider.GetRequiredService<PermissionServices>();
|
||||
return await permissionService.HasPermission(PermissionsMaster.ViewTenant, loggedInEmployee.Id);
|
||||
});
|
||||
|
||||
await Task.WhenAll(manageTask, modifyTask, viewTask);
|
||||
|
||||
var manageTask = Task.Run(async () =>
|
||||
{
|
||||
var permissionService = scope.ServiceProvider.GetRequiredService<PermissionServices>();
|
||||
return await permissionService.HasPermission(PermissionsMaster.ManageTenants, loggedInEmployee.Id);
|
||||
});
|
||||
var modifyTask = Task.Run(async () =>
|
||||
{
|
||||
var permissionService = scope.ServiceProvider.GetRequiredService<PermissionServices>();
|
||||
return await permissionService.HasPermission(PermissionsMaster.ModifyTenant, loggedInEmployee.Id);
|
||||
});
|
||||
var viewTask = Task.Run(async () =>
|
||||
{
|
||||
var permissionService = scope.ServiceProvider.GetRequiredService<PermissionServices>();
|
||||
return await permissionService.HasPermission(PermissionsMaster.ViewTenant, loggedInEmployee.Id);
|
||||
});
|
||||
|
||||
await Task.WhenAll(manageTask, modifyTask, viewTask);
|
||||
|
||||
hasManagePermission = manageTask.Result;
|
||||
hasModifyPermission = modifyTask.Result;
|
||||
hasViewPermission = viewTask.Result;
|
||||
}
|
||||
hasManagePermission = manageTask.Result;
|
||||
hasModifyPermission = modifyTask.Result;
|
||||
hasViewPermission = viewTask.Result;
|
||||
|
||||
if (!hasManagePermission && !hasModifyPermission && !hasViewPermission)
|
||||
{
|
||||
@ -353,6 +351,11 @@ namespace Marco.Pms.Services.Controllers
|
||||
var plans = plansTask.Result;
|
||||
var projects = projectsTask.Result;
|
||||
|
||||
var _razorpayService = scope.ServiceProvider.GetRequiredService<IRazorpayService>();
|
||||
|
||||
var paymentDetailIds = plans.Where(ts => ts.PaymentDetailId.HasValue).Select(ts => ts.PaymentDetailId!.Value).ToList();
|
||||
var paymentsDetails = await _razorpayService.GetPaymentDetailsListFromDataBase(paymentDetailIds);
|
||||
|
||||
// Calculate active/inactive employees count
|
||||
var activeEmployeesCount = employees.Count(e => e.IsActive);
|
||||
var inActiveEmployeesCount = employees.Count - activeEmployeesCount;
|
||||
@ -381,9 +384,16 @@ 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);
|
||||
|
||||
response.CurrentPlanFeatures = await _featureDetailsHelper.GetFeatureDetails(currentPlan?.Plan?.FeaturesId ?? Guid.Empty);
|
||||
// Map subscription history plans to DTO
|
||||
response.SubscriptionHistery = _mapper.Map<List<SubscriptionPlanDetailsVM>>(plans);
|
||||
response.SubscriptionHistery = plans.Select(ts =>
|
||||
{
|
||||
var result = _mapper.Map<SubscriptionPlanDetailsVM>(ts);
|
||||
result.PaymentDetail = paymentsDetails.FirstOrDefault(pd => ts != null && pd.Id == ts.PaymentDetailId);
|
||||
return result;
|
||||
}).ToList();
|
||||
|
||||
_logger.LogInfo("Tenant details fetched successfully for TenantId: {TenantId}", tenant.Id);
|
||||
|
||||
@ -924,6 +934,58 @@ namespace Marco.Pms.Services.Controllers
|
||||
return Ok(ApiResponse<object>.SuccessResponse(responseData, successMessage, 200));
|
||||
}
|
||||
|
||||
[AllowAnonymous]
|
||||
[HttpPost("self/create")]
|
||||
public async Task<IActionResult> SelfRegistrationTenant([FromBody] TenantEnquireDto model)
|
||||
{
|
||||
// Log the start of the registration attempt
|
||||
_logger.LogInfo("Self-registration request received at {Timestamp}.", DateTime.UtcNow);
|
||||
try
|
||||
{
|
||||
// Create db context asynchronously for optimized resource use
|
||||
await using var context = await _dbContextFactory.CreateDbContextAsync();
|
||||
|
||||
// 2. --- VALIDATION ---
|
||||
// Check if a user with the same email already exists.
|
||||
var existingUser = await _userManager.FindByEmailAsync(model.Email);
|
||||
if (existingUser != null)
|
||||
{
|
||||
_logger.LogWarning("Tenant creation failed for email {Email}: an application user with this email already exists.", model.Email);
|
||||
return StatusCode(409, ApiResponse<object>.ErrorResponse("Tenant cannot be created", "A user with the specified email already exists.", 409));
|
||||
}
|
||||
|
||||
var industry = await context.Industries.FirstOrDefaultAsync(i => i.Id == model.IndustryId);
|
||||
if (industry == null)
|
||||
{
|
||||
_logger.LogWarning("Industry not found while creating the tenant enquire");
|
||||
return NotFound(ApiResponse<object>.ErrorResponse("Industry not found", "Industry not found", 404));
|
||||
}
|
||||
|
||||
// Map DTO to domain model and assign new Guid
|
||||
var tenantEnquire = _mapper.Map<TenantEnquire>(model);
|
||||
tenantEnquire.Id = Guid.NewGuid();
|
||||
|
||||
// Add new tenant enquiry to the database
|
||||
await context.TenantEnquires.AddAsync(tenantEnquire);
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
// Log successful registration
|
||||
_logger.LogInfo("Tenant enquiry created successfully. ID: {TenantEnquireId}", tenantEnquire.Id);
|
||||
|
||||
// Return success response with proper status code and user information
|
||||
return Ok(ApiResponse<object>.SuccessResponse(tenantEnquire, "Tenant enquiry added successfully.", 201));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Log error with detailed exception information at Error level
|
||||
_logger.LogError(ex, "Error occurred during self-registration: {Message}", ex.Message);
|
||||
|
||||
// Return standardized error response to the client
|
||||
var errorResponse = ApiResponse<object>.ErrorResponse("Failed to add tenant enquiry, please try again later.", "Failed to add tenant enquiry, please try again later.", 500);
|
||||
|
||||
return StatusCode(500, errorResponse);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@ -1138,6 +1200,9 @@ namespace Marco.Pms.Services.Controllers
|
||||
_logger.LogInfo("Removed {Count} role permission mappings for role {RoleId}", deleteMappings.Count, roleId);
|
||||
}
|
||||
|
||||
var _cache = scope.ServiceProvider.GetRequiredService<CacheUpdateHelper>();
|
||||
await _cache.ClearAllEmployeesFromCacheByTenantId(tenant.Id);
|
||||
|
||||
var _masteData = scope.ServiceProvider.GetRequiredService<MasterDataService>();
|
||||
|
||||
if (features.Modules?.ProjectManagement?.Enabled ?? false)
|
||||
@ -1450,6 +1515,9 @@ namespace Marco.Pms.Services.Controllers
|
||||
_logger.LogInfo("Permissions revoked: {Count} for Role={RoleId}", mappingsToRemove.Count, rootRoleId);
|
||||
}
|
||||
|
||||
var _cache = scope.ServiceProvider.GetRequiredService<CacheUpdateHelper>();
|
||||
await _cache.ClearAllEmployeesFromCacheByTenantId(tenant.Id);
|
||||
|
||||
var _masteData = scope.ServiceProvider.GetRequiredService<MasterDataService>();
|
||||
|
||||
if (features.Modules?.ProjectManagement?.Enabled ?? false)
|
||||
@ -1534,6 +1602,24 @@ namespace Marco.Pms.Services.Controllers
|
||||
}
|
||||
}
|
||||
|
||||
[AllowAnonymous]
|
||||
[HttpPost("self/subscription")]
|
||||
public async Task<IActionResult> SelfSubscriptionAsync(SelfSubscriptionDto model)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var scope = _serviceScopeFactory.CreateScope();
|
||||
var _tenantService = scope.ServiceProvider.GetRequiredService<ITenantService>();
|
||||
var tenant = await _tenantService.CreateTenantAsync(model.TenantEnquireId, model.PaymentDetailId, model.PlanId);
|
||||
return Ok(ApiResponse<object>.SuccessResponse(tenant, "Tenant Registration Successfully", 201));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error occured while add self subscription");
|
||||
return StatusCode(500, ApiResponse<object>.ErrorResponse("Error Occured while self subscription", "Error Occured while self subscription", 500));
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region =================================================================== Subscription Plan APIs ===================================================================
|
||||
|
||||
@ -956,6 +956,18 @@ namespace Marco.Pms.Services.Helpers
|
||||
_logger.LogError(ex, "Error occured while deleting all employees from Cache");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ClearAllEmployeesFromCacheByTenantId(Guid tenantId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var response = await _employeeCache.ClearAllEmployeesFromCacheByTenantId(tenantId);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error occured while deleting all employees from Cache");
|
||||
}
|
||||
}
|
||||
public async Task ClearAllEmployees()
|
||||
{
|
||||
try
|
||||
|
||||
@ -253,17 +253,19 @@ namespace Marco.Pms.Services.Helpers
|
||||
!ts.IsCancelled &&
|
||||
ts.EndDate.Date >= DateTime.UtcNow.Date); // FIX: Subscription should not be expired
|
||||
|
||||
var featureIds = new List<Guid>
|
||||
{
|
||||
new Guid("a4e25142-449b-4334-a6e5-22f70e4732d7"), // Expense Management feature
|
||||
new Guid("81ab8a87-8ccd-4015-a917-0627cee6a100"), // Employee Management feature
|
||||
new Guid("52c9cf54-1eb2-44d2-81bb-524cf29c0a94"), // Attendance Management feature
|
||||
new Guid("a8cf4331-8f04-4961-8360-a3f7c3cc7462"), // Document Management feature
|
||||
new Guid("be3b3afc-6ccf-4566-b9b6-aafcb65546be"), // Masters Management feature
|
||||
new Guid("39e66f81-efc6-446c-95bd-46bff6cfb606"), // Directory Management feature
|
||||
new Guid("6d4c82d6-dbce-48ab-b8b8-f785f4d8c914") // Organization Management feature
|
||||
//new Guid("2f3509b7-160d-410a-b9b6-daadd96c986d") // Tenant Management feature
|
||||
};
|
||||
//var featureIds = new List<Guid>
|
||||
//{
|
||||
// new Guid("a4e25142-449b-4334-a6e5-22f70e4732d7"), // Expense Management feature
|
||||
// new Guid("81ab8a87-8ccd-4015-a917-0627cee6a100"), // Employee Management feature
|
||||
// new Guid("52c9cf54-1eb2-44d2-81bb-524cf29c0a94"), // Attendance Management feature
|
||||
// new Guid("a8cf4331-8f04-4961-8360-a3f7c3cc7462"), // Document Management feature
|
||||
// new Guid("be3b3afc-6ccf-4566-b9b6-aafcb65546be"), // Masters Management feature
|
||||
// new Guid("39e66f81-efc6-446c-95bd-46bff6cfb606"), // Directory Management feature
|
||||
// new Guid("6d4c82d6-dbce-48ab-b8b8-f785f4d8c914") // Organization Management feature
|
||||
// //new Guid("2f3509b7-160d-410a-b9b6-daadd96c986d") // Tenant Management feature
|
||||
//};
|
||||
|
||||
var featureIds = new List<Guid> { new Guid("be3b3afc-6ccf-4566-b9b6-aafcb65546be") };
|
||||
|
||||
if (tenantSubscription == null)
|
||||
{
|
||||
@ -274,9 +276,6 @@ namespace Marco.Pms.Services.Helpers
|
||||
_logger.LogDebug("Active subscription found for tenant: {TenantId}, PlanId: {PlanId}",
|
||||
tenantId, tenantSubscription.Plan!.Id);
|
||||
|
||||
//var featureIds = new List<Guid> { new Guid("be3b3afc-6ccf-4566-b9b6-aafcb65546be") };
|
||||
|
||||
|
||||
// Step 2: Get feature details from Plan
|
||||
var featureDetails = await _featureDetailsHelper.GetFeatureDetails(tenantSubscription.Plan!.FeaturesId);
|
||||
|
||||
|
||||
@ -61,6 +61,7 @@ namespace Marco.Pms.Services.MappingProfiles
|
||||
#endregion
|
||||
|
||||
#region ======================================================= Tenant =======================================================
|
||||
CreateMap<TenantEnquireDto, TenantEnquire>();
|
||||
CreateMap<Tenant, TenantVM>();
|
||||
CreateMap<Tenant, TenantListVM>();
|
||||
CreateMap<Tenant, TenantDetailsVM>();
|
||||
|
||||
@ -34,6 +34,7 @@
|
||||
<PackageReference Include="Mime-Detective" Version="24.12.2" />
|
||||
<PackageReference Include="Mime-Detective.Definitions.Exhaustive" Version="24.12.2" />
|
||||
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.2" />
|
||||
<PackageReference Include="Razorpay" Version="3.3.2" />
|
||||
<PackageReference Include="Serilog.AspNetCore" Version="9.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Async" Version="2.1.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
using Marco.Pms.CacheHelper;
|
||||
using FirebaseAdmin;
|
||||
using Google.Apis.Auth.OAuth2;
|
||||
using Marco.Pms.CacheHelper;
|
||||
using Marco.Pms.DataAccess.Data;
|
||||
using Marco.Pms.Helpers;
|
||||
using Marco.Pms.Helpers.CacheHelper;
|
||||
@ -55,7 +55,7 @@ builder.Services.AddCors(options =>
|
||||
policy.AllowAnyOrigin()
|
||||
.AllowAnyMethod()
|
||||
.AllowAnyHeader()
|
||||
.WithExposedHeaders("Authorization");
|
||||
.WithExposedHeaders("Authorization", "X-Request-ID", "X-Correlation-ID");
|
||||
});
|
||||
|
||||
// A stricter policy for production (loaded from config)
|
||||
@ -65,7 +65,8 @@ builder.Services.AddCors(options =>
|
||||
{
|
||||
policy.WithOrigins(allowedOrigins)
|
||||
.AllowAnyMethod()
|
||||
.AllowAnyHeader();
|
||||
.AllowAnyHeader()
|
||||
.WithExposedHeaders("Authorization", "X-Request-ID", "X-Correlation-ID");
|
||||
});
|
||||
});
|
||||
#endregion
|
||||
@ -182,6 +183,9 @@ builder.Services.AddScoped<IExpensesService, ExpensesService>();
|
||||
builder.Services.AddScoped<IMasterService, MasterService>();
|
||||
builder.Services.AddScoped<IDirectoryService, DirectoryService>();
|
||||
builder.Services.AddScoped<IFirebaseService, FirebaseService>();
|
||||
builder.Services.AddScoped<IRazorpayService, RazorpayService>();
|
||||
builder.Services.AddScoped<IAesEncryption, AesEncryption>();
|
||||
builder.Services.AddScoped<ITenantService, TenantService>();
|
||||
#endregion
|
||||
|
||||
#region Helpers
|
||||
|
||||
36
Marco.Pms.Services/Service/AesEncryption.cs
Normal file
36
Marco.Pms.Services/Service/AesEncryption.cs
Normal file
@ -0,0 +1,36 @@
|
||||
using Marco.Pms.Services.Service.ServiceInterfaces;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace Marco.Pms.Services.Service
|
||||
{
|
||||
public class AesEncryption : IAesEncryption
|
||||
{
|
||||
public (byte[] ciphertext, byte[] nonce, byte[] tag) Encrypt(string plaintext, byte[] key)
|
||||
{
|
||||
byte[] autoKey = new byte[32]; // 32 bytes = 256 bits
|
||||
RandomNumberGenerator.Fill(autoKey);
|
||||
var stringKey = Convert.ToBase64String(autoKey);
|
||||
|
||||
byte[] nonce = RandomNumberGenerator.GetBytes(12);
|
||||
byte[] plaintextBytes = Encoding.UTF8.GetBytes(plaintext);
|
||||
byte[] ciphertext = new byte[plaintextBytes.Length];
|
||||
byte[] tag = new byte[16];
|
||||
|
||||
using var aes = new AesGcm(key, 16);
|
||||
aes.Encrypt(nonce, plaintextBytes, ciphertext, tag);
|
||||
|
||||
return (ciphertext, nonce, tag);
|
||||
}
|
||||
|
||||
public string Decrypt(byte[] ciphertext, byte[] nonce, byte[] tag, byte[] key)
|
||||
{
|
||||
byte[] plaintext = new byte[ciphertext.Length];
|
||||
|
||||
using var aes = new AesGcm(key, 16);
|
||||
aes.Decrypt(nonce, ciphertext, tag, plaintext);
|
||||
|
||||
return Encoding.UTF8.GetString(plaintext);
|
||||
}
|
||||
}
|
||||
}
|
||||
418
Marco.Pms.Services/Service/RazorpayService.cs
Normal file
418
Marco.Pms.Services/Service/RazorpayService.cs
Normal file
@ -0,0 +1,418 @@
|
||||
using Marco.Pms.DataAccess.Data;
|
||||
using Marco.Pms.Model.Employees;
|
||||
using Marco.Pms.Model.PaymentGetway;
|
||||
using Marco.Pms.Model.ViewModels.PaymentGetway;
|
||||
using Marco.Pms.Services.Service.ServiceInterfaces;
|
||||
using MarcoBMS.Services.Service;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Newtonsoft.Json;
|
||||
using Razorpay.Api;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace Marco.Pms.Services.Service
|
||||
{
|
||||
public class RazorpayService : IRazorpayService
|
||||
{
|
||||
private readonly ApplicationDbContext _context;
|
||||
private readonly RazorpayClient _razorpayClient;
|
||||
private readonly ILoggingService _logger;
|
||||
private readonly IConfiguration _configuration;
|
||||
private readonly IAesEncryption _aesEncryption;
|
||||
private readonly string key;
|
||||
private readonly string secret;
|
||||
private readonly byte[] encryptionKey;
|
||||
|
||||
public RazorpayService(ApplicationDbContext context, IConfiguration configuration, ILoggingService logger, IAesEncryption aesEncryption)
|
||||
{
|
||||
_context = context;
|
||||
_logger = logger;
|
||||
_configuration = configuration;
|
||||
_aesEncryption = aesEncryption;
|
||||
|
||||
key = configuration["Razorpay:Key"] ?? "";
|
||||
secret = configuration["Razorpay:Secret"] ?? "";
|
||||
_razorpayClient = new RazorpayClient(key, secret);
|
||||
|
||||
string stringKey = configuration["Encryption:PaymentKey"] ?? "";
|
||||
encryptionKey = Convert.FromBase64String(stringKey);
|
||||
}
|
||||
|
||||
public CreateOrderVM CreateOrder(double amount, Employee loggedInEmployee, Guid tenantId)
|
||||
{
|
||||
RazorpayClient client = new RazorpayClient(key, secret);
|
||||
|
||||
var receipt = $"rec_{Guid.NewGuid()}";
|
||||
var length = receipt.Length;
|
||||
|
||||
Dictionary<string, object> options = new Dictionary<string, object>
|
||||
{
|
||||
{ "amount", amount * 100 }, // amount in paise
|
||||
{ "currency", "INR" },
|
||||
{ "receipt", receipt},
|
||||
{ "payment_capture", 1 }
|
||||
};
|
||||
|
||||
Order order = client.Order.Create(options);
|
||||
var response = new CreateOrderVM
|
||||
{
|
||||
OrderId = order["id"],
|
||||
Key = key
|
||||
};
|
||||
return response;
|
||||
}
|
||||
|
||||
public string GetExpectedSignature(string payload)
|
||||
{
|
||||
string expectedSignature;
|
||||
using (var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret)))
|
||||
{
|
||||
var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(payload));
|
||||
expectedSignature = Convert.ToHexString(hash).ToLower();
|
||||
}
|
||||
|
||||
return expectedSignature;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fetch complete payment details from Razorpay including card details
|
||||
/// </summary>
|
||||
/// <param name="paymentId">Razorpay Payment ID</param>
|
||||
/// <returns>Complete payment details with card information</returns>
|
||||
public async Task<PaymentDetailsVM> GetPaymentDetails(string paymentId)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogInfo("Fetching payment details from Razorpay for PaymentId: {PaymentId}", paymentId);
|
||||
|
||||
// Fetch payment details from Razorpay
|
||||
Payment payment = _razorpayClient.Payment.Fetch(paymentId);
|
||||
|
||||
// Extract customer name from notes or fetch from customer API
|
||||
string customerName = ExtractCustomerName(payment);
|
||||
|
||||
Guid paymentDetailsId = Guid.NewGuid();
|
||||
|
||||
// Map to custom model with all details
|
||||
var paymentDetails = new RazorpayPaymentDetails
|
||||
{
|
||||
PaymentId = payment.Attributes["id"]?.ToString(),
|
||||
OrderId = payment.Attributes["order_id"]?.ToString(),
|
||||
Amount = Convert.ToDecimal(payment.Attributes["amount"]) / 100, // Convert from paise to rupees
|
||||
Currency = payment.Attributes["currency"]?.ToString(),
|
||||
Status = payment.Attributes["status"]?.ToString(),
|
||||
Method = payment.Attributes["method"]?.ToString(),
|
||||
Email = payment.Attributes["email"]?.ToString(),
|
||||
Contact = payment.Attributes["contact"]?.ToString(),
|
||||
Description = payment.Attributes["description"]?.ToString(),
|
||||
CustomerName = customerName,
|
||||
CreatedAt = DateTimeOffset.FromUnixTimeSeconds(Convert.ToInt64(payment.Attributes["created_at"])).DateTime,
|
||||
|
||||
// Card details (if payment method is card)
|
||||
CardDetails = payment.Attributes["method"]?.ToString() == "card"
|
||||
? ExtractCardDetails(payment)
|
||||
: null,
|
||||
|
||||
// Bank details (if payment method is netbanking)
|
||||
BankDetails = payment.Attributes["method"]?.ToString() == "netbanking"
|
||||
? ExtractBankDetails(payment)
|
||||
: null,
|
||||
|
||||
// UPI details (if payment method is upi)
|
||||
UpiDetails = payment.Attributes["method"]?.ToString() == "upi"
|
||||
? ExtractUpiDetails(payment)
|
||||
: null,
|
||||
|
||||
// Wallet details (if payment method is wallet)
|
||||
WalletDetails = payment.Attributes["method"]?.ToString() == "wallet"
|
||||
? ExtractWalletDetails(payment)
|
||||
: null,
|
||||
|
||||
// Additional details
|
||||
Fee = payment.Attributes["fee"] != null
|
||||
? Convert.ToDecimal(payment.Attributes["fee"]) / 100
|
||||
: 0,
|
||||
Tax = payment.Attributes["tax"] != null
|
||||
? Convert.ToDecimal(payment.Attributes["tax"]) / 100
|
||||
: 0,
|
||||
ErrorCode = payment.Attributes["error_code"]?.ToString(),
|
||||
ErrorDescription = payment.Attributes["error_description"]?.ToString(),
|
||||
InternationalPayment = Convert.ToBoolean(payment.Attributes["international"] ?? false),
|
||||
Captured = Convert.ToBoolean(payment.Attributes["captured"] ?? false)
|
||||
};
|
||||
|
||||
var razorpayOrderDetails = GetOrderDetails(paymentDetails.OrderId ?? "");
|
||||
|
||||
var response = new PaymentDetailsVM
|
||||
{
|
||||
Id = paymentDetailsId,
|
||||
RazorpayPaymentDetails = paymentDetails,
|
||||
RazorpayOrderDetails = razorpayOrderDetails
|
||||
};
|
||||
|
||||
string jsonString = JsonConvert.SerializeObject(response);
|
||||
|
||||
var data = _aesEncryption.Encrypt(jsonString, encryptionKey);
|
||||
|
||||
var paymentDetail = new PaymentDetail
|
||||
{
|
||||
Id = paymentDetailsId,
|
||||
PaymentId = paymentDetails.PaymentId ?? "",
|
||||
OrderId = paymentDetails.OrderId ?? "",
|
||||
Status = paymentDetails.Status ?? "",
|
||||
Method = paymentDetails.Method ?? "",
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
EncryptedDetails = data.ciphertext,
|
||||
Nonce = data.nonce,
|
||||
Tag = data.tag
|
||||
};
|
||||
|
||||
_context.PaymentDetails.Add(paymentDetail);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
_logger.LogInfo("Payment details fetched successfully from Razorpay for PaymentId: {PaymentId}", paymentId);
|
||||
return response;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error fetching payment details from Razorpay for PaymentId: {PaymentId}", paymentId);
|
||||
return new PaymentDetailsVM();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fetch order details from Razorpay
|
||||
/// </summary>
|
||||
/// <param name="orderId">Razorpay Order ID</param>
|
||||
/// <returns>Order details</returns>
|
||||
public RazorpayOrderDetails? GetOrderDetails(string orderId)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogInfo("Fetching order details from Razorpay for OrderId: {OrderId}", orderId);
|
||||
|
||||
Order order = _razorpayClient.Order.Fetch(orderId);
|
||||
|
||||
var orderDetails = new RazorpayOrderDetails
|
||||
{
|
||||
OrderId = order.Attributes["id"]?.ToString(),
|
||||
Amount = Convert.ToDecimal(order.Attributes["amount"]) / 100,
|
||||
Currency = order.Attributes["currency"]?.ToString(),
|
||||
Status = order.Attributes["status"]?.ToString(),
|
||||
Receipt = order.Attributes["receipt"]?.ToString(),
|
||||
CreatedAt = DateTimeOffset.FromUnixTimeSeconds(Convert.ToInt64(order.Attributes["created_at"])).DateTime,
|
||||
AmountPaid = order.Attributes["amount_paid"] != null
|
||||
? Convert.ToDecimal(order.Attributes["amount_paid"]) / 100
|
||||
: 0,
|
||||
AmountDue = order.Attributes["amount_due"] != null
|
||||
? Convert.ToDecimal(order.Attributes["amount_due"]) / 100
|
||||
: 0,
|
||||
Attempts = Convert.ToInt32(order.Attributes["attempts"] ?? 0)
|
||||
};
|
||||
|
||||
_logger.LogInfo("Order details fetched successfully from Razorpay for OrderId: {OrderId}", orderId);
|
||||
return orderDetails;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error fetching order details from Razorpay for OrderId: {OrderId}", orderId);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<PaymentDetailsVM> GetPaymentDetailsFromDataBase(Guid id)
|
||||
{
|
||||
var projectDetails = await _context.PaymentDetails.FirstOrDefaultAsync(pd => pd.Id == id);
|
||||
if (projectDetails == null)
|
||||
{
|
||||
return new PaymentDetailsVM();
|
||||
}
|
||||
string decrypedData = _aesEncryption.Decrypt(projectDetails.EncryptedDetails ?? new byte[32], projectDetails.Nonce ?? new byte[12], projectDetails.Tag ?? new byte[16], encryptionKey);
|
||||
// Deserialize JSON string to a Department object
|
||||
PaymentDetailsVM? vm = JsonConvert.DeserializeObject<PaymentDetailsVM>(decrypedData);
|
||||
if (vm == null)
|
||||
{
|
||||
return new PaymentDetailsVM();
|
||||
}
|
||||
else
|
||||
{
|
||||
return vm;
|
||||
}
|
||||
}
|
||||
public async Task<List<PaymentDetailsVM>> GetPaymentDetailsListFromDataBase(List<Guid> paymentDetailsIds)
|
||||
{
|
||||
var projectDetails = await _context.PaymentDetails.Where(pd => paymentDetailsIds.Contains(pd.Id)).ToListAsync();
|
||||
List<PaymentDetailsVM> response = new List<PaymentDetailsVM>();
|
||||
|
||||
foreach (var projectDetail in projectDetails)
|
||||
{
|
||||
string decrypedData = _aesEncryption.Decrypt(projectDetail.EncryptedDetails ?? new byte[32], projectDetail.Nonce ?? new byte[12], projectDetail.Tag ?? new byte[16], encryptionKey);
|
||||
// Deserialize JSON string to a Department object
|
||||
PaymentDetailsVM? vm = JsonConvert.DeserializeObject<PaymentDetailsVM>(decrypedData);
|
||||
if (vm != null)
|
||||
{
|
||||
response.Add(vm);
|
||||
}
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extract card details from payment
|
||||
/// </summary>
|
||||
private CardDetails? ExtractCardDetails(Payment payment)
|
||||
{
|
||||
try
|
||||
{
|
||||
var cardObj = payment.Attributes["card"];
|
||||
if (cardObj == null) return null;
|
||||
|
||||
string json = JsonConvert.SerializeObject(cardObj);
|
||||
Dictionary<string, object>? cardDict = JsonConvert.DeserializeObject<Dictionary<string, object>>(json);
|
||||
|
||||
return new CardDetails
|
||||
{
|
||||
CardId = cardDict?["id"]?.ToString(),
|
||||
Last4Digits = cardDict?["last4"]?.ToString(),
|
||||
Network = cardDict?["network"]?.ToString(), // Visa, MasterCard, Amex, etc.
|
||||
CardType = cardDict?["type"]?.ToString(), // credit, debit, prepaid
|
||||
Issuer = cardDict?["issuer"]?.ToString(), // Bank name
|
||||
International = Convert.ToBoolean(cardDict?["international"] ?? false),
|
||||
Emi = Convert.ToBoolean(cardDict?["emi"] ?? false),
|
||||
SubType = cardDict?["sub_type"]?.ToString() // consumer, business
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error extracting card details");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extract bank details from payment
|
||||
/// </summary>
|
||||
private BankDetails? ExtractBankDetails(Payment payment)
|
||||
{
|
||||
try
|
||||
{
|
||||
return new BankDetails
|
||||
{
|
||||
Bank = payment.Attributes["bank"]?.ToString(),
|
||||
BankCode = payment.Attributes["bank_code"]?.ToString()
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error extracting bank details");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extract UPI details from payment
|
||||
/// </summary>
|
||||
private UpiDetails? ExtractUpiDetails(Payment payment)
|
||||
{
|
||||
try
|
||||
{
|
||||
var vpaObj = payment.Attributes["vpa"];
|
||||
|
||||
return new UpiDetails
|
||||
{
|
||||
Vpa = vpaObj?.ToString(), // UPI ID
|
||||
Provider = payment.Attributes["provider"]?.ToString()
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error extracting UPI details");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extract wallet details from payment
|
||||
/// </summary>
|
||||
private WalletDetails? ExtractWalletDetails(Payment payment)
|
||||
{
|
||||
try
|
||||
{
|
||||
return new WalletDetails
|
||||
{
|
||||
WalletName = payment.Attributes["wallet"]?.ToString() // paytm, phonepe, amazonpay, etc.
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error extracting wallet details");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extract customer name from payment object
|
||||
/// </summary>
|
||||
private string ExtractCustomerName(Payment payment)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Option 1: Get from notes (if you passed customer name during order creation)
|
||||
var notesObj = payment.Attributes["notes"];
|
||||
if (notesObj != null)
|
||||
{
|
||||
var notesDict = notesObj as Dictionary<string, object>;
|
||||
if (notesDict != null && notesDict.ContainsKey("customer_name"))
|
||||
{
|
||||
return notesDict["customer_name"]?.ToString() ?? "CustomerName";
|
||||
}
|
||||
if (notesDict != null && notesDict.ContainsKey("name"))
|
||||
{
|
||||
return notesDict["name"]?.ToString() ?? "CustomerName";
|
||||
}
|
||||
}
|
||||
|
||||
// Option 2: Get from customer ID and fetch customer details
|
||||
var customerId = payment.Attributes["customer_id"]?.ToString();
|
||||
if (!string.IsNullOrEmpty(customerId))
|
||||
{
|
||||
try
|
||||
{
|
||||
Customer customer = _razorpayClient.Customer.Fetch(customerId);
|
||||
return customer.Attributes["name"]?.ToString() ?? "CustomerName";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error fetching customer details for CustomerId: {CustomerId}", customerId);
|
||||
}
|
||||
}
|
||||
|
||||
// Option 3: Extract from card holder name (if available)
|
||||
if (payment.Attributes["method"]?.ToString() == "card")
|
||||
{
|
||||
var cardObj = payment.Attributes["card"];
|
||||
if (cardObj != null)
|
||||
{
|
||||
string json = JsonConvert.SerializeObject(cardObj);
|
||||
Dictionary<string, object>? dictionary = JsonConvert.DeserializeObject<Dictionary<string, object>>(json);
|
||||
|
||||
var cardName = dictionary?["name"]?.ToString();
|
||||
if (!string.IsNullOrEmpty(cardName))
|
||||
{
|
||||
return cardName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Option 4: Use email as fallback
|
||||
return payment.Attributes["email"]?.ToString() ?? "Customer";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error extracting customer name from payment");
|
||||
return "Customer";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
namespace Marco.Pms.Services.Service.ServiceInterfaces
|
||||
{
|
||||
public interface IAesEncryption
|
||||
{
|
||||
(byte[] ciphertext, byte[] nonce, byte[] tag) Encrypt(string plaintext, byte[] key);
|
||||
string Decrypt(byte[] ciphertext, byte[] nonce, byte[] tag, byte[] key);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,15 @@
|
||||
using Marco.Pms.Model.Employees;
|
||||
using Marco.Pms.Model.ViewModels.PaymentGetway;
|
||||
|
||||
namespace Marco.Pms.Services.Service.ServiceInterfaces
|
||||
{
|
||||
public interface IRazorpayService
|
||||
{
|
||||
CreateOrderVM CreateOrder(double amount, Employee loggedInEmployee, Guid tenantId);
|
||||
string GetExpectedSignature(string payload);
|
||||
Task<PaymentDetailsVM> GetPaymentDetails(string paymentId);
|
||||
Task<PaymentDetailsVM> GetPaymentDetailsFromDataBase(Guid id);
|
||||
Task<List<PaymentDetailsVM>> GetPaymentDetailsListFromDataBase(List<Guid> paymentDetailsIds);
|
||||
RazorpayOrderDetails? GetOrderDetails(string orderId);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
using Marco.Pms.Model.Utilities;
|
||||
|
||||
namespace Marco.Pms.Services.Service.ServiceInterfaces
|
||||
{
|
||||
public interface ITenantService
|
||||
{
|
||||
Task<ApiResponse<object>> CreateTenantAsync(Guid enquireId, Guid paymentDetailId, Guid planId);
|
||||
}
|
||||
}
|
||||
682
Marco.Pms.Services/Service/TenantService.cs
Normal file
682
Marco.Pms.Services/Service/TenantService.cs
Normal file
@ -0,0 +1,682 @@
|
||||
using AutoMapper;
|
||||
using Marco.Pms.DataAccess.Data;
|
||||
using Marco.Pms.Helpers.Utility;
|
||||
using Marco.Pms.Model.Dtos.Tenant;
|
||||
using Marco.Pms.Model.Employees;
|
||||
using Marco.Pms.Model.Entitlements;
|
||||
using Marco.Pms.Model.OrganizationModel;
|
||||
using Marco.Pms.Model.Projects;
|
||||
using Marco.Pms.Model.Roles;
|
||||
using Marco.Pms.Model.TenantModels;
|
||||
using Marco.Pms.Model.TenantModels.MongoDBModel;
|
||||
using Marco.Pms.Model.Utilities;
|
||||
using Marco.Pms.Model.ViewModels.Activities;
|
||||
using Marco.Pms.Model.ViewModels.Tenant;
|
||||
using Marco.Pms.Services.Helpers;
|
||||
using Marco.Pms.Services.Service.ServiceInterfaces;
|
||||
using MarcoBMS.Services.Helpers;
|
||||
using MarcoBMS.Services.Service;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Net;
|
||||
|
||||
namespace Marco.Pms.Services.Service
|
||||
{
|
||||
public class TenantService : ITenantService
|
||||
{
|
||||
private readonly IDbContextFactory<ApplicationDbContext> _dbContextFactory;
|
||||
private readonly IServiceScopeFactory _serviceScopeFactory;
|
||||
private readonly ILoggingService _logger;
|
||||
private readonly UserManager<ApplicationUser> _userManager;
|
||||
private readonly IMapper _mapper;
|
||||
private readonly UserHelper _userHelper;
|
||||
private readonly FeatureDetailsHelper _featureDetailsHelper;
|
||||
|
||||
private readonly static Guid projectActiveStatus = Guid.Parse("b74da4c2-d07e-46f2-9919-e75e49b12731");
|
||||
private readonly static Guid projectInProgressStatus = Guid.Parse("cdad86aa-8a56-4ff4-b633-9c629057dfef");
|
||||
private readonly static Guid projectOnHoldStatus = Guid.Parse("603e994b-a27f-4e5d-a251-f3d69b0498ba");
|
||||
private readonly static Guid projectInActiveStatus = Guid.Parse("ef1c356e-0fe0-42df-a5d3-8daee355492d");
|
||||
private readonly static Guid projectCompletedStatus = Guid.Parse("33deaef9-9af1-4f2a-b443-681ea0d04f81");
|
||||
|
||||
private readonly static Guid tenantActiveStatus = Guid.Parse("62b05792-5115-4f99-8ff5-e8374859b191");
|
||||
private readonly static Guid activePlanStatus = Guid.Parse("cd3a68ea-41fd-42f0-bd0c-c871c7337727");
|
||||
private readonly static Guid EmployeeFeatureId = Guid.Parse("81ab8a87-8ccd-4015-a917-0627cee6a100");
|
||||
private readonly static string AdminRoleName = "Admin";
|
||||
public TenantService(IDbContextFactory<ApplicationDbContext> dbContextFactory,
|
||||
IServiceScopeFactory serviceScopeFactory,
|
||||
ILoggingService logger,
|
||||
UserManager<ApplicationUser> userManager,
|
||||
IMapper mapper,
|
||||
UserHelper userHelper,
|
||||
FeatureDetailsHelper featureDetailsHelper)
|
||||
{
|
||||
_dbContextFactory = dbContextFactory ?? throw new ArgumentNullException(nameof(dbContextFactory));
|
||||
_serviceScopeFactory = serviceScopeFactory ?? throw new ArgumentNullException(nameof(serviceScopeFactory));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
_userManager = userManager ?? throw new ArgumentNullException(nameof(userManager));
|
||||
_mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
|
||||
_userHelper = userHelper ?? throw new ArgumentNullException(nameof(userHelper));
|
||||
_featureDetailsHelper = featureDetailsHelper ?? throw new ArgumentNullException(nameof(featureDetailsHelper));
|
||||
}
|
||||
|
||||
public async Task<ApiResponse<object>> CreateTenantAsync(Guid enquireId, Guid paymentDetailId, Guid planId)
|
||||
{
|
||||
using var scope = _serviceScopeFactory.CreateScope();
|
||||
await using var _context = await _dbContextFactory.CreateDbContextAsync();
|
||||
|
||||
var _configuration = scope.ServiceProvider.GetRequiredService<IConfiguration>();
|
||||
var _emailSender = scope.ServiceProvider.GetRequiredService<IEmailSender>();
|
||||
|
||||
var tenantEnquireTask = Task.Run(async () =>
|
||||
{
|
||||
await using var context = await _dbContextFactory.CreateDbContextAsync();
|
||||
return await context.TenantEnquires.FirstOrDefaultAsync(te => te.Id == enquireId);
|
||||
});
|
||||
var paymentDetailTask = Task.Run(async () =>
|
||||
{
|
||||
await using var context = await _dbContextFactory.CreateDbContextAsync();
|
||||
return await context.PaymentDetails.FirstOrDefaultAsync(pd => pd.Id == paymentDetailId);
|
||||
});
|
||||
var subscriptionPlanTask = Task.Run(async () =>
|
||||
{
|
||||
await using var context = await _dbContextFactory.CreateDbContextAsync();
|
||||
return await context.SubscriptionPlanDetails.Include(sp => sp.Plan).FirstOrDefaultAsync(sp => sp.Id == planId);
|
||||
});
|
||||
|
||||
await Task.WhenAll(tenantEnquireTask, paymentDetailTask, subscriptionPlanTask);
|
||||
|
||||
var tenantEnquire = tenantEnquireTask.Result;
|
||||
var paymentDetail = paymentDetailTask.Result;
|
||||
var subscriptionPlan = subscriptionPlanTask.Result;
|
||||
|
||||
if (tenantEnquire == null)
|
||||
{
|
||||
_logger.LogWarning("Tenant Enquire {TenantEnquireId} not found in database", enquireId);
|
||||
return ApiResponse<object>.ErrorResponse("Tenant Enquire not found", "Tenant Enquire not found", 404);
|
||||
}
|
||||
if (paymentDetail == null)
|
||||
{
|
||||
_logger.LogWarning("Payment Details {PaymentDetailsId} not found in database", paymentDetailId);
|
||||
return ApiResponse<object>.ErrorResponse("Payment Details not found", "Payment Details not found", 404);
|
||||
}
|
||||
if (subscriptionPlan == null)
|
||||
{
|
||||
_logger.LogWarning("Subscription plan {PlanId} not found in database", planId);
|
||||
return ApiResponse<object>.ErrorResponse("Subscription plan not found", "Subscription plan not found", 404);
|
||||
}
|
||||
|
||||
var existingUser = await _userManager.FindByEmailAsync(tenantEnquire.Email);
|
||||
if (existingUser != null)
|
||||
{
|
||||
_logger.LogWarning("Tenant creation failed for email {Email}: an application user with this email already exists.", tenantEnquire.Email);
|
||||
return ApiResponse<object>.ErrorResponse("Tenant cannot be created", "A user with the specified email already exists.", 409);
|
||||
}
|
||||
|
||||
await using var transaction = await _context.Database.BeginTransactionAsync();
|
||||
try
|
||||
{
|
||||
Guid employeeId = Guid.NewGuid();
|
||||
DateTime onBoardingDate = DateTime.UtcNow;
|
||||
Guid tenantId = Guid.NewGuid();
|
||||
|
||||
// Get last SPRID and increment for new organization
|
||||
var lastOrganization = await _context.Organizations.OrderByDescending(sp => sp.SPRID).FirstOrDefaultAsync();
|
||||
double lastSPRID = lastOrganization?.SPRID ?? 5400;
|
||||
|
||||
// Map DTO to entity and set defaults
|
||||
Organization organization = new Organization
|
||||
{
|
||||
Name = tenantEnquire.OrganizationName,
|
||||
Email = tenantEnquire.Email,
|
||||
ContactPerson = $"{tenantEnquire.FirstName} {tenantEnquire.LastName}",
|
||||
Address = tenantEnquire.BillingAddress,
|
||||
ContactNumber = tenantEnquire.ContactNumber,
|
||||
SPRID = lastSPRID + 1,
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
CreatedById = employeeId,
|
||||
IsActive = true
|
||||
};
|
||||
|
||||
_context.Organizations.Add(organization);
|
||||
|
||||
// Create the primary Tenant entity
|
||||
|
||||
var tenant = new Tenant
|
||||
{
|
||||
Id = tenantId,
|
||||
Name = tenantEnquire.OrganizationName,
|
||||
Email = tenantEnquire.Email,
|
||||
ContactName = $"{tenantEnquire.FirstName} {tenantEnquire.LastName}",
|
||||
ContactNumber = tenantEnquire.ContactNumber,
|
||||
OrganizationSize = tenantEnquire.OrganizationSize,
|
||||
BillingAddress = tenantEnquire.BillingAddress,
|
||||
IndustryId = tenantEnquire.IndustryId,
|
||||
Reference = tenantEnquire.Reference,
|
||||
OnBoardingDate = onBoardingDate,
|
||||
TenantStatusId = tenantActiveStatus,
|
||||
OrganizationId = organization.Id,
|
||||
CreatedById = employeeId,
|
||||
IsActive = true,
|
||||
IsSuperTenant = false
|
||||
};
|
||||
|
||||
_context.Tenants.Add(tenant);
|
||||
|
||||
// Create the root ApplicationUser for the new tenant
|
||||
var applicationUser = new ApplicationUser
|
||||
{
|
||||
Email = tenantEnquire.Email,
|
||||
UserName = tenantEnquire.Email, // Best practice to use email as username for simplicity
|
||||
IsRootUser = true,
|
||||
EmailConfirmed = true // Auto-confirming email as it's part of a trusted setup process
|
||||
};
|
||||
|
||||
// SECURITY WARNING: Hardcoded passwords are a major vulnerability.
|
||||
// Replace "User@123" with a securely generated random password.
|
||||
var initialPassword = "User@123"; // TODO: Replace with password generation service.
|
||||
var result = await _userManager.CreateAsync(applicationUser, initialPassword);
|
||||
|
||||
if (!result.Succeeded)
|
||||
{
|
||||
// If user creation fails, roll back the transaction immediately and return the errors.
|
||||
await transaction.RollbackAsync();
|
||||
var errors = result.Errors.Select(e => e.Description).ToList();
|
||||
_logger.LogWarning("Failed to create ApplicationUser for tenant {TenantName}. Errors: {Errors}", tenantEnquire.OrganizationName, string.Join(", ", errors));
|
||||
return ApiResponse<object>.ErrorResponse("Failed to create user", errors, 400);
|
||||
}
|
||||
|
||||
// Create the default "Admin" Job Role for the tenant
|
||||
var adminJobRole = new JobRole
|
||||
{
|
||||
Name = AdminRoleName,
|
||||
Description = "Default administrator role for the tenant.",
|
||||
TenantId = tenantId
|
||||
};
|
||||
_context.JobRoles.Add(adminJobRole);
|
||||
|
||||
// Create the primary Employee record and link it to the ApplicationUser and JobRole
|
||||
var employeeUser = new Employee
|
||||
{
|
||||
Id = employeeId,
|
||||
FirstName = tenantEnquire.FirstName,
|
||||
LastName = tenantEnquire.LastName,
|
||||
Email = tenantEnquire.Email,
|
||||
PhoneNumber = tenantEnquire.ContactNumber,
|
||||
JoiningDate = onBoardingDate,
|
||||
ApplicationUserId = applicationUser.Id,
|
||||
JobRole = adminJobRole, // Link to the newly created role
|
||||
CurrentAddress = tenantEnquire.BillingAddress,
|
||||
IsActive = true,
|
||||
IsSystem = false,
|
||||
IsPrimary = true,
|
||||
OrganizationId = organization.Id,
|
||||
HasApplicationAccess = true
|
||||
};
|
||||
_context.Employees.Add(employeeUser);
|
||||
|
||||
var applicationRole = new ApplicationRole
|
||||
{
|
||||
Role = "Super User",
|
||||
Description = "Super User",
|
||||
IsSystem = true,
|
||||
TenantId = tenantId
|
||||
};
|
||||
_context.ApplicationRoles.Add(applicationRole);
|
||||
|
||||
var rolePermissionMappigs = new List<RolePermissionMappings> {
|
||||
new RolePermissionMappings
|
||||
{
|
||||
ApplicationRoleId = applicationRole.Id,
|
||||
FeaturePermissionId = PermissionsMaster.ModifyTenant
|
||||
},
|
||||
new RolePermissionMappings
|
||||
{
|
||||
ApplicationRoleId = applicationRole.Id,
|
||||
FeaturePermissionId = PermissionsMaster.ViewTenant
|
||||
},
|
||||
new RolePermissionMappings
|
||||
{
|
||||
ApplicationRoleId = applicationRole.Id,
|
||||
FeaturePermissionId = PermissionsMaster.ManageMasters
|
||||
},
|
||||
new RolePermissionMappings
|
||||
{
|
||||
ApplicationRoleId = applicationRole.Id,
|
||||
FeaturePermissionId = PermissionsMaster.ViewMasters
|
||||
},
|
||||
new RolePermissionMappings
|
||||
{
|
||||
ApplicationRoleId = applicationRole.Id,
|
||||
FeaturePermissionId = PermissionsMaster.ViewOrganization
|
||||
},
|
||||
new RolePermissionMappings
|
||||
{
|
||||
ApplicationRoleId = applicationRole.Id,
|
||||
FeaturePermissionId = PermissionsMaster.AddOrganization
|
||||
},
|
||||
new RolePermissionMappings
|
||||
{
|
||||
ApplicationRoleId = applicationRole.Id,
|
||||
FeaturePermissionId = PermissionsMaster.EditOrganization
|
||||
}
|
||||
};
|
||||
_context.RolePermissionMappings.AddRange(rolePermissionMappigs);
|
||||
|
||||
_context.EmployeeRoleMappings.Add(new EmployeeRoleMapping
|
||||
{
|
||||
EmployeeId = employeeUser.Id,
|
||||
RoleId = applicationRole.Id,
|
||||
IsEnabled = true,
|
||||
TenantId = tenantId
|
||||
});
|
||||
|
||||
// Create a default project for the new tenant
|
||||
var project = new Project
|
||||
{
|
||||
Name = "Default Project",
|
||||
ProjectStatusId = Guid.Parse("b74da4c2-d07e-46f2-9919-e75e49b12731"), // Consider using a constant for this GUID
|
||||
ProjectAddress = tenantEnquire.BillingAddress,
|
||||
StartDate = onBoardingDate,
|
||||
EndDate = DateTime.MaxValue,
|
||||
PromoterId = organization.Id,
|
||||
PMCId = organization.Id,
|
||||
ContactPerson = tenant.ContactName,
|
||||
TenantId = tenantId
|
||||
};
|
||||
_context.Projects.Add(project);
|
||||
|
||||
var projectAllocation = new ProjectAllocation
|
||||
{
|
||||
ProjectId = project.Id,
|
||||
EmployeeId = employeeUser.Id,
|
||||
AllocationDate = onBoardingDate,
|
||||
IsActive = true,
|
||||
JobRoleId = adminJobRole.Id,
|
||||
TenantId = tenantId
|
||||
};
|
||||
_context.ProjectAllocations.Add(projectAllocation);
|
||||
|
||||
// All entities are now added to the context. Save them all in a single database operation.
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
// 4. --- POST-CREATION ACTIONS ---
|
||||
// Generate a password reset token so the new user can set their own password.
|
||||
_logger.LogInfo("User {Email} created. Sending password setup email.", applicationUser.Email);
|
||||
var token = await _userManager.GeneratePasswordResetTokenAsync(applicationUser);
|
||||
var resetLink = $"{_configuration["AppSettings:WebFrontendUrl"]}/reset-password?token={WebUtility.UrlEncode(token)}&email={WebUtility.UrlEncode(applicationUser.Email)}";
|
||||
await _emailSender.SendResetPasswordEmailOnRegister(applicationUser.Email, employeeUser.FirstName, resetLink);
|
||||
|
||||
// Map the result to a ViewModel for the API response.
|
||||
var tenantVM = _mapper.Map<TenantVM>(tenant);
|
||||
tenantVM.CreatedBy = _mapper.Map<BasicEmployeeVM>(employeeUser);
|
||||
|
||||
// Commit the transaction as all operations were successful.
|
||||
await transaction.CommitAsync();
|
||||
|
||||
await AddSubscriptionAsync(tenantId, employeeId, paymentDetailId, planId);
|
||||
|
||||
_logger.LogInfo("Successfully created tenant {TenantId} for organization {OrganizationName}.", tenant.Id, tenant.Name);
|
||||
return ApiResponse<object>.SuccessResponse(tenantVM, "Tenant created successfully.", 201);
|
||||
}
|
||||
catch (DbUpdateException dbEx)
|
||||
{
|
||||
await transaction.RollbackAsync();
|
||||
// Log the detailed database exception, including the inner exception if available.
|
||||
_logger.LogError(dbEx, "A database update exception occurred while creating tenant for email {Email}. Inner Exception: {InnerException}",
|
||||
tenantEnquire.Email, dbEx.InnerException?.Message ?? string.Empty);
|
||||
return ApiResponse<object>.ErrorResponse("An internal database error occurred.", ExceptionMapper(dbEx), 500);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Log the general exception.
|
||||
_logger.LogError(ex, "An unexpected exception occurred while creating tenant for email {Email}.", tenantEnquire.Email);
|
||||
return ApiResponse<object>.ErrorResponse("An unexpected internal error occurred.", ExceptionMapper(ex), 500);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ApiResponse<object>> AddSubscriptionAsync(Guid tenantId, Guid employeeId, Guid paymentDetailId, Guid planId)
|
||||
{
|
||||
|
||||
_logger.LogInfo("AddSubscription called for Tenant {TenantId} and Plan {PlanId}", tenantId, planId);
|
||||
|
||||
await using var _context = await _dbContextFactory.CreateDbContextAsync();
|
||||
using var scope = _serviceScopeFactory.CreateScope();
|
||||
|
||||
var subscriptionPlan = await _context.SubscriptionPlanDetails.Include(sp => sp.Plan).FirstOrDefaultAsync(sp => sp.Id == planId);
|
||||
|
||||
var tenant = await _context.Tenants.FirstOrDefaultAsync(t => t.Id == tenantId);
|
||||
if (tenant == null)
|
||||
{
|
||||
_logger.LogWarning("Tenant {TenantId} not found in database", tenantId);
|
||||
return ApiResponse<object>.ErrorResponse("Tenant not found", "Tenant not found", 404);
|
||||
}
|
||||
if (subscriptionPlan == null)
|
||||
{
|
||||
_logger.LogWarning("Subscription plan {PlanId} not found in database", planId);
|
||||
return ApiResponse<object>.ErrorResponse("Subscription plan not found", "Subscription plan not found", 404);
|
||||
}
|
||||
var activeUsers = await _context.Employees.CountAsync(e => e.Email != null && e.ApplicationUserId != null && e.TenantId == tenant.Id && e.IsActive);
|
||||
if (activeUsers > subscriptionPlan.MaxUser)
|
||||
{
|
||||
_logger.LogWarning("Add less max user than the active user in the tenant {TenantId}", tenant.Id);
|
||||
return ApiResponse<object>.ErrorResponse("Invalid Max user count", "Max User count must be higher than active user count", 400);
|
||||
}
|
||||
await using var transaction = await _context.Database.BeginTransactionAsync();
|
||||
var utcNow = DateTime.UtcNow;
|
||||
|
||||
// Prepare subscription dates based on frequency
|
||||
var endDate = subscriptionPlan.Frequency switch
|
||||
{
|
||||
PLAN_FREQUENCY.MONTHLY => utcNow.AddDays(30),
|
||||
PLAN_FREQUENCY.QUARTERLY => utcNow.AddDays(90),
|
||||
PLAN_FREQUENCY.HALF_YEARLY => utcNow.AddDays(120),
|
||||
PLAN_FREQUENCY.YEARLY => utcNow.AddDays(360),
|
||||
_ => utcNow // default if unknown
|
||||
};
|
||||
|
||||
var tenantSubscription = new TenantSubscriptions
|
||||
{
|
||||
TenantId = tenantId,
|
||||
PlanId = planId,
|
||||
StatusId = activePlanStatus,
|
||||
CreatedAt = utcNow,
|
||||
MaxUsers = subscriptionPlan.MaxUser,
|
||||
CreatedById = employeeId,
|
||||
CurrencyId = subscriptionPlan.CurrencyId,
|
||||
PaymentDetailId = paymentDetailId,
|
||||
IsTrial = false,
|
||||
StartDate = utcNow,
|
||||
EndDate = endDate,
|
||||
NextBillingDate = endDate,
|
||||
AutoRenew = false
|
||||
};
|
||||
|
||||
_context.TenantSubscriptions.Add(tenantSubscription);
|
||||
|
||||
try
|
||||
{
|
||||
await _context.SaveChangesAsync();
|
||||
_logger.LogInfo("Tenant subscription added successfully for Tenant {TenantId}, Plan {PlanId}",
|
||||
tenantId, planId);
|
||||
}
|
||||
catch (DbUpdateException dbEx)
|
||||
{
|
||||
_logger.LogError(dbEx, "Database exception while adding subscription plan to tenant {TenantId}", tenantId);
|
||||
return ApiResponse<object>.ErrorResponse("Internal error occured", ExceptionMapper(dbEx), 500);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var features = await _featureDetailsHelper.GetFeatureDetails(subscriptionPlan.FeaturesId);
|
||||
if (features == null)
|
||||
{
|
||||
_logger.LogInfo("No features found for subscription plan {PlanId}", planId);
|
||||
await transaction.CommitAsync();
|
||||
return ApiResponse<object>.SuccessResponse(tenantSubscription, "Tenant subscription successfully added", 200);
|
||||
}
|
||||
|
||||
// Helper to get permissions for a module asynchronously
|
||||
async Task<List<Guid>> GetPermissionsForModuleAsync(List<Guid>? featureIds)
|
||||
{
|
||||
if (featureIds == null || featureIds.Count == 0) return new List<Guid>();
|
||||
|
||||
await using var ctx = await _dbContextFactory.CreateDbContextAsync();
|
||||
return await ctx.FeaturePermissions.AsNoTracking()
|
||||
.Where(fp => featureIds.Contains(fp.FeatureId))
|
||||
.Select(fp => fp.Id)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
// Fetch permission tasks for all modules in parallel
|
||||
var projectPermissionTask = GetPermissionsForModuleAsync(features.Modules?.ProjectManagement?.FeatureId);
|
||||
var attendancePermissionTask = GetPermissionsForModuleAsync(features.Modules?.Attendance?.FeatureId);
|
||||
var directoryPermissionTask = GetPermissionsForModuleAsync(features.Modules?.Directory?.FeatureId);
|
||||
var expensePermissionTask = GetPermissionsForModuleAsync(features.Modules?.Expense?.FeatureId);
|
||||
var employeePermissionTask = GetPermissionsForModuleAsync(new List<Guid> { EmployeeFeatureId });
|
||||
|
||||
await Task.WhenAll(projectPermissionTask, attendancePermissionTask, directoryPermissionTask, expensePermissionTask, employeePermissionTask);
|
||||
|
||||
var newPermissionIds = new List<Guid>();
|
||||
var deletePermissionIds = new List<Guid>();
|
||||
|
||||
// Add or remove permissions based on modules enabled status
|
||||
void ProcessPermissions(bool? enabled, List<Guid> permissions)
|
||||
{
|
||||
if (enabled == true)
|
||||
newPermissionIds.AddRange(permissions);
|
||||
else
|
||||
deletePermissionIds.AddRange(permissions);
|
||||
}
|
||||
|
||||
ProcessPermissions(features.Modules?.ProjectManagement?.Enabled, projectPermissionTask.Result);
|
||||
ProcessPermissions(features.Modules?.Attendance?.Enabled, attendancePermissionTask.Result);
|
||||
ProcessPermissions(features.Modules?.Directory?.Enabled, directoryPermissionTask.Result);
|
||||
ProcessPermissions(features.Modules?.Expense?.Enabled, expensePermissionTask.Result);
|
||||
|
||||
newPermissionIds = newPermissionIds.Distinct().ToList();
|
||||
deletePermissionIds = deletePermissionIds.Distinct().ToList();
|
||||
|
||||
// Get root employee and role for this tenant
|
||||
var rootEmployee = await _context.Employees
|
||||
.Include(e => e.ApplicationUser)
|
||||
.FirstOrDefaultAsync(e => e.ApplicationUser != null && (e.ApplicationUser.IsRootUser ?? false) && e.OrganizationId == tenant.OrganizationId);
|
||||
|
||||
if (rootEmployee == null)
|
||||
{
|
||||
_logger.LogWarning("Root employee not found for tenant {TenantId}", tenantId);
|
||||
await transaction.CommitAsync();
|
||||
return ApiResponse<object>.SuccessResponse(tenantSubscription, "Tenant subscription successfully added", 200);
|
||||
}
|
||||
|
||||
var roleId = await _context.EmployeeRoleMappings
|
||||
.AsNoTracking()
|
||||
.Where(er => er.EmployeeId == rootEmployee.Id && er.TenantId == tenantId)
|
||||
.Select(er => er.RoleId)
|
||||
.FirstOrDefaultAsync();
|
||||
|
||||
if (roleId == Guid.Empty)
|
||||
{
|
||||
_logger.LogWarning("RoleId for root employee {EmployeeId} in tenant {TenantId} not found", rootEmployee.Id, tenantId);
|
||||
await transaction.CommitAsync();
|
||||
return ApiResponse<object>.SuccessResponse(tenantSubscription, "Tenant subscription successfully added", 200);
|
||||
}
|
||||
|
||||
var oldRolePermissionMappings = await _context.RolePermissionMappings
|
||||
.Where(rp => rp.ApplicationRoleId == roleId)
|
||||
.ToListAsync();
|
||||
|
||||
var oldPermissionIds = oldRolePermissionMappings.Select(rp => rp.FeaturePermissionId).ToList();
|
||||
|
||||
// Prevent accidentally deleting essential employee permissions
|
||||
var permissionIdCount = oldPermissionIds.Count - deletePermissionIds.Count;
|
||||
if (permissionIdCount <= 4 && deletePermissionIds.Any())
|
||||
{
|
||||
var employeePermissionIds = employeePermissionTask.Result;
|
||||
deletePermissionIds = deletePermissionIds.Where(p => !employeePermissionIds.Contains(p)).ToList();
|
||||
}
|
||||
|
||||
// Prepare mappings to delete and add
|
||||
var deleteMappings = oldRolePermissionMappings.Where(rp => deletePermissionIds.Contains(rp.FeaturePermissionId)).ToList();
|
||||
var addRolePermissionMappings = newPermissionIds
|
||||
.Where(p => !oldPermissionIds.Contains(p))
|
||||
.Select(p => new RolePermissionMappings
|
||||
{
|
||||
ApplicationRoleId = roleId,
|
||||
FeaturePermissionId = p
|
||||
})
|
||||
.ToList();
|
||||
|
||||
if (addRolePermissionMappings.Any())
|
||||
{
|
||||
_context.RolePermissionMappings.AddRange(addRolePermissionMappings);
|
||||
_logger.LogInfo("Added {Count} new role permission mappings for role {RoleId}", addRolePermissionMappings.Count, roleId);
|
||||
}
|
||||
if (deleteMappings.Any())
|
||||
{
|
||||
_context.RolePermissionMappings.RemoveRange(deleteMappings);
|
||||
_logger.LogInfo("Removed {Count} role permission mappings for role {RoleId}", deleteMappings.Count, roleId);
|
||||
}
|
||||
|
||||
var _cache = scope.ServiceProvider.GetRequiredService<CacheUpdateHelper>();
|
||||
await _cache.ClearAllEmployeesFromCacheByTenantId(tenant.Id);
|
||||
|
||||
var _masteData = scope.ServiceProvider.GetRequiredService<MasterDataService>();
|
||||
|
||||
if (features.Modules?.ProjectManagement?.Enabled ?? false)
|
||||
{
|
||||
var workCategoryMaster = _masteData.GetWorkCategoriesData(tenant.Id);
|
||||
var workStatusMaster = _masteData.GetWorkStatusesData(tenant.Id);
|
||||
|
||||
_context.WorkCategoryMasters.AddRange(workCategoryMaster);
|
||||
_context.WorkStatusMasters.AddRange(workStatusMaster);
|
||||
}
|
||||
if (features.Modules?.Expense?.Enabled ?? false)
|
||||
{
|
||||
var expensesTypeMaster = _masteData.GetExpensesTypeesData(tenant.Id);
|
||||
var paymentModeMatser = _masteData.GetPaymentModesData(tenant.Id);
|
||||
|
||||
_context.ExpensesTypeMaster.AddRange(expensesTypeMaster);
|
||||
_context.PaymentModeMatser.AddRange(paymentModeMatser);
|
||||
}
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
await transaction.CommitAsync();
|
||||
|
||||
_logger.LogInfo("Permissions updated successfully for tenant {TenantId} subscription", tenantId);
|
||||
|
||||
return ApiResponse<object>.SuccessResponse(tenantSubscription, "Tenant Subscription Successfully", 200);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await transaction.RollbackAsync();
|
||||
_logger.LogError(ex, "Exception occurred while updating permissions for tenant {TenantId}", tenantId);
|
||||
return ApiResponse<object>.ErrorResponse("Internal error occured", ExceptionMapper(ex), 500);
|
||||
}
|
||||
}
|
||||
|
||||
#region =================================================================== Helper Functions ===================================================================
|
||||
private static object ExceptionMapper(Exception ex)
|
||||
{
|
||||
return new
|
||||
{
|
||||
Message = ex.Message,
|
||||
StackTrace = ex.StackTrace,
|
||||
Source = ex.Source,
|
||||
InnerException = new
|
||||
{
|
||||
Message = ex.InnerException?.Message,
|
||||
StackTrace = ex.InnerException?.StackTrace,
|
||||
Source = ex.InnerException?.Source,
|
||||
}
|
||||
};
|
||||
}
|
||||
private bool IsBase64String(string? input)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(input))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
string base64Data = input;
|
||||
const string dataUriMarker = "base64,";
|
||||
int markerIndex = input.IndexOf(dataUriMarker, StringComparison.Ordinal);
|
||||
|
||||
// If the marker is found, extract the actual Base64 data
|
||||
if (markerIndex >= 0)
|
||||
{
|
||||
base64Data = input.Substring(markerIndex + dataUriMarker.Length);
|
||||
}
|
||||
|
||||
// Now, validate the extracted payload
|
||||
base64Data = base64Data.Trim();
|
||||
|
||||
// Check for valid length (must be a multiple of 4) and non-empty
|
||||
if (base64Data.Length == 0 || base64Data.Length % 4 != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// The most reliable test is to simply try to convert it.
|
||||
// The .NET converter is strict and will throw a FormatException
|
||||
// for invalid characters or incorrect padding.
|
||||
try
|
||||
{
|
||||
Convert.FromBase64String(base64Data);
|
||||
return true;
|
||||
}
|
||||
catch (FormatException)
|
||||
{
|
||||
// The string is not a valid Base64 payload.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles the creation and persistence of SubscriptionPlanDetails for a particular frequency.
|
||||
/// </summary>
|
||||
private async Task<ApiResponse<SubscriptionPlanVM>> CreateSubscriptionPlanDetails(SubscriptionPlanDetailsDto? model, SubscriptionPlan plan, Employee loggedInEmployee, PLAN_FREQUENCY frequency)
|
||||
{
|
||||
if (model == null)
|
||||
{
|
||||
_logger.LogInfo("No plan detail provided for {Frequency} - skipping.", frequency);
|
||||
return ApiResponse<SubscriptionPlanVM>.ErrorResponse("Invalid", "No data provided for this frequency", 400);
|
||||
}
|
||||
|
||||
await using var _dbContext = await _dbContextFactory.CreateDbContextAsync();
|
||||
|
||||
// Fetch currency master record
|
||||
var currencyMaster = await _dbContext.CurrencyMaster.AsNoTracking().FirstOrDefaultAsync(c => c.Id == model.CurrencyId);
|
||||
if (currencyMaster == null)
|
||||
{
|
||||
_logger.LogWarning("Currency with Id {CurrencyId} not found for plan {PlanId}/{Frequency}.", model.CurrencyId, plan.Id, frequency);
|
||||
return ApiResponse<SubscriptionPlanVM>.ErrorResponse("Currency not found", "Specified currency not found", 404);
|
||||
}
|
||||
|
||||
// Map to entity and create related feature details
|
||||
var planDetails = _mapper.Map<SubscriptionPlanDetails>(model);
|
||||
var features = _mapper.Map<FeatureDetails>(model.Features);
|
||||
|
||||
try
|
||||
{
|
||||
await _featureDetailsHelper.AddFeatureDetails(features);
|
||||
_logger.LogInfo("FeatureDetails for plan {PlanId}/{Frequency} saved in MongoDB.", plan.Id, frequency);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Exception occurred while saving features in MongoDB for {PlanId}/{Frequency}.", plan.Id, frequency);
|
||||
return ApiResponse<SubscriptionPlanVM>.ErrorResponse("Internal error occurred", ExceptionMapper(ex), 500);
|
||||
}
|
||||
|
||||
planDetails.PlanId = plan.Id;
|
||||
planDetails.Frequency = frequency;
|
||||
planDetails.FeaturesId = features.Id;
|
||||
planDetails.CreatedById = loggedInEmployee.Id;
|
||||
planDetails.CreateAt = DateTime.UtcNow;
|
||||
|
||||
_dbContext.SubscriptionPlanDetails.Add(planDetails);
|
||||
|
||||
// Prepare view model
|
||||
var VM = _mapper.Map<SubscriptionPlanVM>(planDetails);
|
||||
VM.PlanName = plan.PlanName;
|
||||
VM.Description = plan.Description;
|
||||
VM.Features = features;
|
||||
VM.Currency = currencyMaster;
|
||||
|
||||
try
|
||||
{
|
||||
await _dbContext.SaveChangesAsync();
|
||||
_logger.LogInfo("Subscription plan details for {PlanId}/{Frequency} saved to SQL.", plan.Id, frequency);
|
||||
}
|
||||
catch (DbUpdateException dbEx)
|
||||
{
|
||||
_logger.LogError(dbEx, "Database exception occurred while saving plan details for {PlanId}/{Frequency}.", plan.Id, frequency);
|
||||
return ApiResponse<SubscriptionPlanVM>.ErrorResponse("Internal error occurred", ExceptionMapper(dbEx), 500);
|
||||
}
|
||||
|
||||
return ApiResponse<SubscriptionPlanVM>.SuccessResponse(VM, "Success", 200);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -50,5 +50,13 @@
|
||||
"SerilogDatabaseUrl": "mongodb://localhost:27017/DotNetLogs",
|
||||
"ConnectionString": "mongodb://devuser:DevPass123@147.93.98.152:27017/MarcoBMSCacheLocalDev?authSource=admin&replicaSet=rs01",
|
||||
"ModificationConnectionString": "mongodb://devuser:DevPass123@147.93.98.152:27017/MarcoBMSLocalDev?authSource=admin&replicaSet=rs01&directConnection=true"
|
||||
},
|
||||
"Razorpay": {
|
||||
"Key": "rzp_test_RXCzgEcXucbuAi",
|
||||
"Secret": "YNAVBXxRsDg8Oat4M1C3m09W"
|
||||
},
|
||||
"Encryption": {
|
||||
"PaymentKey": "+V47lEWiolUZOUZcHq/3M6SEd3kPraGJpnJ+K5ni0Oo=",
|
||||
"CollectionKey": "9bVvYrbL1uB+v6TjWRsJ8N8VFI8rE7e7hVhVSKg3JZU="
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user