Document_Manager #129

Merged
ashutosh.nehete merged 83 commits from Document_Manager into main 2025-09-11 04:12:01 +00:00
97 changed files with 32680 additions and 384 deletions
Showing only changes of commit 194764c9d6 - Show all commits

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,37 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Marco.Pms.DataAccess.Migrations
{
/// <inheritdoc />
public partial class Added_New_Parameter_In_Tenant_Table : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "OfficeNumber",
table: "Tenants",
type: "longtext",
nullable: true)
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.UpdateData(
table: "Tenants",
keyColumn: "Id",
keyValue: new Guid("b3466e83-7e11-464c-b93a-daf047838b26"),
column: "OfficeNumber",
value: null);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "OfficeNumber",
table: "Tenants");
}
}
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,28 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Marco.Pms.DataAccess.Migrations
{
/// <inheritdoc />
public partial class Fixed_Spelling_Mistake_In_Tenant_Table : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.RenameColumn(
name: "OragnizationSize",
table: "Tenants",
newName: "OrganizationSize");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.RenameColumn(
name: "OrganizationSize",
table: "Tenants",
newName: "OragnizationSize");
}
}
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,207 @@
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_Subscription_Related_Tables : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "SubscriptionStatus",
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")
},
constraints: table =>
{
table.PrimaryKey("PK_SubscriptionStatus", x => x.Id);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateTable(
name: "SubscriptionPlans",
columns: table => new
{
Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
PlanName = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
Description = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
PriceQuarterly = table.Column<double>(type: "double", nullable: false),
PriceMonthly = table.Column<double>(type: "double", nullable: false),
PriceHalfMonthly = table.Column<double>(type: "double", nullable: false),
PriceYearly = table.Column<double>(type: "double", nullable: false),
TrialDays = table.Column<int>(type: "int", nullable: false),
MaxUser = table.Column<double>(type: "double", nullable: false),
MaxStorage = table.Column<double>(type: "double", nullable: false),
FeaturesId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
CreateAt = table.Column<DateTime>(type: "datetime(6)", nullable: false),
UpdateAt = table.Column<DateTime>(type: "datetime(6)", nullable: true),
CurrencyId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
CreatedById = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
UpdatedById = table.Column<Guid>(type: "char(36)", nullable: true, collation: "ascii_general_ci"),
IsActive = table.Column<bool>(type: "tinyint(1)", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_SubscriptionPlans", x => x.Id);
table.ForeignKey(
name: "FK_SubscriptionPlans_CurrencyMaster_CurrencyId",
column: x => x.CurrencyId,
principalTable: "CurrencyMaster",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_SubscriptionPlans_Employees_CreatedById",
column: x => x.CreatedById,
principalTable: "Employees",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_SubscriptionPlans_Employees_UpdatedById",
column: x => x.UpdatedById,
principalTable: "Employees",
principalColumn: "Id");
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateTable(
name: "TenantSubscriptions",
columns: table => new
{
Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
PlanId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
StartDate = table.Column<DateTime>(type: "datetime(6)", nullable: false),
EndDate = table.Column<DateTime>(type: "datetime(6)", nullable: false),
IsTrial = table.Column<bool>(type: "tinyint(1)", nullable: false),
StatusId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
CurrencyId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
NextBillingDate = table.Column<DateTime>(type: "datetime(6)", nullable: false),
CancellationDate = table.Column<DateTime>(type: "datetime(6)", nullable: true),
AutoRemew = table.Column<bool>(type: "tinyint(1)", nullable: false),
CreatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false),
UpdateAt = table.Column<DateTime>(type: "datetime(6)", nullable: true),
CreatedById = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
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_TenantSubscriptions", x => x.Id);
table.ForeignKey(
name: "FK_TenantSubscriptions_CurrencyMaster_CurrencyId",
column: x => x.CurrencyId,
principalTable: "CurrencyMaster",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_TenantSubscriptions_Employees_CreatedById",
column: x => x.CreatedById,
principalTable: "Employees",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_TenantSubscriptions_Employees_UpdatedById",
column: x => x.UpdatedById,
principalTable: "Employees",
principalColumn: "Id");
table.ForeignKey(
name: "FK_TenantSubscriptions_SubscriptionPlans_PlanId",
column: x => x.PlanId,
principalTable: "SubscriptionPlans",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_TenantSubscriptions_SubscriptionStatus_StatusId",
column: x => x.StatusId,
principalTable: "SubscriptionStatus",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_TenantSubscriptions_Tenants_TenantId",
column: x => x.TenantId,
principalTable: "Tenants",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.InsertData(
table: "SubscriptionStatus",
columns: new[] { "Id", "Name" },
values: new object[,]
{
{ new Guid("1c0e422e-01b6-412f-b72a-1db004cc8a7f"), "Suspended" },
{ new Guid("4ed487b1-af22-4e25-aecd-b63fd850cf2d"), "InActive" },
{ new Guid("cd3a68ea-41fd-42f0-bd0c-c871c7337727"), "Active" }
});
migrationBuilder.CreateIndex(
name: "IX_SubscriptionPlans_CreatedById",
table: "SubscriptionPlans",
column: "CreatedById");
migrationBuilder.CreateIndex(
name: "IX_SubscriptionPlans_CurrencyId",
table: "SubscriptionPlans",
column: "CurrencyId");
migrationBuilder.CreateIndex(
name: "IX_SubscriptionPlans_UpdatedById",
table: "SubscriptionPlans",
column: "UpdatedById");
migrationBuilder.CreateIndex(
name: "IX_TenantSubscriptions_CreatedById",
table: "TenantSubscriptions",
column: "CreatedById");
migrationBuilder.CreateIndex(
name: "IX_TenantSubscriptions_CurrencyId",
table: "TenantSubscriptions",
column: "CurrencyId");
migrationBuilder.CreateIndex(
name: "IX_TenantSubscriptions_PlanId",
table: "TenantSubscriptions",
column: "PlanId");
migrationBuilder.CreateIndex(
name: "IX_TenantSubscriptions_StatusId",
table: "TenantSubscriptions",
column: "StatusId");
migrationBuilder.CreateIndex(
name: "IX_TenantSubscriptions_TenantId",
table: "TenantSubscriptions",
column: "TenantId");
migrationBuilder.CreateIndex(
name: "IX_TenantSubscriptions_UpdatedById",
table: "TenantSubscriptions",
column: "UpdatedById");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "TenantSubscriptions");
migrationBuilder.DropTable(
name: "SubscriptionPlans");
migrationBuilder.DropTable(
name: "SubscriptionStatus");
}
}
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,28 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Marco.Pms.DataAccess.Migrations
{
/// <inheritdoc />
public partial class Corrected_Typo_In_Subscription_Table : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.RenameColumn(
name: "AutoRemew",
table: "TenantSubscriptions",
newName: "AutoRenew");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.RenameColumn(
name: "AutoRenew",
table: "TenantSubscriptions",
newName: "AutoRemew");
}
}
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,28 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Marco.Pms.DataAccess.Migrations
{
/// <inheritdoc />
public partial class Corrected_Typo_In_SubscriptionPlan_Table : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.RenameColumn(
name: "PriceHalfMonthly",
table: "SubscriptionPlans",
newName: "PriceHalfYearly");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.RenameColumn(
name: "PriceHalfYearly",
table: "SubscriptionPlans",
newName: "PriceHalfMonthly");
}
}
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,411 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Marco.Pms.DataAccess.Migrations
{
/// <inheritdoc />
public partial class Seprated_SubscriptionPlan_And_SubscriptionPlanDetails : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_StatusMasters_Tenants_TenantId",
table: "StatusMasters");
migrationBuilder.DropForeignKey(
name: "FK_SubscriptionPlans_CurrencyMaster_CurrencyId",
table: "SubscriptionPlans");
migrationBuilder.DropForeignKey(
name: "FK_SubscriptionPlans_Employees_CreatedById",
table: "SubscriptionPlans");
migrationBuilder.DropForeignKey(
name: "FK_SubscriptionPlans_Employees_UpdatedById",
table: "SubscriptionPlans");
migrationBuilder.DropForeignKey(
name: "FK_TenantSubscriptions_SubscriptionPlans_PlanId",
table: "TenantSubscriptions");
migrationBuilder.DropIndex(
name: "IX_SubscriptionPlans_CreatedById",
table: "SubscriptionPlans");
migrationBuilder.DropIndex(
name: "IX_SubscriptionPlans_CurrencyId",
table: "SubscriptionPlans");
migrationBuilder.DropIndex(
name: "IX_SubscriptionPlans_UpdatedById",
table: "SubscriptionPlans");
migrationBuilder.DropIndex(
name: "IX_StatusMasters_TenantId",
table: "StatusMasters");
migrationBuilder.DropColumn(
name: "CreateAt",
table: "SubscriptionPlans");
migrationBuilder.DropColumn(
name: "CreatedById",
table: "SubscriptionPlans");
migrationBuilder.DropColumn(
name: "CurrencyId",
table: "SubscriptionPlans");
migrationBuilder.DropColumn(
name: "FeaturesId",
table: "SubscriptionPlans");
migrationBuilder.DropColumn(
name: "MaxStorage",
table: "SubscriptionPlans");
migrationBuilder.DropColumn(
name: "MaxUser",
table: "SubscriptionPlans");
migrationBuilder.DropColumn(
name: "PriceHalfYearly",
table: "SubscriptionPlans");
migrationBuilder.DropColumn(
name: "PriceMonthly",
table: "SubscriptionPlans");
migrationBuilder.DropColumn(
name: "PriceQuarterly",
table: "SubscriptionPlans");
migrationBuilder.DropColumn(
name: "PriceYearly",
table: "SubscriptionPlans");
migrationBuilder.DropColumn(
name: "TrialDays",
table: "SubscriptionPlans");
migrationBuilder.DropColumn(
name: "UpdateAt",
table: "SubscriptionPlans");
migrationBuilder.DropColumn(
name: "UpdatedById",
table: "SubscriptionPlans");
migrationBuilder.DropColumn(
name: "TenantId",
table: "StatusMasters");
migrationBuilder.AddColumn<bool>(
name: "IsCancelled",
table: "TenantSubscriptions",
type: "tinyint(1)",
nullable: false,
defaultValue: false);
migrationBuilder.AddColumn<double>(
name: "MaxUsers",
table: "TenantSubscriptions",
type: "double",
nullable: false,
defaultValue: 0.0);
migrationBuilder.CreateTable(
name: "SubscriptionPlanDetails",
columns: table => new
{
Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
Price = table.Column<double>(type: "double", nullable: false),
Frequency = table.Column<int>(type: "int", nullable: false),
TrialDays = table.Column<int>(type: "int", nullable: false),
MaxUser = table.Column<double>(type: "double", nullable: false),
MaxStorage = table.Column<double>(type: "double", nullable: false),
FeaturesId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
CreateAt = table.Column<DateTime>(type: "datetime(6)", nullable: false),
UpdateAt = table.Column<DateTime>(type: "datetime(6)", nullable: true),
PlanId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
CurrencyId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
CreatedById = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
UpdatedById = table.Column<Guid>(type: "char(36)", nullable: true, collation: "ascii_general_ci"),
IsActive = table.Column<bool>(type: "tinyint(1)", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_SubscriptionPlanDetails", x => x.Id);
table.ForeignKey(
name: "FK_SubscriptionPlanDetails_CurrencyMaster_CurrencyId",
column: x => x.CurrencyId,
principalTable: "CurrencyMaster",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_SubscriptionPlanDetails_Employees_CreatedById",
column: x => x.CreatedById,
principalTable: "Employees",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_SubscriptionPlanDetails_Employees_UpdatedById",
column: x => x.UpdatedById,
principalTable: "Employees",
principalColumn: "Id");
table.ForeignKey(
name: "FK_SubscriptionPlanDetails_SubscriptionPlans_PlanId",
column: x => x.PlanId,
principalTable: "SubscriptionPlans",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateIndex(
name: "IX_SubscriptionPlanDetails_CreatedById",
table: "SubscriptionPlanDetails",
column: "CreatedById");
migrationBuilder.CreateIndex(
name: "IX_SubscriptionPlanDetails_CurrencyId",
table: "SubscriptionPlanDetails",
column: "CurrencyId");
migrationBuilder.CreateIndex(
name: "IX_SubscriptionPlanDetails_PlanId",
table: "SubscriptionPlanDetails",
column: "PlanId");
migrationBuilder.CreateIndex(
name: "IX_SubscriptionPlanDetails_UpdatedById",
table: "SubscriptionPlanDetails",
column: "UpdatedById");
migrationBuilder.AddForeignKey(
name: "FK_TenantSubscriptions_SubscriptionPlanDetails_PlanId",
table: "TenantSubscriptions",
column: "PlanId",
principalTable: "SubscriptionPlanDetails",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_TenantSubscriptions_SubscriptionPlanDetails_PlanId",
table: "TenantSubscriptions");
migrationBuilder.DropTable(
name: "SubscriptionPlanDetails");
migrationBuilder.DropColumn(
name: "IsCancelled",
table: "TenantSubscriptions");
migrationBuilder.DropColumn(
name: "MaxUsers",
table: "TenantSubscriptions");
migrationBuilder.AddColumn<DateTime>(
name: "CreateAt",
table: "SubscriptionPlans",
type: "datetime(6)",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
migrationBuilder.AddColumn<Guid>(
name: "CreatedById",
table: "SubscriptionPlans",
type: "char(36)",
nullable: false,
defaultValue: new Guid("00000000-0000-0000-0000-000000000000"),
collation: "ascii_general_ci");
migrationBuilder.AddColumn<Guid>(
name: "CurrencyId",
table: "SubscriptionPlans",
type: "char(36)",
nullable: false,
defaultValue: new Guid("00000000-0000-0000-0000-000000000000"),
collation: "ascii_general_ci");
migrationBuilder.AddColumn<Guid>(
name: "FeaturesId",
table: "SubscriptionPlans",
type: "char(36)",
nullable: false,
defaultValue: new Guid("00000000-0000-0000-0000-000000000000"),
collation: "ascii_general_ci");
migrationBuilder.AddColumn<double>(
name: "MaxStorage",
table: "SubscriptionPlans",
type: "double",
nullable: false,
defaultValue: 0.0);
migrationBuilder.AddColumn<double>(
name: "MaxUser",
table: "SubscriptionPlans",
type: "double",
nullable: false,
defaultValue: 0.0);
migrationBuilder.AddColumn<double>(
name: "PriceHalfYearly",
table: "SubscriptionPlans",
type: "double",
nullable: false,
defaultValue: 0.0);
migrationBuilder.AddColumn<double>(
name: "PriceMonthly",
table: "SubscriptionPlans",
type: "double",
nullable: false,
defaultValue: 0.0);
migrationBuilder.AddColumn<double>(
name: "PriceQuarterly",
table: "SubscriptionPlans",
type: "double",
nullable: false,
defaultValue: 0.0);
migrationBuilder.AddColumn<double>(
name: "PriceYearly",
table: "SubscriptionPlans",
type: "double",
nullable: false,
defaultValue: 0.0);
migrationBuilder.AddColumn<int>(
name: "TrialDays",
table: "SubscriptionPlans",
type: "int",
nullable: false,
defaultValue: 0);
migrationBuilder.AddColumn<DateTime>(
name: "UpdateAt",
table: "SubscriptionPlans",
type: "datetime(6)",
nullable: true);
migrationBuilder.AddColumn<Guid>(
name: "UpdatedById",
table: "SubscriptionPlans",
type: "char(36)",
nullable: true,
collation: "ascii_general_ci");
migrationBuilder.AddColumn<Guid>(
name: "TenantId",
table: "StatusMasters",
type: "char(36)",
nullable: false,
defaultValue: new Guid("00000000-0000-0000-0000-000000000000"),
collation: "ascii_general_ci");
migrationBuilder.UpdateData(
table: "StatusMasters",
keyColumn: "Id",
keyValue: new Guid("33deaef9-9af1-4f2a-b443-681ea0d04f81"),
column: "TenantId",
value: new Guid("b3466e83-7e11-464c-b93a-daf047838b26"));
migrationBuilder.UpdateData(
table: "StatusMasters",
keyColumn: "Id",
keyValue: new Guid("603e994b-a27f-4e5d-a251-f3d69b0498ba"),
column: "TenantId",
value: new Guid("b3466e83-7e11-464c-b93a-daf047838b26"));
migrationBuilder.UpdateData(
table: "StatusMasters",
keyColumn: "Id",
keyValue: new Guid("b74da4c2-d07e-46f2-9919-e75e49b12731"),
column: "TenantId",
value: new Guid("b3466e83-7e11-464c-b93a-daf047838b26"));
migrationBuilder.UpdateData(
table: "StatusMasters",
keyColumn: "Id",
keyValue: new Guid("cdad86aa-8a56-4ff4-b633-9c629057dfef"),
column: "TenantId",
value: new Guid("b3466e83-7e11-464c-b93a-daf047838b26"));
migrationBuilder.UpdateData(
table: "StatusMasters",
keyColumn: "Id",
keyValue: new Guid("ef1c356e-0fe0-42df-a5d3-8daee355492d"),
column: "TenantId",
value: new Guid("b3466e83-7e11-464c-b93a-daf047838b26"));
migrationBuilder.CreateIndex(
name: "IX_SubscriptionPlans_CreatedById",
table: "SubscriptionPlans",
column: "CreatedById");
migrationBuilder.CreateIndex(
name: "IX_SubscriptionPlans_CurrencyId",
table: "SubscriptionPlans",
column: "CurrencyId");
migrationBuilder.CreateIndex(
name: "IX_SubscriptionPlans_UpdatedById",
table: "SubscriptionPlans",
column: "UpdatedById");
migrationBuilder.CreateIndex(
name: "IX_StatusMasters_TenantId",
table: "StatusMasters",
column: "TenantId");
migrationBuilder.AddForeignKey(
name: "FK_StatusMasters_Tenants_TenantId",
table: "StatusMasters",
column: "TenantId",
principalTable: "Tenants",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "FK_SubscriptionPlans_CurrencyMaster_CurrencyId",
table: "SubscriptionPlans",
column: "CurrencyId",
principalTable: "CurrencyMaster",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "FK_SubscriptionPlans_Employees_CreatedById",
table: "SubscriptionPlans",
column: "CreatedById",
principalTable: "Employees",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "FK_SubscriptionPlans_Employees_UpdatedById",
table: "SubscriptionPlans",
column: "UpdatedById",
principalTable: "Employees",
principalColumn: "Id");
migrationBuilder.AddForeignKey(
name: "FK_TenantSubscriptions_SubscriptionPlans_PlanId",
table: "TenantSubscriptions",
column: "PlanId",
principalTable: "SubscriptionPlans",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
}
}
}

File diff suppressed because one or more lines are too long

View File

@ -1,6 +1,7 @@
using Marco.Pms.Model.MongoDBModels.Employees; using Marco.Pms.Model.MongoDBModels.Employees;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using MongoDB.Driver; using MongoDB.Driver;
namespace Marco.Pms.Helpers.CacheHelper namespace Marco.Pms.Helpers.CacheHelper
@ -8,8 +9,11 @@ namespace Marco.Pms.Helpers.CacheHelper
public class EmployeeCache public class EmployeeCache
{ {
private readonly IMongoCollection<EmployeePermissionMongoDB> _collection; private readonly IMongoCollection<EmployeePermissionMongoDB> _collection;
public EmployeeCache(IConfiguration configuration) private readonly ILogger<EmployeeCache> _logger;
public EmployeeCache(IConfiguration configuration, ILogger<EmployeeCache> logger)
{ {
_logger = logger;
var connectionString = configuration["MongoDB:ConnectionString"]; var connectionString = configuration["MongoDB:ConnectionString"];
var mongoUrl = new MongoUrl(connectionString); var mongoUrl = new MongoUrl(connectionString);
var client = new MongoClient(mongoUrl); // Your MongoDB connection string var client = new MongoClient(mongoUrl); // Your MongoDB connection string
@ -185,6 +189,25 @@ namespace Marco.Pms.Helpers.CacheHelper
return true; return true;
} }
public async Task<bool> ClearAllEmployeesFromCacheByEmployeeIds(List<string> employeeIds)
{
try
{
var filter = Builders<EmployeePermissionMongoDB>.Filter.In(x => x.Id, employeeIds);
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. // A private method to handle the one-time setup of the collection's indexes.
private async Task InitializeCollectionAsync() private async Task InitializeCollectionAsync()

View File

@ -0,0 +1,53 @@
using Marco.Pms.Model.TenantModels.MongoDBModel;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using MongoDB.Driver;
namespace Marco.Pms.Helpers.Utility
{
public class FeatureDetailsHelper
{
private readonly IMongoCollection<FeatureDetails> _collection;
private readonly ILogger<FeatureDetailsHelper> _logger;
public FeatureDetailsHelper(IConfiguration configuration, ILogger<FeatureDetailsHelper> logger)
{
_logger = logger;
var connectionString = configuration["MongoDB:ModificationConnectionString"];
var mongoUrl = new MongoUrl(connectionString);
var client = new MongoClient(mongoUrl); // Your MongoDB connection string
var mongoDB = client.GetDatabase(mongoUrl.DatabaseName); // Your MongoDB Database name
_collection = mongoDB.GetCollection<FeatureDetails>("FeatureDetails");
}
public async Task<FeatureDetails?> GetFeatureDetails(Guid Id)
{
try
{
var filter = Builders<FeatureDetails>.Filter.Eq(e => e.Id, Id);
var result = await _collection
.Find(filter)
.FirstOrDefaultAsync();
return result;
}
catch (Exception ex)
{
_logger.LogError(ex, "Exception occured while fetchig features for subscription plan");
return null;
}
}
public async Task<bool> AddFeatureDetails(FeatureDetails featureDetails)
{
try
{
await _collection.InsertOneAsync(featureDetails);
return true;
}
catch (Exception ex)
{
_logger.LogError(ex, "Exception occured while fetchig features for subscription plan");
return false;
}
}
}
}

View File

@ -0,0 +1,224 @@
using Marco.Pms.Model.AppMenu;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using MongoDB.Bson;
using MongoDB.Driver;
namespace Marco.Pms.CacheHelper
{
public class SidebarMenuHelper
{
private readonly IMongoCollection<MenuSection> _collection;
private readonly ILogger<SidebarMenuHelper> _logger;
public SidebarMenuHelper(IConfiguration configuration, ILogger<SidebarMenuHelper> logger)
{
_logger = logger;
var connectionString = configuration["MongoDB:ModificationConnectionString"];
var mongoUrl = new MongoUrl(connectionString);
var client = new MongoClient(mongoUrl);
var database = client.GetDatabase(mongoUrl.DatabaseName);
_collection = database.GetCollection<MenuSection>("Menus");
}
public async Task<MenuSection?> CreateMenuSectionAsync(MenuSection section)
{
try
{
await _collection.InsertOneAsync(section);
return section;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error occurred while adding MenuSection.");
return null;
}
}
public async Task<MenuSection?> UpdateMenuSectionAsync(Guid sectionId, MenuSection updatedSection)
{
try
{
var filter = Builders<MenuSection>.Filter.Eq(s => s.Id, sectionId);
var update = Builders<MenuSection>.Update
.Set(s => s.Header, updatedSection.Header)
.Set(s => s.Title, updatedSection.Title)
.Set(s => s.Items, updatedSection.Items);
var result = await _collection.UpdateOneAsync(filter, update);
if (result.ModifiedCount > 0)
{
return await _collection.Find(s => s.Id == sectionId).FirstOrDefaultAsync();
}
return null;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error updating MenuSection.");
return null;
}
}
public async Task<MenuSection?> AddMenuItemAsync(Guid sectionId, MenuItem newItem)
{
try
{
newItem.Id = Guid.NewGuid();
var filter = Builders<MenuSection>.Filter.Eq(s => s.Id, sectionId);
var update = Builders<MenuSection>.Update.Push(s => s.Items, newItem);
var result = await _collection.UpdateOneAsync(filter, update);
if (result.ModifiedCount > 0)
{
return await _collection.Find(s => s.Id == sectionId).FirstOrDefaultAsync();
}
return null;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error adding menu item.");
return null;
}
}
public async Task<MenuItem?> UpdateMenuItemAsync(Guid sectionId, Guid itemId, MenuItem updatedItem)
{
try
{
var filter = Builders<MenuSection>.Filter.And(
Builders<MenuSection>.Filter.Eq(s => s.Id, sectionId),
Builders<MenuSection>.Filter.ElemMatch(s => s.Items, i => i.Id == itemId)
);
var update = Builders<MenuSection>.Update
.Set("Items.$.Text", updatedItem.Text)
.Set("Items.$.Icon", updatedItem.Icon)
.Set("Items.$.Available", updatedItem.Available)
.Set("Items.$.Link", updatedItem.Link)
.Set("Items.$.PermissionIds", updatedItem.PermissionIds);
var result = await _collection.UpdateOneAsync(filter, update);
if (result.ModifiedCount > 0)
{
// Re-fetch section and return the updated item
var section = await _collection.Find(s => s.Id == sectionId).FirstOrDefaultAsync();
return section?.Items.FirstOrDefault(i => i.Id == itemId);
}
return null;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error updating MenuItem.");
return null;
}
}
public async Task<MenuSection?> AddSubMenuItemAsync(Guid sectionId, Guid itemId, SubMenuItem newSubItem)
{
try
{
newSubItem.Id = Guid.NewGuid();
// Match the MenuSection and the specific MenuItem inside it
var filter = Builders<MenuSection>.Filter.And(
Builders<MenuSection>.Filter.Eq(s => s.Id, sectionId),
Builders<MenuSection>.Filter.ElemMatch(s => s.Items, i => i.Id == itemId)
);
// Use positional operator `$` to target matched MenuItem and push into its Submenu
var update = Builders<MenuSection>.Update.Push("Items.$.Submenu", newSubItem);
var result = await _collection.UpdateOneAsync(filter, update);
if (result.ModifiedCount > 0)
{
return await _collection.Find(s => s.Id == sectionId).FirstOrDefaultAsync();
}
return null;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error adding submenu item.");
return null;
}
}
public async Task<SubMenuItem?> UpdateSubmenuItemAsync(Guid sectionId, Guid itemId, Guid subItemId, SubMenuItem updatedSub)
{
try
{
var filter = Builders<MenuSection>.Filter.Eq(s => s.Id, sectionId);
var arrayFilters = new List<ArrayFilterDefinition>
{
new BsonDocumentArrayFilterDefinition<BsonDocument>(
new BsonDocument("item._id", itemId.ToString())),
new BsonDocumentArrayFilterDefinition<BsonDocument>(
new BsonDocument("sub._id", subItemId.ToString()))
};
var update = Builders<MenuSection>.Update
.Set("Items.$[item].Submenu.$[sub].Text", updatedSub.Text)
.Set("Items.$[item].Submenu.$[sub].Available", updatedSub.Available)
.Set("Items.$[item].Submenu.$[sub].Link", updatedSub.Link)
.Set("Items.$[item].Submenu.$[sub].PermissionKeys", updatedSub.PermissionIds);
var options = new UpdateOptions { ArrayFilters = arrayFilters };
var result = await _collection.UpdateOneAsync(filter, update, options);
if (result.ModifiedCount == 0)
return null;
var updatedSection = await _collection.Find(x => x.Id == sectionId).FirstOrDefaultAsync();
var subItem = updatedSection?.Items
.FirstOrDefault(i => i.Id == itemId)?
.Submenu
.FirstOrDefault(s => s.Id == subItemId);
return subItem;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error updating SubMenuItem.");
return null;
}
}
public async Task<List<MenuSection>> GetAllMenuSectionsAsync(Guid tenantId)
{
var filter = Builders<MenuSection>.Filter.Eq(e => e.TenantId, tenantId);
var result = await _collection
.Find(filter)
.ToListAsync();
if (result.Any())
{
return result;
}
tenantId = Guid.Parse("b3466e83-7e11-464c-b93a-daf047838b26");
filter = Builders<MenuSection>.Filter.Eq(e => e.TenantId, tenantId);
result = await _collection
.Find(filter)
.ToListAsync();
return result;
}
}
}

View File

@ -0,0 +1,23 @@
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
namespace Marco.Pms.Model.AppMenu
{
public class MenuItem
{
[BsonId]
[BsonRepresentation(BsonType.String)]
public Guid Id { get; set; } = Guid.NewGuid();
public string? Text { get; set; }
public string? Icon { get; set; }
public bool Available { get; set; } = true;
public string? Link { get; set; }
// Changed from string → List<string>
public List<string> PermissionIds { get; set; } = new List<string>();
public List<SubMenuItem> Submenu { get; set; } = new List<SubMenuItem>();
}
}

View File

@ -0,0 +1,21 @@
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
namespace Marco.Pms.Model.AppMenu
{
public class MenuSection
{
[BsonId]
[BsonRepresentation(BsonType.String)]
public Guid Id { get; set; } = Guid.NewGuid();
public string? Header { get; set; }
public string? Title { get; set; }
public List<MenuItem> Items { get; set; } = new List<MenuItem>();
[BsonRepresentation(BsonType.String)]
public Guid TenantId { get; set; }
}
}

View File

@ -0,0 +1,20 @@
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
namespace Marco.Pms.Model.AppMenu
{
public class SubMenuItem
{
[BsonId]
[BsonRepresentation(BsonType.String)]
public Guid Id { get; set; } = Guid.NewGuid();
public string? Text { get; set; }
public bool Available { get; set; } = true;
public string Link { get; set; } = string.Empty;
// Changed from string → List<string>
public List<string> PermissionIds { get; set; } = new List<string>();
}
}

View File

@ -0,0 +1,16 @@
namespace Marco.Pms.Model.Dtos.AppMenu
{
public class CreateMenuItemDto
{
public required string Text { get; set; }
public required string Icon { get; set; }
public bool Available { get; set; } = true;
public required string Link { get; set; }
// Changed from string → List<string>
public List<string> PermissionIds { get; set; } = new List<string>();
public List<CreateSubMenuItemDto> Submenu { get; set; } = new List<CreateSubMenuItemDto>();
}
}

View File

@ -0,0 +1,9 @@
namespace Marco.Pms.Model.Dtos.AppMenu
{
public class CreateMenuSectionDto
{
public required string Header { get; set; }
public required string Title { get; set; }
public List<CreateMenuItemDto> Items { get; set; } = new List<CreateMenuItemDto>();
}
}

View File

@ -0,0 +1,13 @@
namespace Marco.Pms.Model.Dtos.AppMenu
{
public class CreateSubMenuItemDto
{
public required string Text { get; set; }
public bool Available { get; set; } = true;
public required string Link { get; set; } = string.Empty;
// Changed from string → List<string>
public List<string> PermissionIds { get; set; } = new List<string>();
}
}

View File

@ -0,0 +1,16 @@
namespace Marco.Pms.Model.Dtos.AppMenu
{
public class UpdateMenuItemDto
{
public required Guid Id { get; set; }
public required string Text { get; set; }
public required string Icon { get; set; }
public bool Available { get; set; } = true;
public required string Link { get; set; }
// Changed from string → List<string>
public List<string> PermissionIds { get; set; } = new List<string>();
}
}

View File

@ -0,0 +1,9 @@
namespace Marco.Pms.Model.Dtos.AppMenu
{
public class UpdateMenuSectionDto
{
public required Guid Id { get; set; }
public required string Header { get; set; }
public required string Title { get; set; }
}
}

View File

@ -0,0 +1,15 @@
namespace Marco.Pms.Model.Dtos.AppMenu
{
public class UpdateSubMenuItemDto
{
public Guid Id { get; set; }
public string? Text { get; set; }
public bool Available { get; set; } = true;
public string Link { get; set; } = string.Empty;
// Changed from string → List<string>
public List<string> PermissionIds { get; set; } = new List<string>();
}
}

View File

@ -0,0 +1,12 @@
namespace Marco.Pms.Model.Dtos.Tenant
{
public class AddSubscriptionDto
{
public Guid TenantId { get; set; }
public Guid PlanId { get; set; }
public Guid CurrencyId { get; set; }
public double MaxUsers { get; set; }
public bool IsTrial { get; set; } = false;
public bool AutoRenew { get; set; } = true;
}
}

View File

@ -0,0 +1,12 @@
namespace Marco.Pms.Model.Dtos.Tenant
{
public class AttendanceDetailsDto
{
public List<Guid>? FeatureId { get; set; }
public string Name { get; set; } = "Attendance Management";
public bool Enabled { get; set; } = false;
public bool ManualEntry { get; set; } = true;
public bool LocationTracking { get; set; } = true;
public bool ShiftManagement { get; set; } = false;
}
}

View File

@ -0,0 +1,21 @@
namespace Marco.Pms.Model.Dtos.Tenant
{
public class CreateTenantDto
{
public required string FirstName { get; set; }
public required string LastName { get; set; }
public required string Email { get; set; }
public string? Description { get; set; }
public string? DomainName { get; set; }
public required string BillingAddress { get; set; }
public string? TaxId { get; set; }
public string? logoImage { get; set; }
public required string OrganizationName { get; set; }
public string? OfficeNumber { get; set; }
public required string ContactNumber { get; set; }
public required DateTime OnBoardingDate { get; set; }
public required string OrganizationSize { get; set; }
public required Guid IndustryId { get; set; }
public required string Reference { get; set; }
}
}

View File

@ -0,0 +1,11 @@
namespace Marco.Pms.Model.Dtos.Tenant
{
public class DirectoryDetailsDto
{
public List<Guid>? FeatureId { get; set; }
public string Name { get; set; } = "Directory Management";
public bool Enabled { get; set; } = false;
public int BucketLimit { get; set; } = 25;
public bool OrganizationChart { get; set; } = false;
}
}

View File

@ -0,0 +1,9 @@
namespace Marco.Pms.Model.Dtos.Tenant
{
public class ExpenseModuleDetailsDto
{
public List<Guid>? FeatureId { get; set; }
public string Name { get; set; } = "Expense Management";
public bool Enabled { get; set; } = false;
}
}

View File

@ -0,0 +1,10 @@
namespace Marco.Pms.Model.Dtos.Tenant
{
public class FeatureDetailsDto
{
public ModulesDetailsDto? Modules { get; set; }
public ReportDetailsDto? Reports { get; set; }
public SupportDetailsDto? Supports { get; set; }
public List<SubscriptionCheckListDto> SubscriptionCheckList { get; set; } = new List<SubscriptionCheckListDto>();
}
}

View File

@ -0,0 +1,10 @@
namespace Marco.Pms.Model.Dtos.Tenant
{
public class ModulesDetailsDto
{
public ProjectManagementDetailsDto? ProjectManagement { get; set; }
public AttendanceDetailsDto? Attendance { get; set; }
public DirectoryDetailsDto? Directory { get; set; }
public ExpenseModuleDetailsDto? Expense { get; set; }
}
}

View File

@ -0,0 +1,13 @@
namespace Marco.Pms.Model.Dtos.Tenant
{
public class ProjectManagementDetailsDto
{
public List<Guid>? FeatureId { get; set; }
public string Name { get; set; } = "Project Management";
public bool Enabled { get; set; } = false;
public int MaxProject { get; set; } = 10;
public double MaxTaskPerProject { get; set; } = 100000;
public bool GanttChart { get; set; } = false;
public bool ResourceAllocation { get; set; } = false;
}
}

View File

@ -0,0 +1,9 @@
namespace Marco.Pms.Model.Dtos.Tenant
{
public class ReportDetailsDto
{
public bool BasicReports { get; set; } = true;
public bool CustomReports { get; set; } = false;
public List<string> ExportData { get; set; } = new List<string>();
}
}

View File

@ -0,0 +1,8 @@
namespace Marco.Pms.Model.Dtos.Tenant
{
public class SubscriptionCheckListDto
{
public string Name { get; set; } = string.Empty;
public bool IsActive { get; set; } = true;
}
}

View File

@ -0,0 +1,13 @@
namespace Marco.Pms.Model.Dtos.Tenant
{
public class SubscriptionPlanDetailsDto
{
public Guid? Id { get; set; }
public double Price { get; set; }
public required int TrialDays { get; set; }
public required double MaxUser { get; set; }
public double MaxStorage { get; set; }
public required FeatureDetailsDto Features { get; set; }
public Guid CurrencyId { get; set; }
}
}

View File

@ -0,0 +1,13 @@
namespace Marco.Pms.Model.Dtos.Tenant
{
public class SubscriptionPlanDto
{
public Guid? Id { get; set; }
public required string PlanName { get; set; }
public required string Description { get; set; }
public SubscriptionPlanDetailsDto? MonthlyPlan { get; set; }
public SubscriptionPlanDetailsDto? QuarterlyPlan { get; set; }
public SubscriptionPlanDetailsDto? HalfYearlyPlan { get; set; }
public SubscriptionPlanDetailsDto? YearlyPlan { get; set; }
}
}

View File

@ -0,0 +1,9 @@
namespace Marco.Pms.Model.Dtos.Tenant
{
public class SupportDetailsDto
{
public bool EmailSupport { get; set; } = true;
public bool PhoneSupport { get; set; } = false;
public bool PrioritySupport { get; set; } = false;
}
}

View File

@ -0,0 +1,10 @@
namespace Marco.Pms.Model.Dtos.Tenant
{
public class UpdateSubscriptionDto
{
public Guid TenantId { get; set; }
public Guid PlanId { get; set; }
public Guid CurrencyId { get; set; }
public double? MaxUsers { get; set; }
}
}

View File

@ -0,0 +1,19 @@
namespace Marco.Pms.Model.Dtos.Tenant
{
public class UpdateTenantDto
{
public Guid Id { get; set; }
public required string FirstName { get; set; }
public required string LastName { get; set; }
public string? Description { get; set; }
public string? DomainName { get; set; }
public required string BillingAddress { get; set; }
public string? TaxId { get; set; }
public string? logoImage { get; set; }
public string? OfficeNumber { get; set; }
public required string ContactNumber { get; set; }
public required string OrganizationSize { get; set; }
public required Guid IndustryId { get; set; }
public required string Reference { get; set; }
}
}

View File

@ -1,25 +0,0 @@
using System.ComponentModel.DataAnnotations.Schema;
using Marco.Pms.Model.Master;
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
namespace Marco.Pms.Model.Entitlements
{
public class Tenant
{
public Guid Id { get; set; }
public string? Name { get; set; }
public string? Description { get; set; }
public string? DomainName { get; set; }
public string? ContactName { get; set; }
public string? ContactNumber { get; set; }
public DateTime OnBoardingDate { get; set; }
public string? OragnizationSize { get; set; }
public Guid? IndustryId { get; set; }
[ForeignKey("IndustryId")]
[ValidateNever]
public Industry? Industry { get; set; }
public bool IsActive { get; set; } = true;
}
}

View File

@ -2,9 +2,14 @@
{ {
public static class PermissionsMaster public static class PermissionsMaster
{ {
public static readonly Guid ManageTenants = Guid.Parse("d032cb1a-3f30-462c-bef0-7ace73a71c0b");
public static readonly Guid ModifyTenant = Guid.Parse("00e20637-ce8d-4417-bec4-9b31b5e65092");
public static readonly Guid ViewTenant = Guid.Parse("647145c6-2108-4c98-aab4-178602236e55");
public static readonly Guid DirectoryAdmin = Guid.Parse("4286a13b-bb40-4879-8c6d-18e9e393beda"); public static readonly Guid DirectoryAdmin = Guid.Parse("4286a13b-bb40-4879-8c6d-18e9e393beda");
public static readonly Guid DirectoryManager = Guid.Parse("62668630-13ce-4f52-a0f0-db38af2230c5"); public static readonly Guid DirectoryManager = Guid.Parse("62668630-13ce-4f52-a0f0-db38af2230c5");
public static readonly Guid DirectoryUser = Guid.Parse("0f919170-92d4-4337-abd3-49b66fc871bb"); public static readonly Guid DirectoryUser = Guid.Parse("0f919170-92d4-4337-abd3-49b66fc871bb");
public static readonly Guid ViewProject = Guid.Parse("6ea44136-987e-44ba-9e5d-1cf8f5837ebc"); public static readonly Guid ViewProject = Guid.Parse("6ea44136-987e-44ba-9e5d-1cf8f5837ebc");
public static readonly Guid ManageProject = Guid.Parse("172fc9b6-755b-4f62-ab26-55c34a330614"); public static readonly Guid ManageProject = Guid.Parse("172fc9b6-755b-4f62-ab26-55c34a330614");
public static readonly Guid ManageTeam = Guid.Parse("b94802ce-0689-4643-9e1d-11c86950c35b"); public static readonly Guid ManageTeam = Guid.Parse("b94802ce-0689-4643-9e1d-11c86950c35b");
@ -14,15 +19,19 @@
public static readonly Guid AddAndEditTask = Guid.Parse("08752f33-3b29-4816-b76b-ea8a968ed3c5"); public static readonly Guid AddAndEditTask = Guid.Parse("08752f33-3b29-4816-b76b-ea8a968ed3c5");
public static readonly Guid AssignAndReportProgress = Guid.Parse("6a32379b-8b3f-49a6-8c48-4b7ac1b55dc2"); public static readonly Guid AssignAndReportProgress = Guid.Parse("6a32379b-8b3f-49a6-8c48-4b7ac1b55dc2");
public static readonly Guid ApproveTask = Guid.Parse("db4e40c5-2ba9-4b6d-b8a6-a16a250ff99c"); public static readonly Guid ApproveTask = Guid.Parse("db4e40c5-2ba9-4b6d-b8a6-a16a250ff99c");
public static readonly Guid ViewAllEmployees = Guid.Parse("60611762-7f8a-4fb5-b53f-b1139918796b"); public static readonly Guid ViewAllEmployees = Guid.Parse("60611762-7f8a-4fb5-b53f-b1139918796b");
public static readonly Guid ViewTeamMembers = Guid.Parse("b82d2b7e-0d52-45f3-997b-c008ea460e7f"); public static readonly Guid ViewTeamMembers = Guid.Parse("b82d2b7e-0d52-45f3-997b-c008ea460e7f");
public static readonly Guid AddAndEditEmployee = Guid.Parse("a97d366a-c2bb-448d-be93-402bd2324566"); public static readonly Guid AddAndEditEmployee = Guid.Parse("a97d366a-c2bb-448d-be93-402bd2324566");
public static readonly Guid AssignRoles = Guid.Parse("fbd213e0-0250-46f1-9f5f-4b2a1e6e76a3"); public static readonly Guid AssignRoles = Guid.Parse("fbd213e0-0250-46f1-9f5f-4b2a1e6e76a3");
public static readonly Guid TeamAttendance = Guid.Parse("915e6bff-65f6-4e3f-aea8-3fd217d3ea9e"); public static readonly Guid TeamAttendance = Guid.Parse("915e6bff-65f6-4e3f-aea8-3fd217d3ea9e");
public static readonly Guid RegularizeAttendance = Guid.Parse("57802c4a-00aa-4a1f-a048-fd2f70dd44b6"); public static readonly Guid RegularizeAttendance = Guid.Parse("57802c4a-00aa-4a1f-a048-fd2f70dd44b6");
public static readonly Guid SelfAttendance = Guid.Parse("ccb0589f-712b-43de-92ed-5b6088e7dc4e"); public static readonly Guid SelfAttendance = Guid.Parse("ccb0589f-712b-43de-92ed-5b6088e7dc4e");
public static readonly Guid ViewMasters = Guid.Parse("5ffbafe0-7ab0-48b1-bb50-c1bf76b65f9d"); public static readonly Guid ViewMasters = Guid.Parse("5ffbafe0-7ab0-48b1-bb50-c1bf76b65f9d");
public static readonly Guid ManageMasters = Guid.Parse("588a8824-f924-4955-82d8-fc51956cf323"); public static readonly Guid ManageMasters = Guid.Parse("588a8824-f924-4955-82d8-fc51956cf323");
public static readonly Guid ExpenseViewSelf = Guid.Parse("385be49f-8fde-440e-bdbc-3dffeb8dd116"); public static readonly Guid ExpenseViewSelf = Guid.Parse("385be49f-8fde-440e-bdbc-3dffeb8dd116");
public static readonly Guid ExpenseViewAll = Guid.Parse("01e06444-9ca7-4df4-b900-8c3fa051b92f"); public static readonly Guid ExpenseViewAll = Guid.Parse("01e06444-9ca7-4df4-b900-8c3fa051b92f");
public static readonly Guid ExpenseUpload = Guid.Parse("0f57885d-bcb2-4711-ac95-d841ace6d5a7"); public static readonly Guid ExpenseUpload = Guid.Parse("0f57885d-bcb2-4711-ac95-d841ace6d5a7");

View File

@ -35,8 +35,10 @@ namespace Marco.Pms.Model.Mapper
PhoneNumber = model.PhoneNumber, PhoneNumber = model.PhoneNumber,
Photo = base64String, Photo = base64String,
IsActive = model.IsActive, IsActive = model.IsActive,
IsRootUser = model.ApplicationUser?.IsRootUser ?? false,
IsSystem = model.IsSystem, IsSystem = model.IsSystem,
JoiningDate = model.JoiningDate JoiningDate = model.JoiningDate,
TenantId = model.TenantId
}; };
} }
public static BasicEmployeeVM ToBasicEmployeeVMFromEmployee(this Employee employee) public static BasicEmployeeVM ToBasicEmployeeVMFromEmployee(this Employee employee)

View File

@ -1,8 +1,6 @@
using Marco.Pms.Model.Utilities; namespace Marco.Pms.Model.Master
namespace Marco.Pms.Model.Master
{ {
public class StatusMaster : TenantRelation public class StatusMaster
{ {
public Guid Id { get; set; } public Guid Id { get; set; }
public string? Status { get; set; } public string? Status { get; set; }

View File

@ -0,0 +1,8 @@
namespace Marco.Pms.Model.Master
{
public class SubscriptionStatus
{
public Guid Id { get; set; }
public string Name { get; set; } = string.Empty;
}
}

View File

@ -0,0 +1,8 @@
namespace Marco.Pms.Model.Master
{
public class TenantStatus
{
public Guid Id { get; set; }
public string Name { get; set; } = string.Empty;
}
}

View File

@ -0,0 +1,20 @@
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
namespace Marco.Pms.Model.TenantModels.MongoDBModel
{
public class AttendanceDetails
{
[BsonId]
[BsonRepresentation(BsonType.String)]
public Guid Id { get; set; } = Guid.NewGuid();
public string? Name { get; set; }
[BsonRepresentation(BsonType.String)]
public List<Guid> FeatureId { get; set; } = new List<Guid>();
public bool Enabled { get; set; } = false;
public bool ManualEntry { get; set; } = true;
public bool LocationTracking { get; set; } = true;
public bool ShiftManagement { get; set; } = false;
}
}

View File

@ -0,0 +1,19 @@
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
namespace Marco.Pms.Model.TenantModels.MongoDBModel
{
public class DirectoryDetails
{
[BsonId]
[BsonRepresentation(BsonType.String)]
public Guid Id { get; set; } = Guid.NewGuid();
public string? Name { get; set; }
[BsonRepresentation(BsonType.String)]
public List<Guid> FeatureId { get; set; } = new List<Guid>();
public bool Enabled { get; set; } = false;
public int BucketLimit { get; set; } = 25;
public bool OrganizationChart { get; set; } = false;
}
}

View File

@ -0,0 +1,17 @@
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
namespace Marco.Pms.Model.TenantModels.MongoDBModel
{
public class ExpenseModuleDetails
{
[BsonId]
[BsonRepresentation(BsonType.String)]
public Guid Id { get; set; } = Guid.NewGuid();
public string? Name { get; set; }
[BsonRepresentation(BsonType.String)]
public List<Guid> FeatureId { get; set; } = new List<Guid>();
public bool Enabled { get; set; } = false;
}
}

View File

@ -0,0 +1,18 @@
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
namespace Marco.Pms.Model.TenantModels.MongoDBModel
{
public class FeatureDetails
{
[BsonId]
[BsonRepresentation(BsonType.String)]
public Guid Id { get; set; } = Guid.NewGuid();
public ModulesDetails? Modules { get; set; }
public ReportDetails? Reports { get; set; }
public SupportDetails? Supports { get; set; }
public List<SubscriptionCheckList> SubscriptionCheckList { get; set; } = new List<SubscriptionCheckList>();
}
}

View File

@ -0,0 +1,16 @@
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
namespace Marco.Pms.Model.TenantModels.MongoDBModel
{
public class ModulesDetails
{
[BsonId]
[BsonRepresentation(BsonType.String)]
public Guid Id { get; set; } = Guid.NewGuid();
public ProjectManagementDetails? ProjectManagement { get; set; }
public AttendanceDetails? Attendance { get; set; }
public DirectoryDetails? Directory { get; set; }
public ExpenseModuleDetails? Expense { get; set; }
}
}

View File

@ -0,0 +1,21 @@
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
namespace Marco.Pms.Model.TenantModels.MongoDBModel
{
public class ProjectManagementDetails
{
[BsonId]
[BsonRepresentation(BsonType.String)]
public Guid Id { get; set; } = Guid.NewGuid();
public string? Name { get; set; }
[BsonRepresentation(BsonType.String)]
public List<Guid> FeatureId { get; set; } = new List<Guid>();
public bool Enabled { get; set; } = false;
public int MaxProject { get; set; } = 10;
public double MaxTaskPerProject { get; set; } = 100000000;
public bool GanttChart { get; set; } = false;
public bool ResourceAllocation { get; set; } = false;
}
}

View File

@ -0,0 +1,15 @@
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
namespace Marco.Pms.Model.TenantModels.MongoDBModel
{
public class ReportDetails
{
[BsonId]
[BsonRepresentation(BsonType.String)]
public Guid Id { get; set; } = Guid.NewGuid();
public bool BasicReports { get; set; } = true;
public bool CustomReports { get; set; } = false;
public List<string> ExportData { get; set; } = new List<string>();
}
}

View File

@ -0,0 +1,14 @@
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
namespace Marco.Pms.Model.TenantModels.MongoDBModel
{
public class SubscriptionCheckList
{
[BsonId]
[BsonRepresentation(BsonType.String)]
public Guid Id { get; set; } = Guid.NewGuid();
public string Name { get; set; } = string.Empty;
public bool IsActive { get; set; } = true;
}
}

View File

@ -0,0 +1,15 @@
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
namespace Marco.Pms.Model.TenantModels.MongoDBModel
{
public class SupportDetails
{
[BsonId]
[BsonRepresentation(BsonType.String)]
public Guid Id { get; set; } = Guid.NewGuid();
public bool EmailSupport { get; set; } = true;
public bool PhoneSupport { get; set; } = false;
public bool PrioritySupport { get; set; } = false;
}
}

View File

@ -0,0 +1,15 @@
namespace Marco.Pms.Model.TenantModels
{
public class SubscriptionPlan
{
public Guid Id { get; set; }
public string PlanName { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
public bool IsActive { get; set; } = true;
}
public enum PLAN_FREQUENCY
{
MONTHLY = 0, QUARTERLY = 1, HALF_YEARLY = 2, YEARLY = 3
}
}

View File

@ -0,0 +1,41 @@
using Marco.Pms.Model.Employees;
using Marco.Pms.Model.Master;
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
using System.ComponentModel.DataAnnotations.Schema;
namespace Marco.Pms.Model.TenantModels
{
public class SubscriptionPlanDetails
{
public Guid Id { get; set; }
public double Price { get; set; }
public PLAN_FREQUENCY Frequency { get; set; }
public int TrialDays { get; set; } = 30;
public double MaxUser { get; set; } = 10;
public double MaxStorage { get; set; }
public Guid FeaturesId { get; set; }
public DateTime CreateAt { get; set; }
public DateTime? UpdateAt { get; set; }
public Guid PlanId { get; set; }
[ForeignKey("PlanId")]
[ValidateNever]
public SubscriptionPlan? Plan { get; set; }
public Guid CurrencyId { get; set; }
[ForeignKey("CurrencyId")]
[ValidateNever]
public CurrencyMaster? Currency { get; set; }
public Guid CreatedById { get; set; }
[ForeignKey("CreatedById")]
[ValidateNever]
public Employee? CreatedBy { get; set; }
public Guid? UpdatedById { get; set; }
[ForeignKey("UpdatedById")]
[ValidateNever]
public Employee? UpdatedBy { get; set; }
public bool IsActive { get; set; } = true;
}
}

View File

@ -0,0 +1,38 @@
using Marco.Pms.Model.Master;
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
using System.ComponentModel.DataAnnotations.Schema;
namespace Marco.Pms.Model.TenantModels
{
public class Tenant
{
public Guid Id { get; set; }
public string Name { get; set; } = string.Empty;
public string Email { get; set; } = string.Empty;
public string? Description { get; set; }
public string? DomainName { get; set; }
public string ContactName { get; set; } = string.Empty;
public string ContactNumber { get; set; } = string.Empty;
public string? OfficeNumber { get; set; }
public string BillingAddress { get; set; } = string.Empty;
public string? TaxId { get; set; }
public string? logoImage { get; set; } // Base64
public DateTime OnBoardingDate { get; set; }
public string? OrganizationSize { get; set; }
public Guid? IndustryId { get; set; }
[ForeignKey("IndustryId")]
[ValidateNever]
public Industry? Industry { get; set; }
public Guid? CreatedById { get; set; } // EmployeeId
public Guid TenantStatusId { get; set; }
[ForeignKey("TenantStatusId")]
[ValidateNever]
public TenantStatus? TenantStatus { get; set; }
public string Reference { get; set; } = string.Empty;
public bool IsActive { get; set; } = true;
public bool IsSuperTenant { get; set; } = false;
}
}

View File

@ -0,0 +1,48 @@
using Marco.Pms.Model.Employees;
using Marco.Pms.Model.Master;
using Marco.Pms.Model.Utilities;
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
using System.ComponentModel.DataAnnotations.Schema;
namespace Marco.Pms.Model.TenantModels
{
public class TenantSubscriptions : TenantRelation
{
public Guid Id { get; set; }
public Guid PlanId { get; set; }
[ForeignKey("PlanId")]
[ValidateNever]
public SubscriptionPlanDetails? Plan { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public bool IsTrial { get; set; }
public double MaxUsers { get; set; }
public Guid StatusId { get; set; }
[ForeignKey("StatusId")]
[ValidateNever]
public SubscriptionStatus? Status { get; set; }
public Guid CurrencyId { get; set; }
[ForeignKey("CurrencyId")]
[ValidateNever]
public CurrencyMaster? Currency { get; set; }
public DateTime NextBillingDate { get; set; }
public DateTime? CancellationDate { get; set; }
public bool AutoRenew { get; set; } = true;
public bool IsCancelled { get; set; } = false;
public DateTime CreatedAt { get; set; }
public DateTime? UpdateAt { get; set; }
public Guid CreatedById { get; set; }
[ForeignKey("CreatedById")]
[ValidateNever]
public Employee? CreatedBy { get; set; }
public Guid? UpdatedById { get; set; }
[ForeignKey("UpdatedById")]
[ValidateNever]
public Employee? UpdatedBy { get; set; }
}
}

View File

@ -0,0 +1,12 @@
namespace Marco.Pms.Model.Utilities
{
public class TenantFilter
{
public List<Guid>? IndustryIds { get; set; }
public List<Guid>? CreatedByIds { get; set; }
public List<Guid>? TenantStatusIds { get; set; }
public List<string>? References { get; set; }
public DateTime? StartDate { get; set; }
public DateTime? EndDate { get; set; }
}
}

View File

@ -1,6 +1,6 @@
using System.ComponentModel.DataAnnotations.Schema; using Marco.Pms.Model.TenantModels;
using Marco.Pms.Model.Entitlements;
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
using System.ComponentModel.DataAnnotations.Schema;
namespace Marco.Pms.Model.Utilities namespace Marco.Pms.Model.Utilities
{ {

View File

@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
namespace Marco.Pms.Model.ViewModels.AppMenu
{
public class MenuSectionVm
{
public string? Header { get; set; }
public string? Title { get; set; }
public List<MenuItemVm> Items { get; set; } = new();
}
public class MenuItemVm
{
public string? Text { get; set; }
public string? Icon { get; set; }
public bool Available { get; set; } = true;
public string? Link { get; set; }
public List<SubMenuItemVm> Submenu { get; set; } = new();
}
public class SubMenuItemVm
{
public string? Text { get; set; }
public bool Available { get; set; } = true;
public string Link { get; set; } = string.Empty;
}
}

View File

@ -0,0 +1,8 @@
namespace Marco.Pms.Model.ViewModels.AppMenu
{
public class MasterMenuVM
{
public int Id { get; set; }
public string? Name { get; set; }
}
}

View File

@ -0,0 +1,13 @@
namespace Marco.Pms.Model.ViewModels.DocumentManager
{
public class MenuItemVM
{
public Guid Id { get; set; }
public string? Name { get; set; }
public string? Icon { get; set; }
public bool Available { get; set; }
public string? Link { get; set; }
public List<SubMenuItemVM> Submenu { get; set; } = new List<SubMenuItemVM>();
}
}

View File

@ -0,0 +1,9 @@
namespace Marco.Pms.Model.ViewModels.DocumentManager
{
public class MenuSectionApplicationVM
{
public Guid Id { get; set; }
public string? Name { get; set; }
public bool Available { get; set; }
}
}

View File

@ -0,0 +1,11 @@
namespace Marco.Pms.Model.ViewModels.DocumentManager
{
public class MenuSectionVM
{
public Guid Id { get; set; }
public string? Header { get; set; }
public string? Name { get; set; }
public List<MenuItemVM> Items { get; set; } = new List<MenuItemVM>();
}
}

View File

@ -0,0 +1,12 @@
namespace Marco.Pms.Model.ViewModels.DocumentManager
{
public class SubMenuItemVM
{
public Guid Id { get; set; }
public string? Name { get; set; }
public bool Available { get; set; }
public string? Link { get; set; }
}
}

View File

@ -21,6 +21,7 @@
public string? AadharNumber { get; set; } public string? AadharNumber { get; set; }
public bool IsActive { get; set; } = true; public bool IsActive { get; set; } = true;
public bool IsRootUser { get; set; }
public string? PanNumber { get; set; } public string? PanNumber { get; set; }
public string? Photo { get; set; } // To store the captured photo public string? Photo { get; set; } // To store the captured photo
@ -28,6 +29,7 @@
public string? ApplicationUserId { get; set; } public string? ApplicationUserId { get; set; }
public Guid? JobRoleId { get; set; } public Guid? JobRoleId { get; set; }
public Guid TenantId { get; set; }
public bool IsSystem { get; set; } public bool IsSystem { get; set; }
public string? JobRole { get; set; } public string? JobRole { get; set; }

View File

@ -0,0 +1,11 @@
namespace Marco.Pms.Model.ViewModels.Projects
{
public class ProjectHisteryVM
{
public string? ProjectName { get; set; }
public string? ProjectShortName { get; set; }
public DateTime AssignedDate { get; set; }
public DateTime? RemovedDate { get; set; }
public string? Designation { get; set; }
}
}

View File

@ -0,0 +1,23 @@
using Marco.Pms.Model.Master;
using Marco.Pms.Model.TenantModels;
using Marco.Pms.Model.ViewModels.Activities;
namespace Marco.Pms.Model.ViewModels.Tenant
{
public class SubscriptionPlanDetailsVM
{
public Guid Id { get; set; }
public string? PlanName { get; set; }
public string? Description { get; set; }
public double Price { get; set; }
public double MaxUsers { get; set; }
public PLAN_FREQUENCY Frequency { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime? UpdatedAt { get; set; }
public BasicEmployeeVM? CreatedBy { get; set; }
public BasicEmployeeVM? updatedBy { get; set; }
public CurrencyMaster? Currency { get; set; }
}
}

View File

@ -0,0 +1,20 @@
using Marco.Pms.Model.Master;
using Marco.Pms.Model.TenantModels;
using Marco.Pms.Model.TenantModels.MongoDBModel;
namespace Marco.Pms.Model.ViewModels.Tenant
{
public class SubscriptionPlanVM
{
public Guid Id { get; set; }
public string? PlanName { get; set; }
public string? Description { get; set; }
public double? Price { get; set; }
public PLAN_FREQUENCY? Frequency { get; set; }
public int TrialDays { get; set; }
public double MaxUser { get; set; }
public double MaxStorage { get; set; }
public FeatureDetails? Features { get; set; }
public CurrencyMaster? Currency { get; set; }
}
}

View File

@ -0,0 +1,43 @@
using Marco.Pms.Model.Master;
using Marco.Pms.Model.TenantModels.MongoDBModel;
using Marco.Pms.Model.ViewModels.Activities;
namespace Marco.Pms.Model.ViewModels.Tenant
{
public class TenantDetailsVM
{
public Guid Id { get; set; }
public string Name { get; set; } = string.Empty;
public string Email { get; set; } = string.Empty;
public string? Description { get; set; }
public string? DomainName { get; set; }
public string ContactName { get; set; } = string.Empty;
public string ContactNumber { get; set; } = string.Empty;
public string? OfficeNumber { get; set; }
public string BillingAddress { get; set; } = string.Empty;
public string? TaxId { get; set; }
public string? logoImage { get; set; } // Base64
public DateTime OnBoardingDate { get; set; }
public string? OrganizationSize { get; set; }
public Industry? Industry { get; set; }
public TenantStatus? TenantStatus { get; set; }
public string Reference { get; set; } = string.Empty;
public bool IsActive { get; set; } = true;
public bool IsSuperTenant { get; set; } = false;
public int SeatsAvailable { get; set; }
public int ActiveEmployees { get; set; }
public int InActiveEmployees { get; set; }
public int? ActiveProjects { get; set; }
public int? InProgressProjects { get; set; }
public int? OnHoldProjects { get; set; }
public int? InActiveProjects { get; set; }
public int? CompletedProjects { get; set; }
public DateTime? ExpiryDate { get; set; }
public DateTime? NextBillingDate { get; set; }
public BasicEmployeeVM? CreatedBy { get; set; }
public List<SubscriptionPlanDetailsVM>? SubscriptionHistery { get; set; }
public SubscriptionPlanDetailsVM? CurrentPlan { get; set; }
public FeatureDetails? CurrentPlanFeatures { get; set; }
}
}

View File

@ -0,0 +1,18 @@
using Marco.Pms.Model.Master;
namespace Marco.Pms.Model.ViewModels.Tenant
{
public class TenantListVM
{
public Guid Id { get; set; }
public string Name { get; set; } = string.Empty;
public string Email { get; set; } = string.Empty;
public string? DomainName { get; set; }
public string ContactName { get; set; } = string.Empty;
public string ContactNumber { get; set; } = string.Empty;
public string? logoImage { get; set; } // Base64
public string? OrganizationSize { get; set; }
public Industry? Industry { get; set; }
public TenantStatus? TenantStatus { get; set; }
}
}

View File

@ -0,0 +1,28 @@
using Marco.Pms.Model.Master;
using Marco.Pms.Model.ViewModels.Activities;
namespace Marco.Pms.Model.ViewModels.Tenant
{
public class TenantVM
{
public Guid Id { get; set; }
public string Name { get; set; } = string.Empty;
public string Email { get; set; } = string.Empty;
public string? Description { get; set; }
public string? DomainName { get; set; }
public string ContactName { get; set; } = string.Empty;
public string ContactNumber { get; set; } = string.Empty;
public string BillingAddress { get; set; } = string.Empty;
public string? TaxId { get; set; }
public string? logoImage { get; set; } // Base64
public DateTime OnBoardingDate { get; set; }
public string? OrganizationSize { get; set; }
public Industry? Industry { get; set; }
public BasicEmployeeVM? CreatedBy { get; set; } // EmployeeId
public TenantStatus? TenantStatus { get; set; }
public string Reference { get; set; } = string.Empty;
public bool IsActive { get; set; } = true;
public bool IsSuperTenant { get; set; } = false;
}
}

View File

@ -0,0 +1,736 @@
using AutoMapper;
using Marco.Pms.CacheHelper;
using Marco.Pms.Model.AppMenu;
using Marco.Pms.Model.Dtos.AppMenu;
using Marco.Pms.Model.Utilities;
using Marco.Pms.Model.ViewModels.AppMenu;
using Marco.Pms.Model.ViewModels.DocumentManager;
using Marco.Pms.Services.Helpers;
using Marco.Pms.Services.Service;
using MarcoBMS.Services.Helpers;
using MarcoBMS.Services.Service;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Marco.Pms.Services.Controllers
{
[Authorize]
[ApiController]
[Route("api/[controller]")]
public class AppMenuController : ControllerBase
{
private readonly UserHelper _userHelper;
private readonly SidebarMenuHelper _sideBarMenuHelper;
private readonly IMapper _mapper;
private readonly ILoggingService _logger;
private readonly IServiceScopeFactory _serviceScopeFactory;
private readonly Guid tenantId;
private static readonly Guid superTenantId = Guid.Parse("b3466e83-7e11-464c-b93a-daf047838b26");
private static readonly Guid ProjectManagement = Guid.Parse("53176ebf-c75d-42e5-839f-4508ffac3def");
private static readonly Guid ExpenseManagement = Guid.Parse("a4e25142-449b-4334-a6e5-22f70e4732d7");
private static readonly Guid TaskManagement = Guid.Parse("9d4b5489-2079-40b9-bd77-6e1bf90bc19f");
private static readonly Guid EmployeeManagement = Guid.Parse("81ab8a87-8ccd-4015-a917-0627cee6a100");
private static readonly Guid AttendanceManagement = Guid.Parse("52c9cf54-1eb2-44d2-81bb-524cf29c0a94");
private static readonly Guid MastersMangent = Guid.Parse("be3b3afc-6ccf-4566-b9b6-aafcb65546be");
private static readonly Guid DirectoryManagement = Guid.Parse("39e66f81-efc6-446c-95bd-46bff6cfb606");
private static readonly Guid TenantManagement = Guid.Parse("2f3509b7-160d-410a-b9b6-daadd96c986d");
public AppMenuController(UserHelper userHelper,
SidebarMenuHelper sideBarMenuHelper,
IMapper mapper,
ILoggingService logger,
IServiceScopeFactory serviceScopeFactory)
{
_userHelper = userHelper;
_sideBarMenuHelper = sideBarMenuHelper;
_mapper = mapper;
_logger = logger;
_serviceScopeFactory = serviceScopeFactory;
tenantId = userHelper.GetTenantId();
}
/// <summary>
/// Creates a new sidebar menu section for the tenant.
/// Only accessible by root users or for the super tenant.
/// </summary>
/// <param name="menuSectionDto">The data for the new menu section.</param>
/// <returns>HTTP response with result of the operation.</returns>
[HttpPost("add/sidebar/menu-section")]
public async Task<IActionResult> CreateAppSideBarMenu([FromBody] CreateMenuSectionDto menuSectionDto)
{
// Step 1: Fetch logged-in user
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
var isRootUser = loggedInEmployee.ApplicationUser?.IsRootUser ?? false;
// Step 2: Authorization check
if (!isRootUser || tenantId != superTenantId)
{
_logger.LogWarning("Access denied: Employee {EmployeeId} attempted to create sidebar menu in Tenant {TenantId}", loggedInEmployee.Id, tenantId);
return StatusCode(403, ApiResponse<object>.ErrorResponse("Access Denied", "User does not have permission.", 403));
}
// Step 3: Map DTO to entity
var sideMenuSection = _mapper.Map<MenuSection>(menuSectionDto);
sideMenuSection.TenantId = tenantId;
try
{
// Step 4: Save entity using helper
sideMenuSection = await _sideBarMenuHelper.CreateMenuSectionAsync(sideMenuSection);
if (sideMenuSection == null)
{
_logger.LogWarning("Failed to create sidebar menu section. Tenant: {TenantId}, Request: {@MenuSectionDto}", tenantId, menuSectionDto);
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid MenuSection", 400));
}
// Step 5: Log success
_logger.LogInfo("Sidebar menu created successfully. SectionId: {SectionId}, TenantId: {TenantId}, EmployeeId: {EmployeeId}",
sideMenuSection.Id, tenantId, loggedInEmployee.Id);
return Ok(ApiResponse<object>.SuccessResponse(sideMenuSection, "Sidebar menu created successfully.", 201));
}
catch (Exception ex)
{
// Step 6: Handle and log unexpected server errors
_logger.LogError(ex, "Unexpected error occurred while creating sidebar menu. Tenant: {TenantId}, EmployeeId: {EmployeeId}, Request: {@MenuSectionDto}",
tenantId, loggedInEmployee.Id, menuSectionDto);
return StatusCode(500, ApiResponse<object>.ErrorResponse("Server Error", "An unexpected error occurred.", 500));
}
}
/// <summary>
/// Updates an existing sidebar menu section for the tenant.
/// Only accessible by root users or for the super tenant.
/// </summary>
/// <param name="sectionId">The unique identifier of the section to update.</param>
/// <param name="updatedSection">The updated data for the sidebar menu section.</param>
/// <returns>HTTP response with the result of the operation.</returns>
[HttpPut("edit/sidebar/menu-section/{sectionId}")]
public async Task<IActionResult> UpdateMenuSection(Guid sectionId, [FromBody] UpdateMenuSectionDto updatedSection)
{
// Step 1: Fetch logged-in user
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
var isRootUser = loggedInEmployee.ApplicationUser?.IsRootUser ?? false;
// Step 2: Authorization check
if (!isRootUser && tenantId != superTenantId)
{
_logger.LogWarning("Access denied: User {UserId} attempted to update sidebar menu section {SectionId} in Tenant {TenantId}",
loggedInEmployee.Id, sectionId, tenantId);
return StatusCode(403, ApiResponse<object>.ErrorResponse("Access Denied", "User does not have permission.", 403));
}
// Step 3: Validate request
if (sectionId == Guid.Empty || sectionId != updatedSection.Id)
{
_logger.LogWarning("Invalid update request. Tenant: {TenantId}, SectionId: {SectionId}, PayloadId: {PayloadId}, UserId: {UserId}",
tenantId, sectionId, updatedSection.Id, loggedInEmployee.Id);
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid section ID or mismatched payload.", 400));
}
// Step 4: Map DTO to entity
var menuSectionEntity = _mapper.Map<MenuSection>(updatedSection);
try
{
// Step 5: Perform update operation
var result = await _sideBarMenuHelper.UpdateMenuSectionAsync(sectionId, menuSectionEntity);
if (result == null)
{
_logger.LogWarning("Menu section not found for update. SectionId: {SectionId}, TenantId: {TenantId}, UserId: {UserId}",
sectionId, tenantId, loggedInEmployee.Id);
return NotFound(ApiResponse<object>.ErrorResponse("Menu section not found", 404));
}
// Step 6: Successful update
_logger.LogInfo("Menu section updated successfully. SectionId: {SectionId}, TenantId: {TenantId}, UserId: {UserId}",
sectionId, tenantId, loggedInEmployee.Id);
return Ok(ApiResponse<object>.SuccessResponse(result, "Menu section updated successfully"));
}
catch (Exception ex)
{
// Step 7: Unexpected server error
_logger.LogError(ex, "Failed to update menu section. SectionId: {SectionId}, TenantId: {TenantId}, UserId: {UserId}, Payload: {@UpdatedSection}",
sectionId, tenantId, loggedInEmployee.Id, updatedSection);
return StatusCode(500, ApiResponse<object>.ErrorResponse("Server error", "An unexpected error occurred while updating the menu section.", 500));
}
}
/// <summary>
/// Adds a new menu item to an existing sidebar menu section.
/// Only accessible by root users or for the super tenant.
/// </summary>
/// <param name="sectionId">The unique identifier of the section the item will be added to.</param>
/// <param name="newItemDto">The details of the new menu item.</param>
/// <returns>HTTP response with the result of the operation.</returns>
[HttpPost("add/sidebar/menus/{sectionId}/items")]
public async Task<IActionResult> AddMenuItem(Guid sectionId, [FromBody] CreateMenuItemDto newItemDto)
{
// Step 1: Fetch logged-in user
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
var isRootUser = loggedInEmployee.ApplicationUser?.IsRootUser ?? false;
// Step 2: Authorization check
if (!isRootUser && tenantId != superTenantId)
{
_logger.LogWarning("Access denied: User {UserId} attempted to add menu item to section {SectionId} in Tenant {TenantId}",
loggedInEmployee.Id, sectionId, tenantId);
return StatusCode(403, ApiResponse<object>.ErrorResponse("Access Denied", "User does not have permission.", 403));
}
// Step 3: Input validation
if (sectionId == Guid.Empty || newItemDto == null)
{
_logger.LogWarning("Invalid AddMenuItem request. Tenant: {TenantId}, SectionId: {SectionId}, UserId: {UserId}",
tenantId, sectionId, loggedInEmployee.Id);
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid section ID or menu item payload.", 400));
}
try
{
// Step 4: Map DTO to entity
var menuItemEntity = _mapper.Map<MenuItem>(newItemDto);
// Step 5: Perform Add operation
var result = await _sideBarMenuHelper.AddMenuItemAsync(sectionId, menuItemEntity);
if (result == null)
{
_logger.LogWarning("Menu section not found. Unable to add menu item. SectionId: {SectionId}, TenantId: {TenantId}, UserId: {UserId}",
sectionId, tenantId, loggedInEmployee.Id);
return NotFound(ApiResponse<object>.ErrorResponse("Menu section not found", 404));
}
// Step 6: Successful addition
_logger.LogInfo("Menu item added successfully. SectionId: {SectionId}, MenuItemId: {MenuItemId}, TenantId: {TenantId}, UserId: {UserId}",
sectionId, result.Id, tenantId, loggedInEmployee.Id);
return Ok(ApiResponse<object>.SuccessResponse(result, "Menu item added successfully"));
}
catch (Exception ex)
{
// Step 7: Handle unexpected errors
_logger.LogError(ex, "Error occurred while adding menu item. SectionId: {SectionId}, TenantId: {TenantId}, UserId: {UserId}, Payload: {@NewItemDto}",
sectionId, tenantId, loggedInEmployee.Id, newItemDto);
return StatusCode(500, ApiResponse<object>.ErrorResponse("Server error", "An unexpected error occurred while adding the menu item.", 500));
}
}
/// <summary>
/// Updates an existing menu item inside a sidebar menu section.
/// Only accessible by root users or within the super tenant.
/// </summary>
/// <param name="sectionId">The ID of the sidebar menu section.</param>
/// <param name="itemId">The ID of the menu item to update.</param>
/// <param name="updatedMenuItem">The updated menu item details.</param>
/// <returns>HTTP response with the result of the update operation.</returns>
[HttpPut("edit/sidebar/{sectionId}/items/{itemId}")]
public async Task<IActionResult> UpdateMenuItem(Guid sectionId, Guid itemId, [FromBody] UpdateMenuItemDto updatedMenuItem)
{
// Step 1: Fetch logged-in user
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
var isRootUser = loggedInEmployee.ApplicationUser?.IsRootUser ?? false;
// Step 2: Authorization check
if (!isRootUser && tenantId != superTenantId)
{
_logger.LogWarning("Access denied: User {UserId} attempted to update menu item {ItemId} in Section {SectionId}, Tenant {TenantId}",
loggedInEmployee.Id, itemId, sectionId, tenantId);
return StatusCode(403, ApiResponse<object>.ErrorResponse("Access Denied", "User does not have permission.", 403));
}
// Step 3: Input validation
if (sectionId == Guid.Empty || itemId == Guid.Empty || updatedMenuItem == null || updatedMenuItem.Id != itemId)
{
_logger.LogWarning("Invalid UpdateMenuItem request. Tenant: {TenantId}, SectionId: {SectionId}, ItemId: {ItemId}, UserId: {UserId}",
tenantId, sectionId, itemId, loggedInEmployee.Id);
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid section ID, item ID, or menu item payload.", 400));
}
// Step 4: Map DTO to entity
var menuItemEntity = _mapper.Map<MenuItem>(updatedMenuItem);
try
{
// Step 5: Perform update operation
var result = await _sideBarMenuHelper.UpdateMenuItemAsync(sectionId, itemId, menuItemEntity);
if (result == null)
{
_logger.LogWarning("Menu item not found or update failed. Tenant: {TenantId}, SectionId: {SectionId}, ItemId: {ItemId}, UserId: {UserId}",
tenantId, sectionId, itemId, loggedInEmployee.Id);
return NotFound(ApiResponse<object>.ErrorResponse("Menu item not found or update failed.", 404));
}
// Step 6: Success log
_logger.LogInfo("Menu item updated successfully. Tenant: {TenantId}, SectionId: {SectionId}, ItemId: {ItemId}, UserId: {UserId}",
tenantId, sectionId, itemId, loggedInEmployee.Id);
return Ok(ApiResponse<object>.SuccessResponse(result, "Sidebar menu item updated successfully."));
}
catch (Exception ex)
{
// ✅ Step 7: Handle server errors
_logger.LogError(ex, "Error occurred while updating menu item. Tenant: {TenantId}, SectionId: {SectionId}, ItemId: {ItemId}, UserId: {UserId}, Payload: {@UpdatedMenuItem}",
tenantId, sectionId, itemId, loggedInEmployee.Id, updatedMenuItem);
return StatusCode(
500,
ApiResponse<object>.ErrorResponse("Server Error", "An unexpected error occurred while updating the menu item.", 500)
);
}
}
/// <summary>
/// Adds a new sub-menu item to an existing menu item inside a sidebar menu section.
/// Only accessible by root users or within the super tenant.
/// </summary>
/// <param name="sectionId">The ID of the sidebar menu section.</param>
/// <param name="itemId">The ID of the parent menu item.</param>
/// <param name="newSubItem">The details of the new sub-menu item.</param>
/// <returns>HTTP response with the result of the add operation.</returns>
[HttpPost("add/sidebar/menus/{sectionId}/items/{itemId}/subitems")]
public async Task<IActionResult> AddSubMenuItem(Guid sectionId, Guid itemId, [FromBody] CreateSubMenuItemDto newSubItem)
{
// Step 1: Fetch logged-in user
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
var isRootUser = loggedInEmployee.ApplicationUser?.IsRootUser ?? false;
// Step 2: Authorization check
if (!isRootUser && tenantId != superTenantId)
{
_logger.LogWarning("Access denied: User {UserId} attempted to add sub-menu item in Section {SectionId}, MenuItem {ItemId}, Tenant {TenantId}",
loggedInEmployee.Id, sectionId, itemId, tenantId);
return StatusCode(403, ApiResponse<object>.ErrorResponse("Access Denied", "User does not have permission.", 403));
}
// Step 3: Validate input
if (sectionId == Guid.Empty || itemId == Guid.Empty || newSubItem == null)
{
_logger.LogWarning("Invalid AddSubMenuItem request. Tenant: {TenantId}, SectionId: {SectionId}, ItemId: {ItemId}, UserId: {UserId}",
tenantId, sectionId, itemId, loggedInEmployee.Id);
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid section ID, item ID, or sub-menu item payload.", 400));
}
try
{
// Step 4: Map DTO to entity
var subMenuItemEntity = _mapper.Map<SubMenuItem>(newSubItem);
// Step 5: Perform add operation
var result = await _sideBarMenuHelper.AddSubMenuItemAsync(sectionId, itemId, subMenuItemEntity);
if (result == null)
{
_logger.LogWarning("Parent menu item not found. Failed to add sub-menu item. Tenant: {TenantId}, SectionId: {SectionId}, ItemId: {ItemId}, UserId: {UserId}",
tenantId, sectionId, itemId, loggedInEmployee.Id);
return NotFound(ApiResponse<object>.ErrorResponse("Parent menu item not found.", 404));
}
// Step 6: Success logging
_logger.LogInfo("Sub-menu item added successfully. Tenant: {TenantId}, SectionId: {SectionId}, ParentItemId: {ItemId}, SubItemId: {SubItemId}, UserId: {UserId}",
tenantId, sectionId, itemId, result.Id, loggedInEmployee.Id);
return Ok(ApiResponse<object>.SuccessResponse(result, "Sub-menu item added successfully."));
}
catch (Exception ex)
{
// Step 7: Handle unexpected errors
_logger.LogError(ex, "Error occurred while adding sub-menu item. Tenant: {TenantId}, SectionId: {SectionId}, ItemId: {ItemId}, UserId: {UserId}, Payload: {@NewSubItem}",
tenantId, sectionId, itemId, loggedInEmployee.Id, newSubItem);
return StatusCode(500, ApiResponse<object>.ErrorResponse("Server Error", "An unexpected error occurred while adding the sub-menu item.", 500));
}
}
/// <summary>
/// Updates an existing sub-menu item inside a sidebar menu section.
/// Only accessible by root users or within the super tenant.
/// </summary>
/// <param name="sectionId">The ID of the sidebar menu section.</param>
/// <param name="itemId">The ID of the parent menu item.</param>
/// <param name="subItemId">The ID of the sub-menu item to update.</param>
/// <param name="updatedSubMenuItem">The updated sub-menu item details.</param>
/// <returns>HTTP response with the result of the update operation.</returns>
[HttpPut("edit/sidebar/{sectionId}/items/{itemId}/subitems/{subItemId}")]
public async Task<IActionResult> UpdateSubmenuItem(Guid sectionId, Guid itemId, Guid subItemId, [FromBody] UpdateSubMenuItemDto updatedSubMenuItem)
{
// Step 1: Fetch logged-in user
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
var isRootUser = loggedInEmployee.ApplicationUser?.IsRootUser ?? false;
// Step 2: Authorization check
if (!isRootUser && tenantId != superTenantId)
{
_logger.LogWarning("Access denied: User {UserId} attempted to update sub-menu {SubItemId} under MenuItem {ItemId} in Section {SectionId}, Tenant {TenantId}",
loggedInEmployee.Id, subItemId, itemId, sectionId, tenantId);
return StatusCode(403, ApiResponse<object>.ErrorResponse("Access Denied", "User does not have permission.", 403));
}
// Step 3: Input validation
if (sectionId == Guid.Empty || itemId == Guid.Empty || subItemId == Guid.Empty || updatedSubMenuItem == null || updatedSubMenuItem.Id != subItemId)
{
_logger.LogWarning("Invalid UpdateSubMenuItem request. Tenant: {TenantId}, SectionId: {SectionId}, ItemId: {ItemId}, SubItemId: {SubItemId}, UserId: {UserId}",
tenantId, sectionId, itemId, subItemId, loggedInEmployee.Id);
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid section ID, menu item ID, sub-item ID, or payload mismatch.", 400));
}
try
{
// Step 4: Map DTO to entity
var subMenuEntity = _mapper.Map<SubMenuItem>(updatedSubMenuItem);
// Step 5: Perform update operation
var result = await _sideBarMenuHelper.UpdateSubmenuItemAsync(sectionId, itemId, subItemId, subMenuEntity);
if (result == null)
{
_logger.LogWarning("Sub-menu item not found or update failed. Tenant: {TenantId}, SectionId: {SectionId}, ItemId: {ItemId}, SubItemId: {SubItemId}, UserId: {UserId}",
tenantId, sectionId, itemId, subItemId, loggedInEmployee.Id);
return NotFound(ApiResponse<object>.ErrorResponse("Sub-menu item not found.", 404));
}
// Step 6: Log success
_logger.LogInfo("Sub-menu item updated successfully. Tenant: {TenantId}, SectionId: {SectionId}, MenuItemId: {ItemId}, SubItemId: {SubItemId}, UserId: {UserId}",
tenantId, sectionId, itemId, subItemId, loggedInEmployee.Id);
return Ok(ApiResponse<object>.SuccessResponse(result, "Sub-menu item updated successfully."));
}
catch (Exception ex)
{
// Step 7: Handle unexpected errors
_logger.LogError(ex, "Error occurred while updating sub-menu item. Tenant: {TenantId}, SectionId: {SectionId}, MenuItemId: {ItemId}, SubItemId: {SubItemId}, UserId: {UserId}, Payload: {@UpdatedSubMenuItem}",
tenantId, sectionId, itemId, subItemId, loggedInEmployee.Id, updatedSubMenuItem);
return StatusCode(500, ApiResponse<object>.ErrorResponse("Server Error", "An unexpected error occurred while updating the sub-menu item.", 500));
}
}
/// <summary>
/// Fetches the sidebar menu for the current tenant and filters items based on employee permissions.
/// </summary>
/// <returns>The sidebar menu with only the items/sub-items the employee has access to.</returns>
[HttpGet("get/menu")]
public async Task<IActionResult> GetAppSideBarMenu()
{
// Step 1: Get logged-in employee
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
var employeeId = loggedInEmployee.Id;
using var scope = _serviceScopeFactory.CreateScope();
var _permissions = scope.ServiceProvider.GetRequiredService<PermissionServices>();
try
{
// Step 2: Fetch all menu sections for the tenant
var menus = await _sideBarMenuHelper.GetAllMenuSectionsAsync(tenantId);
foreach (var menu in menus)
{
var allowedItems = new List<MenuItem>();
foreach (var item in menu.Items)
{
// --- Item permission check ---
if (!item.PermissionIds.Any())
{
allowedItems.Add(item);
}
else
{
// Convert permission string IDs to GUIDs
var menuPermissionIds = item.PermissionIds
.Select(Guid.Parse)
.ToList();
bool isAllowed = await _permissions.HasPermissionAny(menuPermissionIds, employeeId);
// If allowed, filter its submenus as well
if (isAllowed)
{
if (item.Submenu?.Any() == true)
{
var allowedSubmenus = new List<SubMenuItem>();
foreach (var subItem in item.Submenu)
{
if (!subItem.PermissionIds.Any())
{
allowedSubmenus.Add(subItem);
continue;
}
var subMenuPermissionIds = subItem.PermissionIds
.Select(Guid.Parse)
.ToList();
bool isSubItemAllowed = await _permissions.HasPermissionAny(subMenuPermissionIds, employeeId);
if (isSubItemAllowed)
{
allowedSubmenus.Add(subItem);
}
}
// Replace with filtered submenus
item.Submenu = allowedSubmenus;
}
allowedItems.Add(item);
}
}
}
// Replace with filtered items
menu.Items = allowedItems;
}
// Step 3: Log success
_logger.LogInfo("Fetched sidebar menu successfully. Tenant: {TenantId}, EmployeeId: {EmployeeId}, SectionsReturned: {Count}",
tenantId, employeeId, menus.Count);
var response = _mapper.Map<List<MenuSectionVM>>(menus);
return Ok(ApiResponse<object>.SuccessResponse(response, "Sidebar menu fetched successfully"));
}
catch (Exception ex)
{
// Step 4: Handle unexpected errors
_logger.LogError(ex, "Error occurred while fetching sidebar menu. Tenant: {TenantId}, EmployeeId: {EmployeeId}",
tenantId, employeeId);
return StatusCode(500, ApiResponse<object>.ErrorResponse("Server Error", "An unexpected error occurred while fetching the sidebar menu.", 500));
}
}
/// <summary>
/// Retrieves the master menu list based on enabled features for the current tenant.
/// </summary>
/// <returns>List of master menu items available for the tenant</returns>
[HttpGet("get/master-list")]
public async Task<IActionResult> GetMasterList()
{
// Start logging scope for observability
try
{
// Get currently logged-in employee
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
_logger.LogInfo("Fetching master list for EmployeeId: {EmployeeId}", loggedInEmployee.Id);
using var scope = _serviceScopeFactory.CreateScope();
var generalHelper = scope.ServiceProvider.GetRequiredService<GeneralHelper>();
// Define static master menus for each feature section
var featureMenus = new Dictionary<Guid, List<MasterMenuVM>>
{
{
EmployeeManagement, new List<MasterMenuVM>
{
new MasterMenuVM { Id = 1, Name = "Application Role" },
new MasterMenuVM { Id = 2, Name = "Job Role" }
}
},
{
ProjectManagement, new List<MasterMenuVM>
{
new MasterMenuVM { Id = 3, Name = "Activity" },
new MasterMenuVM { Id = 4, Name = "Work Category" }
}
},
{
DirectoryManagement, new List<MasterMenuVM>
{
new MasterMenuVM { Id = 5, Name = "Contact Category" },
new MasterMenuVM { Id = 6, Name = "Contact Tag" }
}
},
{
ExpenseManagement, new List<MasterMenuVM>
{
new MasterMenuVM { Id = 7, Name = "Expense Type" },
new MasterMenuVM { Id = 8, Name = "Payment Mode" }
}
}
};
if (tenantId == superTenantId)
{
var superResponse = featureMenus.Values.SelectMany(list => list).OrderBy(r => r.Name).ToList();
_logger.LogInfo("MasterMenu count for TenantId {TenantId}: {Count}", tenantId, superResponse.Count);
return Ok(ApiResponse<object>.SuccessResponse(superResponse, "Successfully fetched the master table list", 200));
}
// Fetch features enabled for tenant
var featureIds = await generalHelper.GetFeatureIdsByTenentIdAsync(tenantId);
_logger.LogInfo("Enabled features for TenantId: {TenantId} -> {FeatureIds}", tenantId, string.Join(",", featureIds));
// Aggregate menus based on enabled features
var response = featureIds
.Where(id => featureMenus.ContainsKey(id))
.SelectMany(id => featureMenus[id])
.OrderBy(r => r.Name)
.ToList();
_logger.LogInfo("MasterMenu count for TenantId {TenantId}: {Count}", tenantId, response.Count);
return Ok(ApiResponse<object>.SuccessResponse(response, "Successfully fetched the master table list", 200));
}
catch (Exception ex)
{
// Critical error tracking
_logger.LogError(ex, "Error occurred while fetching master menu list for TenantId: {TenantId}", tenantId);
return StatusCode(500, ApiResponse<string>.ErrorResponse("An unexpected error occurred while fetching master menu list."));
}
}
[HttpGet("get/menu-mobile")]
public async Task<IActionResult> GetAppSideBarMenuForobile()
{
// Step 1: Get logged-in employee
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
var employeeId = loggedInEmployee.Id;
using var scope = _serviceScopeFactory.CreateScope();
var _permissions = scope.ServiceProvider.GetRequiredService<PermissionServices>();
try
{
// Step 2: Fetch all menu sections for the tenant
var menus = await _sideBarMenuHelper.GetAllMenuSectionsAsync(tenantId);
List<MenuSectionApplicationVM> response = new List<MenuSectionApplicationVM>();
foreach (var menu in menus)
{
var allowedItems = new List<MenuItem>();
foreach (var item in menu.Items)
{
// --- Item permission check ---
if (!item.PermissionIds.Any())
{
MenuSectionApplicationVM menuVM = new MenuSectionApplicationVM
{
Id = item.Id,
Name = item.Text,
Available = true
};
response.Add(menuVM);
}
else
{
// Convert permission string IDs to GUIDs
var menuPermissionIds = item.PermissionIds
.Select(Guid.Parse)
.ToList();
bool isAllowed = await _permissions.HasPermissionAny(menuPermissionIds, employeeId);
// If allowed, filter its submenus as well
if (isAllowed)
{
if (item.Submenu?.Any() == true)
{
var allowedSubmenus = new List<SubMenuItem>();
foreach (var subItem in item.Submenu)
{
if (!subItem.PermissionIds.Any())
{
MenuSectionApplicationVM subMenuVM = new MenuSectionApplicationVM
{
Id = subItem.Id,
Name = subItem.Text,
Available = true
};
response.Add(subMenuVM);
continue;
}
var subMenuPermissionIds = subItem.PermissionIds
.Select(Guid.Parse)
.ToList();
bool isSubItemAllowed = await _permissions.HasPermissionAny(subMenuPermissionIds, employeeId);
if (isSubItemAllowed)
{
MenuSectionApplicationVM subMenuVM = new MenuSectionApplicationVM
{
Id = subItem.Id,
Name = subItem.Text,
Available = true
};
response.Add(subMenuVM);
}
}
// Replace with filtered submenus
item.Submenu = allowedSubmenus;
}
MenuSectionApplicationVM menuVM = new MenuSectionApplicationVM
{
Id = item.Id,
Name = item.Text,
Available = true
};
response.Add(menuVM);
}
}
}
// Replace with filtered items
menu.Items = allowedItems;
}
// Step 3: Log success
_logger.LogInfo("Fetched sidebar menu successfully. Tenant: {TenantId}, EmployeeId: {EmployeeId}, SectionsReturned: {Count}",
tenantId, employeeId, menus.Count);
return Ok(ApiResponse<object>.SuccessResponse(response, "Sidebar menu fetched successfully", 200));
}
catch (Exception ex)
{
// Step 4: Handle unexpected errors
_logger.LogError(ex, "Error occurred while fetching sidebar menu. Tenant: {TenantId}, EmployeeId: {EmployeeId}",
tenantId, employeeId);
return StatusCode(500, ApiResponse<object>.ErrorResponse("Server Error", "An unexpected error occurred while fetching the sidebar menu.", 500));
}
}
}
}

View File

@ -9,6 +9,7 @@ using Marco.Pms.Model.Projects;
using Marco.Pms.Model.Utilities; using Marco.Pms.Model.Utilities;
using Marco.Pms.Model.ViewModels.Activities; using Marco.Pms.Model.ViewModels.Activities;
using Marco.Pms.Model.ViewModels.Employee; using Marco.Pms.Model.ViewModels.Employee;
using Marco.Pms.Services.Helpers;
using Marco.Pms.Services.Hubs; using Marco.Pms.Services.Hubs;
using Marco.Pms.Services.Service; using Marco.Pms.Services.Service;
using Marco.Pms.Services.Service.ServiceInterfaces; using Marco.Pms.Services.Service.ServiceInterfaces;
@ -36,6 +37,7 @@ namespace MarcoBMS.Services.Controllers
private readonly IEmailSender _emailSender; private readonly IEmailSender _emailSender;
private readonly EmployeeHelper _employeeHelper; private readonly EmployeeHelper _employeeHelper;
private readonly UserHelper _userHelper; private readonly UserHelper _userHelper;
private readonly GeneralHelper _generalHelper;
private readonly IConfiguration _configuration; private readonly IConfiguration _configuration;
private readonly ILoggingService _logger; private readonly ILoggingService _logger;
private readonly IHubContext<MarcoHub> _signalR; private readonly IHubContext<MarcoHub> _signalR;
@ -47,13 +49,14 @@ namespace MarcoBMS.Services.Controllers
public EmployeeController(UserManager<ApplicationUser> userManager, IEmailSender emailSender, public EmployeeController(UserManager<ApplicationUser> userManager, IEmailSender emailSender,
ApplicationDbContext context, EmployeeHelper employeeHelper, UserHelper userHelper, IConfiguration configuration, ILoggingService logger, ApplicationDbContext context, EmployeeHelper employeeHelper, UserHelper userHelper, IConfiguration configuration, ILoggingService logger,
IHubContext<MarcoHub> signalR, PermissionServices permission, IProjectServices projectServices, IMapper mapper) IHubContext<MarcoHub> signalR, PermissionServices permission, IProjectServices projectServices, IMapper mapper, GeneralHelper generalHelper)
{ {
_context = context; _context = context;
_userManager = userManager; _userManager = userManager;
_emailSender = emailSender; _emailSender = emailSender;
_employeeHelper = employeeHelper; _employeeHelper = employeeHelper;
_userHelper = userHelper; _userHelper = userHelper;
_generalHelper = generalHelper;
_configuration = configuration; _configuration = configuration;
_logger = logger; _logger = logger;
_signalR = signalR; _signalR = signalR;
@ -191,24 +194,88 @@ namespace MarcoBMS.Services.Controllers
var response = await employeeQuery.Take(10).Select(e => _mapper.Map<BasicEmployeeVM>(e)).ToListAsync(); var response = await employeeQuery.Take(10).Select(e => _mapper.Map<BasicEmployeeVM>(e)).ToListAsync();
return Ok(ApiResponse<object>.SuccessResponse(response, $"{response.Count} records of employees fetched successfully", 200)); return Ok(ApiResponse<object>.SuccessResponse(response, $"{response.Count} records of employees fetched successfully", 200));
} }
[HttpGet]
[Route("search/{name}/{projectid?}")] /// <summary>
public async Task<IActionResult> SearchEmployee(string name, Guid? projectid) /// Retrieves a paginated list of employees assigned to a specified project (if provided),
/// with optional search functionality.
/// Ensures that the logged-in user has necessary permissions before accessing project employees.
/// </summary>
/// <param name="projectId">Optional project identifier to filter employees by project.</param>
/// <param name="searchString">Optional search string to filter employees by name.</param>
/// <param name="pageNumber">Page number for pagination (default = 1).</param>
/// <returns>Paginated list of employees in BasicEmployeeVM format wrapped in ApiResponse.</returns>
[HttpGet("search")]
public async Task<IActionResult> GetEmployeesByProjectBasic(Guid? projectId, [FromQuery] string? searchString,
[FromQuery] int pageNumber = 1, [FromQuery] int pageSize = 10)
{ {
if (!ModelState.IsValid) // Log API entry with context
_logger.LogInfo("Fetching employees. ProjectId: {ProjectId}, SearchString: {SearchString}, PageNumber: {PageNumber}",
projectId ?? Guid.Empty, searchString ?? "", pageNumber);
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
_logger.LogDebug("Logged-in EmployeeId: {EmployeeId}", loggedInEmployee.Id);
// Initialize query scoped by tenant
var employeeQuery = _context.Employees.Where(e => e.TenantId == tenantId);
// Filter by project if projectId is supplied
if (projectId.HasValue && projectId.Value != Guid.Empty)
{ {
var errors = ModelState.Values _logger.LogDebug("Project filter applied. Checking permission for EmployeeId: {EmployeeId} on ProjectId: {ProjectId}",
.SelectMany(v => v.Errors) loggedInEmployee.Id, projectId);
.Select(e => e.ErrorMessage)
.ToList();
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid data", errors, 400));
// Validate project access permission
var hasProjectPermission = await _permission.HasProjectPermission(loggedInEmployee, projectId.Value);
if (!hasProjectPermission)
{
_logger.LogWarning("Access denied. EmployeeId: {EmployeeId} does not have permission for ProjectId: {ProjectId}",
loggedInEmployee.Id, projectId);
return StatusCode(403, ApiResponse<object>.ErrorResponse(
"Access denied",
"User does not have access to view employees for this project",
403));
}
// Employees allocated to the project
var employeeIds = await _context.ProjectAllocations
.Where(pa => pa.ProjectId == projectId && pa.IsActive && pa.TenantId == tenantId)
.Select(pa => pa.EmployeeId)
.ToListAsync();
_logger.LogDebug("Project employees retrieved. Total linked employees found: {Count}", employeeIds.Count);
// Apply project allocation filter
employeeQuery = employeeQuery.Where(e => employeeIds.Contains(e.Id));
} }
var result = await _employeeHelper.SearchEmployeeByProjectId(GetTenantId(), name.ToLower(), projectid);
return Ok(ApiResponse<object>.SuccessResponse(result, "Filter applied.", 200)); // Apply search filter if provided
if (!string.IsNullOrWhiteSpace(searchString))
{
var searchStringLower = searchString.ToLower();
_logger.LogDebug("Search filter applied. Search term: {SearchTerm}", searchStringLower);
employeeQuery = employeeQuery.Where(e =>
(e.FirstName + " " + e.LastName).ToLower().Contains(searchStringLower));
}
// Pagination and Projection (executed in DB)
var employees = await employeeQuery
.Skip((pageNumber - 1) * pageSize)
.Take(pageSize)
.Select(e => _mapper.Map<BasicEmployeeVM>(e))
.ToListAsync();
_logger.LogInfo("Employees fetched successfully. Records returned: {Count}", employees.Count);
return Ok(ApiResponse<object>.SuccessResponse(
employees,
$"{employees.Count} employee records fetched successfully",
200));
} }
[HttpGet] [HttpGet]
[Route("profile/get/{employeeId}")] [Route("profile/get/{employeeId}")]
public async Task<IActionResult> GetEmployeeProfileById(Guid employeeId) public async Task<IActionResult> GetEmployeeProfileById(Guid employeeId)
@ -233,11 +300,6 @@ namespace MarcoBMS.Services.Controllers
return _userHelper.GetTenantId(); return _userHelper.GetTenantId();
} }
//[HttpPost("manage/quick")]
//public async Task<IActionResult> CreateQuickUser([FromBody] CreateQuickUserDto model)
//{
// return Ok("Pending implementation");
//}
[HttpPost("manage")] [HttpPost("manage")]
public async Task<IActionResult> CreateUser([FromBody] CreateUserDto model) public async Task<IActionResult> CreateUser([FromBody] CreateUserDto model)
@ -294,7 +356,12 @@ namespace MarcoBMS.Services.Controllers
TenantId = tenantId TenantId = tenantId
}; };
var isSeatsAvaiable = await _generalHelper.CheckSeatsRemainingAsync(tenantId);
if (!isSeatsAvaiable)
{
_logger.LogWarning("Maximum number of users reached for Tenant {TenantId}", tenantId);
return BadRequest(ApiResponse<object>.ErrorResponse("Maximum number of users reached. Cannot add new user", "Maximum number of users reached. Cannot add new user", 400));
}
// Create Identity User // Create Identity User
var result = await _userManager.CreateAsync(user, "User@123"); var result = await _userManager.CreateAsync(user, "User@123");
if (!result.Succeeded) if (!result.Succeeded)
@ -447,86 +514,104 @@ namespace MarcoBMS.Services.Controllers
} }
[HttpDelete("{id}")] [HttpDelete("{id}")]
public async Task<IActionResult> SuspendEmployee(Guid id) public async Task<IActionResult> SuspendEmployee(Guid id, [FromQuery] bool active = false)
{ {
Guid tenantId = _userHelper.GetTenantId(); Guid tenantId = _userHelper.GetTenantId();
var LoggedEmployee = await _userHelper.GetCurrentEmployeeAsync(); var LoggedEmployee = await _userHelper.GetCurrentEmployeeAsync();
Employee? employee = await _context.Employees.FirstOrDefaultAsync(e => e.Id == id && e.IsActive && e.TenantId == tenantId); Employee? employee = await _context.Employees.FirstOrDefaultAsync(e => e.Id == id && e.TenantId == tenantId);
if (employee != null) if (employee == null)
{ {
if (employee.IsSystem) _logger.LogWarning("Employee with ID {EmploueeId} not found in database", id);
return NotFound(ApiResponse<object>.ErrorResponse("Employee Not found successfully", "Employee Not found successfully", 404));
}
if (employee.IsSystem)
{
_logger.LogWarning("Employee with ID {LoggedEmployeeId} tries to suspend system-defined employee with ID {EmployeeId}", LoggedEmployee.Id, employee.Id);
return BadRequest(ApiResponse<object>.ErrorResponse("System-defined employees cannot be suspended.", "System-defined employees cannot be suspended.", 400));
}
var assignedToTasks = await _context.TaskMembers.Where(t => t.EmployeeId == employee.Id).ToListAsync();
if (assignedToTasks.Count != 0)
{
List<Guid> taskIds = assignedToTasks.Select(t => t.TaskAllocationId).ToList();
var tasks = await _context.TaskAllocations.Where(t => taskIds.Contains(t.Id)).ToListAsync();
foreach (var assignedToTask in assignedToTasks)
{ {
_logger.LogWarning("Employee with ID {LoggedEmployeeId} tries to suspend system-defined employee with ID {EmployeeId}", LoggedEmployee.Id, employee.Id); var task = tasks.Find(t => t.Id == assignedToTask.TaskAllocationId);
return BadRequest(ApiResponse<object>.ErrorResponse("System-defined employees cannot be suspended.", "System-defined employees cannot be suspended.", 400)); if (task != null && task.CompletedTask == 0)
{
_logger.LogWarning("Employee with ID {EmployeeId} is currently assigned to any incomplete task", employee.Id);
return BadRequest(ApiResponse<object>.ErrorResponse("Employee is currently assigned to any incomplete task", "Employee is currently assigned to any incomplete task", 400));
}
} }
else }
var attendance = await _context.Attendes.Where(a => a.EmployeeID == employee.Id && (a.OutTime == null || a.Activity == ATTENDANCE_MARK_TYPE.REQUEST_REGULARIZE)).ToListAsync();
if (attendance.Count != 0)
{
_logger.LogWarning("Employee with ID {EmployeeId} have any pending check-out or regularization requests", employee.Id);
return BadRequest(ApiResponse<object>.ErrorResponse("Employee have any pending check-out or regularization requests", "Employee have any pending check-out or regularization requests", 400));
}
if (active)
{
employee.IsActive = true;
var user = await _context.ApplicationUsers.FirstOrDefaultAsync(u => u.Id == employee.ApplicationUserId);
if (user != null)
{ {
var assignedToTasks = await _context.TaskMembers.Where(t => t.EmployeeId == employee.Id).ToListAsync(); user.IsActive = true;
if (assignedToTasks.Count != 0) _logger.LogInfo("The application user associated with employee ID {EmployeeId} has been actived.", employee.Id);
{
List<Guid> taskIds = assignedToTasks.Select(t => t.TaskAllocationId).ToList();
var tasks = await _context.TaskAllocations.Where(t => taskIds.Contains(t.Id)).ToListAsync();
foreach (var assignedToTask in assignedToTasks)
{
var task = tasks.Find(t => t.Id == assignedToTask.TaskAllocationId);
if (task != null && task.CompletedTask == 0)
{
_logger.LogWarning("Employee with ID {EmployeeId} is currently assigned to any incomplete task", employee.Id);
return BadRequest(ApiResponse<object>.ErrorResponse("Employee is currently assigned to any incomplete task", "Employee is currently assigned to any incomplete task", 400));
}
}
}
var attendance = await _context.Attendes.Where(a => a.EmployeeID == employee.Id && (a.OutTime == null || a.Activity == ATTENDANCE_MARK_TYPE.REQUEST_REGULARIZE)).ToListAsync();
if (attendance.Count != 0)
{
_logger.LogWarning("Employee with ID {EmployeeId} have any pending check-out or regularization requests", employee.Id);
return BadRequest(ApiResponse<object>.ErrorResponse("Employee have any pending check-out or regularization requests", "Employee have any pending check-out or regularization requests", 400));
}
employee.IsActive = false;
var projectAllocations = await _context.ProjectAllocations.Where(a => a.EmployeeId == employee.Id).ToListAsync();
if (projectAllocations.Count != 0)
{
List<ProjectAllocation> allocations = new List<ProjectAllocation>();
foreach (var projectAllocation in projectAllocations)
{
projectAllocation.ReAllocationDate = DateTime.UtcNow;
projectAllocation.IsActive = false;
allocations.Add(projectAllocation);
}
_logger.LogInfo("Employee with ID {EmployeeId} has been removed from all assigned projects.", employee.Id);
}
var user = await _context.ApplicationUsers.FirstOrDefaultAsync(u => u.Id == employee.ApplicationUserId);
if (user != null)
{
user.IsActive = false;
_logger.LogInfo("The application user associated with employee ID {EmployeeId} has been suspended.", employee.Id);
var refreshTokens = await _context.RefreshTokens.AsNoTracking().Where(t => t.UserId == user.Id).ToListAsync();
if (refreshTokens.Count != 0)
{
_context.RefreshTokens.RemoveRange(refreshTokens);
_logger.LogInfo("Refresh tokens associated with employee ID {EmployeeId} has been removed.", employee.Id);
}
}
var roleMapping = await _context.EmployeeRoleMappings.AsNoTracking().Where(r => r.EmployeeId == employee.Id).ToListAsync();
if (roleMapping.Count != 0)
{
_context.EmployeeRoleMappings.RemoveRange(roleMapping);
_logger.LogInfo("Application role mapping associated with employee ID {EmployeeId} has been removed.", employee.Id);
}
await _context.SaveChangesAsync();
_logger.LogInfo("Employee with ID {EmployeId} Deleted successfully", employee.Id);
var notification = new { LoggedInUserId = LoggedEmployee.Id, Keyword = "Employee", EmployeeId = employee.Id };
await _signalR.Clients.All.SendAsync("NotificationEventHandler", notification);
} }
_logger.LogInfo("Employee with ID {EmployeId} Actived successfully", employee.Id);
} }
else else
{ {
_logger.LogWarning("Employee with ID {EmploueeId} not found in database", id); employee.IsActive = false;
var projectAllocations = await _context.ProjectAllocations.Where(a => a.EmployeeId == employee.Id).ToListAsync();
if (projectAllocations.Count != 0)
{
List<ProjectAllocation> allocations = new List<ProjectAllocation>();
foreach (var projectAllocation in projectAllocations)
{
projectAllocation.ReAllocationDate = DateTime.UtcNow;
projectAllocation.IsActive = false;
allocations.Add(projectAllocation);
}
_logger.LogInfo("Employee with ID {EmployeeId} has been removed from all assigned projects.", employee.Id);
}
var user = await _context.ApplicationUsers.FirstOrDefaultAsync(u => u.Id == employee.ApplicationUserId);
if (user != null)
{
user.IsActive = false;
_logger.LogInfo("The application user associated with employee ID {EmployeeId} has been suspended.", employee.Id);
var refreshTokens = await _context.RefreshTokens.AsNoTracking().Where(t => t.UserId == user.Id).ToListAsync();
if (refreshTokens.Count != 0)
{
_context.RefreshTokens.RemoveRange(refreshTokens);
_logger.LogInfo("Refresh tokens associated with employee ID {EmployeeId} has been removed.", employee.Id);
}
}
var roleMapping = await _context.EmployeeRoleMappings.AsNoTracking().Where(r => r.EmployeeId == employee.Id).ToListAsync();
if (roleMapping.Count != 0)
{
_context.EmployeeRoleMappings.RemoveRange(roleMapping);
_logger.LogInfo("Application role mapping associated with employee ID {EmployeeId} has been removed.", employee.Id);
}
_logger.LogInfo("Employee with ID {EmployeId} Deleted successfully", employee.Id);
} }
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateException ex)
{
_logger.LogError(ex, "Exception Occured While activting/deactivting employee {EmployeeId}", employee.Id);
return StatusCode(500, ApiResponse<object>.ErrorResponse("Internal Error Occured", "Error occured while saving the entity", 500));
}
var notification = new { LoggedInUserId = LoggedEmployee.Id, Keyword = "Employee", EmployeeId = employee.Id };
await _signalR.Clients.All.SendAsync("NotificationEventHandler", notification);
return Ok(ApiResponse<object>.SuccessResponse(new { }, "Employee Suspended successfully", 200)); return Ok(ApiResponse<object>.SuccessResponse(new { }, "Employee Suspended successfully", 200));
} }
private static Employee GetNewEmployeeModel(CreateUserDto model, Guid TenantId, string ApplicationUserId) private static Employee GetNewEmployeeModel(CreateUserDto model, Guid TenantId, string ApplicationUserId)

View File

@ -1,9 +1,11 @@
using Marco.Pms.DataAccess.Data; using AutoMapper;
using Marco.Pms.Model.Entitlements; using Marco.Pms.DataAccess.Data;
using Marco.Pms.Model.Mapper;
using Marco.Pms.Model.Master; using Marco.Pms.Model.Master;
using Marco.Pms.Model.Utilities; using Marco.Pms.Model.Utilities;
using Marco.Pms.Model.ViewModels.Master; using Marco.Pms.Model.ViewModels.Master;
using Marco.Pms.Services.Helpers;
using MarcoBMS.Services.Helpers;
using MarcoBMS.Services.Service;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
@ -15,43 +17,102 @@ namespace MarcoBMS.Services.Controllers
public class FeatureController : ControllerBase public class FeatureController : ControllerBase
{ {
private readonly ApplicationDbContext _context; private readonly ApplicationDbContext _context;
private readonly GeneralHelper _generalHelper;
//private readonly UserHelper _userHelper;
private readonly IMapper _mapper;
private readonly ILoggingService _logger;
private readonly Guid tenantId;
public FeatureController(ApplicationDbContext context, GeneralHelper generalHelper, UserHelper userHelper, IMapper mapper, ILoggingService logger)
public FeatureController(ApplicationDbContext context)
{ {
_context = context; _context = context;
_generalHelper = generalHelper;
//_userHelper = userHelper;
_mapper = mapper;
_logger = logger;
tenantId = userHelper.GetTenantId();
} }
/// <summary>
/// Converts FeaturePermissions from Feature entity into FeaturePermissionVM collection.
/// </summary>
/// <param name="model">Feature entity from DB</param>
/// <returns>Collection of FeaturePermissionVM, ordered by Name</returns>
private ICollection<FeaturePermissionVM> GetFeaturePermissionVMs(Feature model) private ICollection<FeaturePermissionVM> GetFeaturePermissionVMs(Feature model)
{ {
ICollection<FeaturePermissionVM> features = []; if (model.FeaturePermissions == null || !model.FeaturePermissions.Any())
if (model.FeaturePermissions != null)
{ {
foreach (FeaturePermission permission in model.FeaturePermissions) _logger.LogInfo("No feature permissions found for Feature: {FeatureId}", model.Id);
{ return new List<FeaturePermissionVM>();
FeaturePermissionVM item = permission.ToFeaturePermissionVMFromFeaturePermission();
features.Add(item);
}
} }
return features.OrderBy(f => f.Name).ToList();
// Project and order feature permissions
var features = model.FeaturePermissions
.Select(p => _mapper.Map<FeaturePermissionVM>(p))
.OrderBy(f => f.Name)
.ToList();
_logger.LogDebug("Mapped {Count} feature permissions for Feature: {FeatureId}", features.Count, model.Id);
return features;
} }
/// <summary>
/// API endpoint to fetch all features and their permissions for the given tenant.
/// </summary>
[HttpGet] [HttpGet]
public async Task<IActionResult> GetAllFeatures() public async Task<IActionResult> GetAllFeaturesAsync()
{ {
var roles = await _context.Features.Include("FeaturePermissions").Include("Module").ToListAsync(); try
var rolesVM = roles.Select(c => new FeatureVM()
{ {
Id = c.Id, _logger.LogInfo("Fetching all features for tenant: {TenantId}", tenantId);
Name = c.Name,
Description = c.Description, var featureQuery = _context.Features
FeaturePermissions = GetFeaturePermissionVMs(c), .AsNoTracking(); // Optimization: Read-only query
ModuleId = c.ModuleId, if (tenantId != Guid.Parse("b3466e83-7e11-464c-b93a-daf047838b26"))
ModuleName = c.Module != null ? c.Module.Name : string.Empty, {
IsActive = c.IsActive
}).OrderBy(f => f.Name).ToList(); // Step 1: Get tenant-specific FeatureIds
return Ok(ApiResponse<object>.SuccessResponse(rolesVM, "Success.", 200)); List<Guid> featureIds = await _generalHelper.GetFeatureIdsByTenentIdAsync(tenantId);
if (featureIds == null || !featureIds.Any())
{
_logger.LogWarning("No features found for tenant: {TenantId}", tenantId);
return Ok(ApiResponse<object>.SuccessResponse(new List<FeatureVM>(), "No features found.", 200));
}
_logger.LogDebug("Retrieved {Count} feature IDs for tenant: {TenantId}", featureIds.Count, tenantId);
// Step 2: Query Features with related FeaturePermissions & Module
featureQuery = featureQuery.Where(f => featureIds.Contains(f.Id));
}
var features = await featureQuery
.Include(f => f.FeaturePermissions)
.Include(f => f.Module).ToListAsync();
_logger.LogDebug("Fetched {Count} features from DB for tenant: {TenantId}", features.Count, tenantId);
// Step 3: Map features to ViewModels
var featureVMs = features
.Select(c => new FeatureVM
{
Id = c.Id,
Name = c.Name,
Description = c.Description,
FeaturePermissions = GetFeaturePermissionVMs(c),
ModuleId = c.ModuleId,
ModuleName = c.Module?.Name ?? string.Empty,
IsActive = c.IsActive
})
.OrderBy(f => f.Name)
.ToList();
_logger.LogInfo("Returning {Count} features for tenant: {TenantId}", featureVMs.Count, tenantId);
return Ok(ApiResponse<object>.SuccessResponse(featureVMs, "Success.", 200));
}
catch (Exception ex)
{
_logger.LogError(ex, "Error while fetching features for tenant: {TenantId}", tenantId);
return StatusCode(500, ApiResponse<object>.ErrorResponse("An unexpected error occurred.", 500));
}
} }
} }
} }

View File

@ -37,7 +37,7 @@ namespace Marco.Pms.Services.Controllers
return Ok(ApiResponse<object>.SuccessResponse(industries, "Success.", 200)); return Ok(ApiResponse<object>.SuccessResponse(industries, "Success.", 200));
} }
[HttpPost("inquiry")] [HttpPost("enquire")]
public async Task<IActionResult> RequestDemo([FromBody] InquiryDto inquiryDto) public async Task<IActionResult> RequestDemo([FromBody] InquiryDto inquiryDto)
{ {
Inquiries inquiry = inquiryDto.ToInquiriesFromInquiriesDto(); Inquiries inquiry = inquiryDto.ToInquiriesFromInquiriesDto();

View File

@ -276,6 +276,23 @@ namespace MarcoBMS.Services.Controllers
return StatusCode(response.StatusCode, response); return StatusCode(response.StatusCode, response);
} }
[HttpGet("allocation-histery/{employeeId}")]
public async Task<IActionResult> GetProjectByEmployeeBasic([FromRoute] Guid employeeId)
{
// --- Step 1: Input Validation ---
if (!ModelState.IsValid)
{
var errors = ModelState.Values.SelectMany(v => v.Errors).Select(e => e.ErrorMessage).ToList();
_logger.LogWarning("Get project list by employee Id called with invalid model state \n Errors: {Errors}", string.Join(", ", errors));
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid request data provided.", errors, 400));
}
// --- Step 2: Prepare data without I/O ---
Employee loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
var response = await _projectServices.GetProjectByEmployeeBasicAsync(employeeId, tenantId, loggedInEmployee);
return StatusCode(response.StatusCode, response);
}
[HttpPost("assign-projects/{employeeId}")] [HttpPost("assign-projects/{employeeId}")]
public async Task<IActionResult> AssigneProjectsToEmployee([FromBody] List<ProjectsAllocationDto> projectAllocationDtos, [FromRoute] Guid employeeId) public async Task<IActionResult> AssigneProjectsToEmployee([FromBody] List<ProjectsAllocationDto> projectAllocationDtos, [FromRoute] Guid employeeId)
{ {
@ -338,6 +355,25 @@ namespace MarcoBMS.Services.Controllers
var response = await _projectServices.GetWorkItemsAsync(workAreaId, tenantId, loggedInEmployee); var response = await _projectServices.GetWorkItemsAsync(workAreaId, tenantId, loggedInEmployee);
return StatusCode(response.StatusCode, response); return StatusCode(response.StatusCode, response);
} }
[HttpGet("tasks-employee/{employeeId}")]
public async Task<IActionResult> GetTasksByEmployee(Guid employeeId, [FromQuery] DateTime? fromDate, DateTime? toDate)
{
// --- Step 1: Input Validation ---
if (!ModelState.IsValid)
{
var errors = ModelState.Values.SelectMany(v => v.Errors).Select(e => e.ErrorMessage).ToList();
_logger.LogWarning("Get Work Items by employeeId called with invalid model state \n Errors: {Errors}", string.Join(", ", errors));
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid request data provided.", errors, 400));
}
if (!toDate.HasValue) toDate = DateTime.UtcNow;
if (!fromDate.HasValue) fromDate = toDate.Value.AddDays(-7);
// --- Step 2: Prepare data without I/O ---
Employee loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
var response = await _projectServices.GetTasksByEmployeeAsync(employeeId, fromDate.Value, toDate.Value, tenantId, loggedInEmployee);
return StatusCode(response.StatusCode, response);
}
#endregion #endregion

View File

@ -1,5 +1,4 @@
using System.Data; using Marco.Pms.DataAccess.Data;
using Marco.Pms.DataAccess.Data;
using Marco.Pms.Model.Dtos.Employees; using Marco.Pms.Model.Dtos.Employees;
using Marco.Pms.Model.Dtos.Roles; using Marco.Pms.Model.Dtos.Roles;
using Marco.Pms.Model.Employees; using Marco.Pms.Model.Employees;
@ -11,12 +10,13 @@ using Marco.Pms.Model.ViewModels;
using Marco.Pms.Model.ViewModels.Master; using Marco.Pms.Model.ViewModels.Master;
using Marco.Pms.Model.ViewModels.Roles; using Marco.Pms.Model.ViewModels.Roles;
using Marco.Pms.Services.Helpers; using Marco.Pms.Services.Helpers;
using Marco.Pms.Services.Service;
using MarcoBMS.Services.Helpers; using MarcoBMS.Services.Helpers;
using MarcoBMS.Services.Service; using MarcoBMS.Services.Service;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using System.Data;
#nullable disable #nullable disable
namespace MarcoBMS.Services.Controllers namespace MarcoBMS.Services.Controllers
{ {
@ -28,15 +28,15 @@ namespace MarcoBMS.Services.Controllers
private readonly ApplicationDbContext _context; private readonly ApplicationDbContext _context;
private readonly RolesHelper _rolesHelper; private readonly RolesHelper _rolesHelper;
private readonly UserHelper _userHelper; private readonly UserHelper _userHelper;
private readonly UserManager<ApplicationUser> _userManager; private readonly PermissionServices _permissionService;
private readonly ILoggingService _logger; private readonly ILoggingService _logger;
private readonly CacheUpdateHelper _cache; private readonly CacheUpdateHelper _cache;
public RolesController(UserManager<ApplicationUser> userManager, ApplicationDbContext context, RolesHelper rolesHelper, UserHelper userHelper, ILoggingService logger, public RolesController(PermissionServices permissionServices, ApplicationDbContext context, RolesHelper rolesHelper, UserHelper userHelper, ILoggingService logger,
CacheUpdateHelper cache) CacheUpdateHelper cache)
{ {
_context = context; _context = context;
_userManager = userManager; _permissionService = permissionServices;
_rolesHelper = rolesHelper; _rolesHelper = rolesHelper;
_userHelper = userHelper; _userHelper = userHelper;
_logger = logger; _logger = logger;
@ -213,12 +213,17 @@ namespace MarcoBMS.Services.Controllers
} }
Guid TenantId = GetTenantId(); Guid TenantId = GetTenantId();
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
if (createRoleDto.FeaturesPermission == null || (createRoleDto.FeaturesPermission != null && createRoleDto.FeaturesPermission.Count == 0)) if (createRoleDto.FeaturesPermission == null || (createRoleDto.FeaturesPermission != null && createRoleDto.FeaturesPermission.Count == 0))
{ {
return BadRequest(ApiResponse<object>.ErrorResponse("Feature Permission is required.", "Feature Permission is required.", 400)); return BadRequest(ApiResponse<object>.ErrorResponse("Feature Permission is required.", "Feature Permission is required.", 400));
} }
var hasManageMasterPermission = await _permissionService.HasPermission(PermissionsMaster.ManageMasters, loggedInEmployee.Id);
if (!hasManageMasterPermission)
{
return StatusCode(403, ApiResponse<object>.SuccessResponse("Access Denied", "User do not have permission for this action", 403));
}
bool roleExists = _context.ApplicationRoles bool roleExists = _context.ApplicationRoles
.Any(r => r.TenantId == TenantId && r.Role.ToLower() == createRoleDto.Role.ToLower());// assuming role name is unique per tenant .Any(r => r.TenantId == TenantId && r.Role.ToLower() == createRoleDto.Role.ToLower());// assuming role name is unique per tenant
if (roleExists) if (roleExists)
@ -228,14 +233,19 @@ namespace MarcoBMS.Services.Controllers
ApplicationRole role = createRoleDto.ToApplicationRoleFromCreateDto(TenantId); ApplicationRole role = createRoleDto.ToApplicationRoleFromCreateDto(TenantId);
_context.ApplicationRoles.Add(role); _context.ApplicationRoles.Add(role);
var hasPermission = await _permissionService.HasPermission(PermissionsMaster.ManageTenants, loggedInEmployee.Id);
foreach (var permission in createRoleDto.FeaturesPermission) foreach (var permission in createRoleDto.FeaturesPermission)
{ {
var item = new RolePermissionMappings() { ApplicationRoleId = role.Id, FeaturePermissionId = permission.Id }; if (!hasPermission &&
bool assigned = _context.RolePermissionMappings.Any(c => c.ApplicationRoleId == role.Id && c.FeaturePermissionId == permission.Id); permission.Id != PermissionsMaster.ManageTenants)
if (permission.IsEnabled && !assigned) {
_context.RolePermissionMappings.Add(item); var item = new RolePermissionMappings() { ApplicationRoleId = role.Id, FeaturePermissionId = permission.Id };
else bool assigned = _context.RolePermissionMappings.Any(c => c.ApplicationRoleId == role.Id && c.FeaturePermissionId == permission.Id);
_context.RolePermissionMappings.Remove(item); if (permission.IsEnabled && !assigned)
_context.RolePermissionMappings.Add(item);
else
_context.RolePermissionMappings.Remove(item);
}
} }
await _context.SaveChangesAsync(); await _context.SaveChangesAsync();

File diff suppressed because it is too large Load Diff

View File

@ -6,8 +6,11 @@ using Marco.Pms.Model.Utilities;
using Marco.Pms.Model.ViewModels.Employee; using Marco.Pms.Model.ViewModels.Employee;
using Marco.Pms.Services.Service.ServiceInterfaces; using Marco.Pms.Services.Service.ServiceInterfaces;
using MarcoBMS.Services.Helpers; using MarcoBMS.Services.Helpers;
using MarcoBMS.Services.Service;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using System.Net.Mail;
namespace MarcoBMS.Services.Controllers namespace MarcoBMS.Services.Controllers
{ {
@ -18,14 +21,17 @@ namespace MarcoBMS.Services.Controllers
public class UserController : ControllerBase public class UserController : ControllerBase
{ {
private readonly UserHelper _userHelper; private readonly UserHelper _userHelper;
private readonly UserManager<ApplicationUser> _userManager;
private readonly EmployeeHelper _employeeHelper; private readonly EmployeeHelper _employeeHelper;
private readonly ILoggingService _logger;
private readonly IProjectServices _projectServices; private readonly IProjectServices _projectServices;
private readonly RolesHelper _rolesHelper; private readonly RolesHelper _rolesHelper;
public UserController(EmployeeHelper employeeHelper, IProjectServices projectServices, UserHelper userHelper, RolesHelper rolesHelper) public UserController(EmployeeHelper employeeHelper, UserManager<ApplicationUser> userManager, ILoggingService logger, IProjectServices projectServices, UserHelper userHelper, RolesHelper rolesHelper)
{ {
_userManager = userManager;
_userHelper = userHelper; _userHelper = userHelper;
_logger = logger;
_employeeHelper = employeeHelper; _employeeHelper = employeeHelper;
_projectServices = projectServices; _projectServices = projectServices;
_rolesHelper = rolesHelper; _rolesHelper = rolesHelper;
@ -81,5 +87,44 @@ namespace MarcoBMS.Services.Controllers
return Ok(ApiResponse<object>.SuccessResponse(profile, "Success", 200)); return Ok(ApiResponse<object>.SuccessResponse(profile, "Success", 200));
} }
[HttpGet("email/{email}")]
public async Task<IActionResult> GetUserByEmail(string email)
{
var isvalid = IsValidEmail(email);
if (!isvalid)
{
_logger.LogWarning("User provided invalid email address");
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid email", "Invalid email", 400));
}
var user = await _userManager.FindByEmailAsync(email);
if (user == null)
{
_logger.LogInfo("User with email {Email} not found in ASP.NET users table", email);
return Ok(ApiResponse<object>.SuccessResponse(true, "User not exists", 200));
}
else
{
_logger.LogInfo("User with email {Email} founded in ASP.NET users table", email);
return Ok(ApiResponse<object>.SuccessResponse(false, "User exists", 200));
}
}
private static bool IsValidEmail(string email)
{
if (string.IsNullOrWhiteSpace(email))
return false;
try
{
var addr = new MailAddress(email);
return addr.Address == email.Trim();
}
catch
{
return false;
}
}
} }
} }

View File

@ -853,6 +853,18 @@ namespace Marco.Pms.Services.Helpers
_logger.LogWarning("Error occured while deleting Application role {RoleId} from Cache for employee {EmployeeId}: {Error}", roleId, employeeId, ex.Message); _logger.LogWarning("Error occured while deleting Application role {RoleId} from Cache for employee {EmployeeId}: {Error}", roleId, employeeId, ex.Message);
} }
} }
public async Task ClearAllEmployeesFromCacheByEmployeeIds(List<Guid> employeeIds)
{
try
{
var stringEmployeeIds = employeeIds.Select(e => e.ToString()).ToList();
var response = await _employeeCache.ClearAllEmployeesFromCacheByEmployeeIds(stringEmployeeIds);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error occured while deleting all employees from Cache");
}
}
public async Task ClearAllEmployees() public async Task ClearAllEmployees()
{ {
try try

View File

@ -20,7 +20,7 @@ namespace MarcoBMS.Services.Helpers
public async Task<Employee> GetEmployeeByID(Guid EmployeeID) public async Task<Employee> GetEmployeeByID(Guid EmployeeID)
{ {
return await _context.Employees.Include(e => e.JobRole).FirstOrDefaultAsync(e => e.Id == EmployeeID && e.IsActive == true) ?? new Employee { }; return await _context.Employees.Include(e => e.JobRole).FirstOrDefaultAsync(e => e.Id == EmployeeID) ?? new Employee { };
} }
public async Task<Employee> GetEmployeeByApplicationUserID(string ApplicationUserID) public async Task<Employee> GetEmployeeByApplicationUserID(string ApplicationUserID)
@ -29,7 +29,7 @@ namespace MarcoBMS.Services.Helpers
{ {
var result = await _context.Employees.Where(c => c.ApplicationUserId == ApplicationUserID && c.IsActive == true).ToListAsync(); var result = await _context.Employees.Where(c => c.ApplicationUserId == ApplicationUserID && c.IsActive == true).ToListAsync();
return await _context.Employees.Where(c => c.ApplicationUserId == ApplicationUserID && c.IsActive == true).SingleOrDefaultAsync() ?? new Employee { }; return await _context.Employees.Include(e => e.ApplicationUser).Where(c => c.ApplicationUserId == ApplicationUserID && c.ApplicationUser != null && c.IsActive == true).SingleOrDefaultAsync() ?? new Employee { };
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@ -1,4 +1,5 @@
using Marco.Pms.DataAccess.Data; using Marco.Pms.DataAccess.Data;
using Marco.Pms.Helpers.Utility;
using Marco.Pms.Model.MongoDBModels.Masters; using Marco.Pms.Model.MongoDBModels.Masters;
using Marco.Pms.Model.MongoDBModels.Project; using Marco.Pms.Model.MongoDBModels.Project;
using MarcoBMS.Services.Service; using MarcoBMS.Services.Service;
@ -11,13 +12,16 @@ namespace Marco.Pms.Services.Helpers
private readonly IDbContextFactory<ApplicationDbContext> _dbContextFactory; private readonly IDbContextFactory<ApplicationDbContext> _dbContextFactory;
private readonly ApplicationDbContext _context; // Keeping this for direct scoped context use where appropriate private readonly ApplicationDbContext _context; // Keeping this for direct scoped context use where appropriate
private readonly ILoggingService _logger; private readonly ILoggingService _logger;
private readonly FeatureDetailsHelper _featureDetailsHelper;
public GeneralHelper(IDbContextFactory<ApplicationDbContext> dbContextFactory, public GeneralHelper(IDbContextFactory<ApplicationDbContext> dbContextFactory,
ApplicationDbContext context, ApplicationDbContext context,
ILoggingService logger) ILoggingService logger,
FeatureDetailsHelper featureDetailsHelper)
{ {
_dbContextFactory = dbContextFactory ?? throw new ArgumentNullException(nameof(dbContextFactory)); _dbContextFactory = dbContextFactory ?? throw new ArgumentNullException(nameof(dbContextFactory));
_context = context ?? throw new ArgumentNullException(nameof(context)); _context = context ?? throw new ArgumentNullException(nameof(context));
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); _logger = logger ?? throw new ArgumentNullException(nameof(logger));
_featureDetailsHelper = featureDetailsHelper ?? throw new ArgumentNullException(nameof(featureDetailsHelper));
} }
public async Task<List<BuildingMongoDB>> GetProjectInfraFromDB(Guid projectId) public async Task<List<BuildingMongoDB>> GetProjectInfraFromDB(Guid projectId)
{ {
@ -211,5 +215,161 @@ namespace Marco.Pms.Services.Helpers
return new List<WorkItemMongoDB>(); return new List<WorkItemMongoDB>();
} }
} }
/// <summary>
/// Retrieves all enabled feature IDs for a given tenant based on their active subscription.
/// </summary>
/// <param name="tenantId">The unique identifier of the tenant.</param>
/// <returns>A list of feature IDs available for the tenant.</returns>
public async Task<List<Guid>> GetFeatureIdsByTenentIdAsync(Guid tenantId)
{
try
{
_logger.LogInfo("Fetching feature IDs for tenant: {TenantId}", tenantId);
// Step 1: Get active tenant subscription with plan
var tenantSubscription = await _context.TenantSubscriptions
.Include(ts => ts.Plan)
.AsNoTracking() // Optimization: Read-only query, no need to track
.FirstOrDefaultAsync(ts =>
ts.TenantId == tenantId &&
ts.Plan != null &&
!ts.IsCancelled &&
ts.EndDate.Date >= DateTime.UtcNow.Date); // FIX: Subscription should not be expired
if (tenantSubscription == null)
{
_logger.LogWarning("No active subscription found for tenant: {TenantId}", tenantId);
return new List<Guid>();
}
_logger.LogDebug("Active subscription found for tenant: {TenantId}, PlanId: {PlanId}",
tenantId, tenantSubscription.Plan!.Id);
var featureIds = new List<Guid> { new Guid("2f3509b7-160d-410a-b9b6-daadd96c986d"), new Guid("be3b3afc-6ccf-4566-b9b6-aafcb65546be") };
// Step 2: Get feature details from Plan
var featureDetails = await _featureDetailsHelper.GetFeatureDetails(tenantSubscription.Plan!.FeaturesId);
if (featureDetails == null)
{
_logger.LogWarning("No feature details found for tenant: {TenantId}, PlanId: {PlanId}",
tenantId, tenantSubscription.Plan!.Id);
return new List<Guid>();
}
// Step 3: Collect all enabled feature IDs from modules
if (featureDetails.Modules?.Attendance?.Enabled == true)
{
featureIds.AddRange(featureDetails.Modules.Attendance.FeatureId);
_logger.LogDebug("Added Attendance module features for tenant: {TenantId}", tenantId);
}
if (featureDetails.Modules?.ProjectManagement?.Enabled == true)
{
featureIds.AddRange(featureDetails.Modules.ProjectManagement.FeatureId);
_logger.LogDebug("Added Project Management module features for tenant: {TenantId}", tenantId);
}
if (featureDetails.Modules?.Directory?.Enabled == true)
{
featureIds.AddRange(featureDetails.Modules.Directory.FeatureId);
_logger.LogDebug("Added Directory module features for tenant: {TenantId}", tenantId);
}
if (featureDetails.Modules?.Expense?.Enabled == true)
{
featureIds.AddRange(featureDetails.Modules.Expense.FeatureId);
_logger.LogDebug("Added Expense module features for tenant: {TenantId}", tenantId);
}
_logger.LogInfo("Returning {Count} feature IDs for tenant: {TenantId}", featureIds.Count, tenantId);
return featureIds.Distinct().ToList();
}
catch (Exception ex)
{
// Step 4: Handle unexpected errors
_logger.LogError(ex, "Error retrieving feature IDs for tenant: {TenantId}", tenantId);
return new List<Guid>();
}
}
/// <summary>
/// Checks whether the tenant still has available seats (MaxUsers not exceeded).
/// </summary>
/// <param name="tenantId">The ID of the tenant to check.</param>
/// <returns>True if seats are available; otherwise false.</returns>
public async Task<bool> CheckSeatsRemainingAsync(Guid tenantId)
{
_logger.LogInfo("Checking seats remaining for TenantId: {TenantId}", tenantId);
try
{
// Run both queries concurrently
var totalSeatsTask = GetMaxSeatsAsync(tenantId);
var totalSeatsTakenTask = GetActiveEmployeesCountAsync(tenantId);
await Task.WhenAll(totalSeatsTask, totalSeatsTakenTask);
var totalSeats = await totalSeatsTask;
var totalSeatsTaken = await totalSeatsTakenTask;
_logger.LogInfo(
"TenantId: {TenantId} | TotalSeats: {TotalSeats} | SeatsTaken: {SeatsTaken}",
tenantId, totalSeats, totalSeatsTaken);
bool seatsAvailable = totalSeats >= totalSeatsTaken;
_logger.LogDebug("TenantId: {TenantId} | Seats Available: {SeatsAvailable}",
tenantId, seatsAvailable);
return seatsAvailable;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error checking seats for TenantId: {TenantId}", tenantId);
throw;
}
}
/// <summary>
/// Retrieves the maximum number of allowed seats (MaxUsers) for a tenant.
/// </summary>
private async Task<double> GetMaxSeatsAsync(Guid tenantId)
{
_logger.LogDebug("Fetching maximum seats for TenantId: {TenantId}", tenantId);
await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
var maxSeats = await dbContext.TenantSubscriptions
.Where(ts => ts.TenantId == tenantId && !ts.IsCancelled)
.Select(ts => ts.MaxUsers)
.FirstOrDefaultAsync();
_logger.LogDebug("TenantId: {TenantId} | MaxSeats: {MaxSeats}", tenantId, maxSeats);
return maxSeats;
}
/// <summary>
/// Counts the number of active employees for a tenant.
/// </summary>
private async Task<int> GetActiveEmployeesCountAsync(Guid tenantId)
{
_logger.LogDebug("Counting active employees for TenantId: {TenantId}", tenantId);
await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
var activeEmployees = await dbContext.Employees
.Where(e => e.TenantId == tenantId && e.IsActive)
.CountAsync();
_logger.LogDebug("TenantId: {TenantId} | ActiveEmployees: {ActiveEmployees}", tenantId, activeEmployees);
return activeEmployees;
}
} }
} }

View File

@ -70,12 +70,13 @@ namespace MarcoBMS.Services.Helpers
// --- Step 3: Execute the main query on the main thread using its original context --- // --- Step 3: Execute the main query on the main thread using its original context ---
// This is now safe because the background task is using a different DbContext instance. // This is now safe because the background task is using a different DbContext instance.
var permissions = await ( var roleIds = await employeeRoleIdsQuery.ToListAsync();
from rpm in _context.RolePermissionMappings
join fp in _context.FeaturePermissions.Include(f => f.Feature) var permissionIds = await _context.RolePermissionMappings
on rpm.FeaturePermissionId equals fp.Id .Where(rp => roleIds.Contains(rp.ApplicationRoleId)).Select(rp => rp.FeaturePermissionId).ToListAsync();
where employeeRoleIdsQuery.Contains(rpm.ApplicationRoleId) && fp.IsEnabled == true
select fp) var permissions = await _context.FeaturePermissions.Include(f => f.Feature)
.Where(fp => permissionIds.Contains(fp.Id))
.Distinct() .Distinct()
.ToListAsync(); .ToListAsync();

View File

@ -1,9 +1,10 @@
using System.Security.Claims; using Marco.Pms.DataAccess.Data;
using Marco.Pms.DataAccess.Data;
using Marco.Pms.Model.Employees; using Marco.Pms.Model.Employees;
using Marco.Pms.Model.Entitlements; using Marco.Pms.Model.Entitlements;
using Marco.Pms.Model.TenantModels;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using System.Security.Claims;
namespace MarcoBMS.Services.Helpers namespace MarcoBMS.Services.Helpers
{ {
@ -25,6 +26,15 @@ namespace MarcoBMS.Services.Helpers
var tenant = _httpContextAccessor.HttpContext?.User.FindFirst("TenantId")?.Value; var tenant = _httpContextAccessor.HttpContext?.User.FindFirst("TenantId")?.Value;
return (tenant != null ? Guid.Parse(tenant) : Guid.Empty); return (tenant != null ? Guid.Parse(tenant) : Guid.Empty);
} }
public async Task<Tenant?> GetCurrentTenant()
{
var tenantId = _httpContextAccessor.HttpContext?.User.FindFirst("TenantId")?.Value;
if (tenantId != null)
{
return await _context.Tenants.FirstOrDefaultAsync(t => t.Id == Guid.Parse(tenantId));
}
return null;
}
public async Task<IdentityUser?> GetCurrentUserAsync() public async Task<IdentityUser?> GetCurrentUserAsync()
{ {

View File

@ -1,10 +1,14 @@
using AutoMapper; using AutoMapper;
using Marco.Pms.Model.Directory; using Marco.Pms.Model.Directory;
using Marco.Pms.Model.Dtos.Directory; using Marco.Pms.Model.Dtos.Directory;
using Marco.Pms.Model.AppMenu;
using Marco.Pms.Model.Dtos.AppMenu;
using Marco.Pms.Model.Dtos.Expenses; using Marco.Pms.Model.Dtos.Expenses;
using Marco.Pms.Model.Dtos.Master; using Marco.Pms.Model.Dtos.Master;
using Marco.Pms.Model.Dtos.Project; using Marco.Pms.Model.Dtos.Project;
using Marco.Pms.Model.Dtos.Tenant;
using Marco.Pms.Model.Employees; using Marco.Pms.Model.Employees;
using Marco.Pms.Model.Entitlements;
using Marco.Pms.Model.Expenses; using Marco.Pms.Model.Expenses;
using Marco.Pms.Model.Master; using Marco.Pms.Model.Master;
using Marco.Pms.Model.MongoDBModels; using Marco.Pms.Model.MongoDBModels;
@ -13,6 +17,8 @@ using Marco.Pms.Model.MongoDBModels.Expenses;
using Marco.Pms.Model.MongoDBModels.Masters; using Marco.Pms.Model.MongoDBModels.Masters;
using Marco.Pms.Model.MongoDBModels.Project; using Marco.Pms.Model.MongoDBModels.Project;
using Marco.Pms.Model.Projects; using Marco.Pms.Model.Projects;
using Marco.Pms.Model.TenantModels;
using Marco.Pms.Model.TenantModels.MongoDBModel;
using Marco.Pms.Model.ViewModels.Activities; using Marco.Pms.Model.ViewModels.Activities;
using Marco.Pms.Model.ViewModels.Directory; using Marco.Pms.Model.ViewModels.Directory;
using Marco.Pms.Model.ViewModels.DocumentManager; using Marco.Pms.Model.ViewModels.DocumentManager;
@ -21,6 +27,7 @@ using Marco.Pms.Model.ViewModels.Expanses;
using Marco.Pms.Model.ViewModels.Expenses; using Marco.Pms.Model.ViewModels.Expenses;
using Marco.Pms.Model.ViewModels.Master; using Marco.Pms.Model.ViewModels.Master;
using Marco.Pms.Model.ViewModels.Projects; using Marco.Pms.Model.ViewModels.Projects;
using Marco.Pms.Model.ViewModels.Tenant;
namespace Marco.Pms.Services.MappingProfiles namespace Marco.Pms.Services.MappingProfiles
{ {
@ -28,6 +35,67 @@ namespace Marco.Pms.Services.MappingProfiles
{ {
public MappingProfile() public MappingProfile()
{ {
#region ======================================================= Tenant =======================================================
CreateMap<Tenant, TenantVM>();
CreateMap<Tenant, TenantListVM>();
CreateMap<Tenant, TenantDetailsVM>();
CreateMap<CreateTenantDto, Tenant>()
.ForMember(
dest => dest.ContactName,
opt => opt.MapFrom(src => $"{src.FirstName} {src.LastName}")
)
.ForMember(
dest => dest.Name,
opt => opt.MapFrom(src => src.OrganizationName)
);
CreateMap<UpdateTenantDto, Tenant>()
.ForMember(
dest => dest.ContactName,
opt => opt.MapFrom(src => $"{src.FirstName} {src.LastName}")
);
CreateMap<SubscriptionPlanDetails, SubscriptionPlanVM>()
.ForMember(
dest => dest.PlanName,
opt => opt.MapFrom(src => src.Plan != null ? src.Plan.PlanName : "")
)
.ForMember(
dest => dest.Description,
opt => opt.MapFrom(src => src.Plan != null ? src.Plan.Description : "")
);
CreateMap<TenantSubscriptions, SubscriptionPlanDetailsVM>()
.ForMember(
dest => dest.PlanName,
opt => opt.MapFrom(src => src.Plan!.Plan != null ? src.Plan.Plan.PlanName : "")
)
.ForMember(
dest => dest.Description,
opt => opt.MapFrom(src => src.Plan!.Plan != null ? src.Plan.Plan.Description : "")
)
.ForMember(
dest => dest.Price,
opt => opt.MapFrom(src => src.Plan != null ? src.Plan.Price : 0)
)
.ForMember(
dest => dest.Frequency,
opt => opt.MapFrom(src => src.Plan != null ? src.Plan.Frequency : PLAN_FREQUENCY.MONTHLY)
);
CreateMap<SubscriptionPlanDetailsDto, SubscriptionPlanDetails>();
CreateMap<SubscriptionPlanDto, SubscriptionPlan>();
CreateMap<FeatureDetailsDto, FeatureDetails>();
CreateMap<SubscriptionCheckListDto, SubscriptionCheckList>();
CreateMap<SupportDetailsDto, SupportDetails>();
CreateMap<ReportDetailsDto, ReportDetails>();
CreateMap<ModulesDetailsDto, ModulesDetails>();
CreateMap<ProjectManagementDetailsDto, ProjectManagementDetails>();
CreateMap<AttendanceDetailsDto, AttendanceDetails>();
CreateMap<DirectoryDetailsDto, DirectoryDetails>();
CreateMap<ExpenseModuleDetailsDto, ExpenseModuleDetails>();
#endregion
#region ======================================================= Projects ======================================================= #region ======================================================= Projects =======================================================
// Your mappings // Your mappings
CreateMap<Project, ProjectVM>(); CreateMap<Project, ProjectVM>();
@ -160,6 +228,8 @@ namespace Marco.Pms.Services.MappingProfiles
#region ======================================================= Master ======================================================= #region ======================================================= Master =======================================================
CreateMap<FeaturePermission, FeaturePermissionVM>();
#region ======================================================= Expenses Type Master ======================================================= #region ======================================================= Expenses Type Master =======================================================
CreateMap<ExpensesTypeMasterDto, ExpensesTypeMaster>() CreateMap<ExpensesTypeMasterDto, ExpensesTypeMaster>()
@ -242,6 +312,29 @@ namespace Marco.Pms.Services.MappingProfiles
#endregion #endregion
#region ======================================================= AppMenu =======================================================
CreateMap<CreateMenuSectionDto, MenuSection>();
CreateMap<UpdateMenuSectionDto, MenuSection>();
CreateMap<MenuSection, MenuSectionVM>()
.ForMember(
dest => dest.Name,
opt => opt.MapFrom(src => src.Title));
CreateMap<CreateMenuItemDto, MenuItem>();
CreateMap<UpdateMenuItemDto, MenuItem>();
CreateMap<MenuItem, MenuItemVM>()
.ForMember(
dest => dest.Name,
opt => opt.MapFrom(src => src.Text));
CreateMap<CreateSubMenuItemDto, SubMenuItem>();
CreateMap<UpdateSubMenuItemDto, SubMenuItem>();
CreateMap<SubMenuItem, SubMenuItemVM>()
.ForMember(
dest => dest.Name,
opt => opt.MapFrom(src => src.Text));
#endregion
#region ======================================================= Directory ======================================================= #region ======================================================= Directory =======================================================
CreateMap<Contact, ContactVM>(); CreateMap<Contact, ContactVM>();

View File

@ -1,3 +1,4 @@
using Marco.Pms.CacheHelper;
using Marco.Pms.DataAccess.Data; using Marco.Pms.DataAccess.Data;
using Marco.Pms.Helpers; using Marco.Pms.Helpers;
using Marco.Pms.Helpers.CacheHelper; using Marco.Pms.Helpers.CacheHelper;
@ -172,6 +173,7 @@ builder.Services.AddTransient<S3UploadService>();
#region Customs Services #region Customs Services
builder.Services.AddScoped<RefreshTokenService>(); builder.Services.AddScoped<RefreshTokenService>();
builder.Services.AddScoped<PermissionServices>(); builder.Services.AddScoped<PermissionServices>();
builder.Services.AddScoped<MasterDataService>();
builder.Services.AddScoped<ISignalRService, SignalRService>(); builder.Services.AddScoped<ISignalRService, SignalRService>();
builder.Services.AddScoped<IProjectServices, ProjectServices>(); builder.Services.AddScoped<IProjectServices, ProjectServices>();
builder.Services.AddScoped<IExpensesService, ExpensesService>(); builder.Services.AddScoped<IExpensesService, ExpensesService>();
@ -188,6 +190,7 @@ builder.Services.AddScoped<DirectoryHelper>();
builder.Services.AddScoped<MasterHelper>(); builder.Services.AddScoped<MasterHelper>();
builder.Services.AddScoped<ReportHelper>(); builder.Services.AddScoped<ReportHelper>();
builder.Services.AddScoped<CacheUpdateHelper>(); builder.Services.AddScoped<CacheUpdateHelper>();
builder.Services.AddScoped<FeatureDetailsHelper>();
builder.Services.AddScoped<UtilityMongoDBHelper>(); builder.Services.AddScoped<UtilityMongoDBHelper>();
#endregion #endregion
@ -196,6 +199,7 @@ builder.Services.AddScoped<ProjectCache>();
builder.Services.AddScoped<EmployeeCache>(); builder.Services.AddScoped<EmployeeCache>();
builder.Services.AddScoped<ReportCache>(); builder.Services.AddScoped<ReportCache>();
builder.Services.AddScoped<ExpenseCache>(); builder.Services.AddScoped<ExpenseCache>();
builder.Services.AddScoped<SidebarMenuHelper>();
#endregion #endregion
// Singleton services (one instance for the app's lifetime) // Singleton services (one instance for the app's lifetime)

View File

@ -0,0 +1,337 @@
using Marco.Pms.Model.Forum;
using Marco.Pms.Model.Master;
namespace Marco.Pms.Services.Service
{
public class MasterDataService
{
public List<TicketStatusMaster> GetTicketStatusesData(Guid tenantId)
{
return new List<TicketStatusMaster>
{
new TicketStatusMaster
{
Id = Guid.NewGuid(),
Name = "New",
Description = "This is a newly created issue.",
ColorCode = "#FFCC99",
IsDefault = true,
TenantId = tenantId
},
new TicketStatusMaster
{
Id = Guid.NewGuid(),
Name = "Assigned",
Description = "Assigned to employee or team of employees",
ColorCode = "#E6FF99",
IsDefault = true,
TenantId = tenantId
},
new TicketStatusMaster
{
Id = Guid.NewGuid(),
Name = "In Progress",
Description = "These issues are currently in progress",
ColorCode = "#99E6FF",
IsDefault = true,
TenantId = tenantId
},
new TicketStatusMaster
{
Id = Guid.NewGuid(),
Name = "In Review",
Description = "These issues are currently under review",
ColorCode = "#8592a3",
IsDefault = true,
TenantId = tenantId
},
new TicketStatusMaster
{
Id = Guid.NewGuid(),
Name = "Done",
Description = "The following issues are resolved and closed",
ColorCode = "#B399FF",
IsDefault = true,
TenantId = tenantId
}
};
}
public List<TicketTypeMaster> GetTicketTypesData(Guid tenantId)
{
return new List<TicketTypeMaster>
{
new TicketTypeMaster
{
Id = Guid.NewGuid(),
Name = "Quality Issue",
Description = "An identified problem that affects the performance, reliability, or standards of a product or service",
IsDefault = true,
TenantId = tenantId
},
new TicketTypeMaster
{
Id = Guid.NewGuid(),
Name = "Help Desk",
Description = "A support service that assists users with technical issues, requests, or inquiries.",
IsDefault = true,
TenantId = tenantId
}
};
}
public List<TicketPriorityMaster> GetTicketPrioritysData(Guid tenantId)
{
return new List<TicketPriorityMaster>
{
new TicketPriorityMaster
{
Id = Guid.NewGuid(),
Name = "Low",
ColorCode = "008000",
Level = 1,
IsDefault = true,
TenantId = tenantId
},
new TicketPriorityMaster
{
Id = Guid.NewGuid(),
Name = "Medium",
ColorCode = "FFFF00",
Level = 2,
IsDefault = true,
TenantId = tenantId
},
new TicketPriorityMaster
{
Id = Guid.NewGuid(),
Name = "High",
ColorCode = "#FFA500",
Level = 3,
IsDefault = true,
TenantId = tenantId
},
new TicketPriorityMaster
{
Id = Guid.NewGuid(),
Name = "Critical",
ColorCode = "#FFA500",
Level = 4,
IsDefault = true,
TenantId = tenantId
},
new TicketPriorityMaster
{
Id = Guid.NewGuid(),
Name = "Urgent",
ColorCode = "#FF0000",
Level = 5,
IsDefault = true,
TenantId = tenantId
}
};
}
public List<TicketTagMaster> GetTicketTagsData(Guid tenantId)
{
return new List<TicketTagMaster>
{
new TicketTagMaster
{
Id = Guid.NewGuid(),
Name = "Quality Issue",
ColorCode = "#e59866",
IsDefault = true,
TenantId = tenantId
},
new TicketTagMaster
{
Id = Guid.NewGuid(),
Name = "Help Desk",
ColorCode = "#85c1e9",
IsDefault = true,
TenantId = tenantId
}
};
}
public List<WorkCategoryMaster> GetWorkCategoriesData(Guid tenantId)
{
return new List<WorkCategoryMaster>
{
new WorkCategoryMaster
{
Id = Guid.NewGuid(),
Name = "Fresh Work",
Description = "Created new task in a professional or creative context",
IsSystem = true,
TenantId = tenantId
},
new WorkCategoryMaster
{
Id = Guid.NewGuid(),
Name = "Rework",
Description = "Revising, modifying, or correcting a task to improve its quality or fix issues",
IsSystem = true,
TenantId = tenantId
},
new WorkCategoryMaster
{
Id = Guid.NewGuid(),
Name = "Quality Issue",
Description = "Any defect, deviation, or non-conformance in a task that fails to meet established standards or customer expectations.",
IsSystem = true,
TenantId = tenantId
}
};
}
public List<WorkStatusMaster> GetWorkStatusesData(Guid tenantId)
{
return new List<WorkStatusMaster>
{
new WorkStatusMaster
{
Id = Guid.NewGuid(),
Name = "Approve",
Description = "Confirm the tasks are actually finished as reported",
IsSystem = true,
TenantId = tenantId
},
new WorkStatusMaster
{
Id = Guid.NewGuid(),
Name = "Partially Approve",
Description = "Not all tasks are actually finished as reported",
IsSystem = true,
TenantId = tenantId
},
new WorkStatusMaster
{
Id = Guid.NewGuid(),
Name = "NCR",
Description = "Tasks are not finished as reported or have any issues in al the tasks",
IsSystem = true,
TenantId = tenantId
}
};
}
public List<ExpensesTypeMaster> GetExpensesTypeesData(Guid tenantId)
{
return new List<ExpensesTypeMaster>
{
new ExpensesTypeMaster
{
Id = Guid.NewGuid(),
Name = "Procurement",
Description = "Materials, equipment and supplies purchased for site operations.",
NoOfPersonsRequired = false,
IsActive = true,
TenantId = tenantId
},
new ExpensesTypeMaster
{
Id = Guid.NewGuid(),
Name = "Transport",
Description = "Vehicle fuel, logistics services and delivery of goods or personnel.",
NoOfPersonsRequired = false,
IsActive = true,
TenantId = tenantId
},
new ExpensesTypeMaster
{
Id = Guid.NewGuid(),
Name = "Travelling",
Description = "Delivery of personnel.",
NoOfPersonsRequired = true,
IsActive = true,
TenantId = tenantId
},
new ExpensesTypeMaster
{
Id = Guid.NewGuid(),
Name = "Mobilization",
Description = "Site setup costs including equipment deployment and temporary infrastructure.",
NoOfPersonsRequired = false,
IsActive = true,
TenantId = tenantId
},
new ExpensesTypeMaster
{
Id = Guid.NewGuid(),
Name = "Employee Welfare",
Description = " Worker amenities like snacks, meals, safety gear, accommodation, medical support etc.",
NoOfPersonsRequired = true,
IsActive = true,
TenantId = tenantId
},
new ExpensesTypeMaster
{
Id = Guid.NewGuid(),
Name = "Maintenance & Utilities",
Description = "Machinery servicing, electricity, water, and temporary office needs.",
NoOfPersonsRequired = false,
IsActive = true,
TenantId = tenantId
},
new ExpensesTypeMaster
{
Id = Guid.NewGuid(),
Name = "Vendor/Supplier Payments",
Description = "Scheduled payments for external services or goods.",
NoOfPersonsRequired = false,
IsActive = true,
TenantId = tenantId
},
new ExpensesTypeMaster
{
Id = Guid.NewGuid(),
Name = "Compliance & Safety",
Description = "Government fees, insurance, inspections and safety-related expenditures.",
NoOfPersonsRequired = false,
IsActive = true,
TenantId = tenantId
}
};
}
public List<PaymentModeMatser> GetPaymentModesData(Guid tenantId)
{
return new List<PaymentModeMatser>
{
new PaymentModeMatser
{
Id = Guid.NewGuid(),
Name = "Cash",
Description = "Physical currency; still used for small or informal transactions.",
IsActive = true,
TenantId = tenantId
},
new PaymentModeMatser
{
Id = Guid.NewGuid(),
Name = "Cheque",
Description = "Paper-based payment order; less common now due to processing delays and fraud risks.",
IsActive = true,
TenantId = tenantId
},
new PaymentModeMatser
{
Id = Guid.NewGuid(),
Name = "NetBanking",
Description = "Online banking portals used to transfer funds directly between accounts",
IsActive = true,
TenantId = tenantId
},
new PaymentModeMatser
{
Id = Guid.NewGuid(),
Name = "UPI",
Description = "Real-time bank-to-bank transfer using mobile apps; widely used for peer-to-peer and merchant payments.",
IsActive = true,
TenantId = tenantId
}
};
}
public List<object> GetData(Guid tenantId)
{
return new List<object>
{
};
}
}
}

View File

@ -30,6 +30,18 @@ namespace Marco.Pms.Services.Service
var hasPermission = featurePermissionIds.Contains(featurePermissionId); var hasPermission = featurePermissionIds.Contains(featurePermissionId);
return hasPermission; return hasPermission;
} }
public async Task<bool> HasPermissionAny(List<Guid> featurePermissionIds, Guid employeeId)
{
var allFeaturePermissionIds = await _cache.GetPermissions(employeeId);
if (allFeaturePermissionIds == null)
{
List<FeaturePermission> featurePermission = await _rolesHelper.GetFeaturePermissionByEmployeeId(employeeId);
allFeaturePermissionIds = featurePermission.Select(fp => fp.Id).ToList();
}
var hasPermission = featurePermissionIds.Any(f => allFeaturePermissionIds.Contains(f));
return hasPermission;
}
public async Task<bool> HasProjectPermission(Employee LoggedInEmployee, Guid projectId) public async Task<bool> HasProjectPermission(Employee LoggedInEmployee, Guid projectId)
{ {
var employeeId = LoggedInEmployee.Id; var employeeId = LoggedInEmployee.Id;

View File

@ -7,6 +7,7 @@ using Marco.Pms.Model.Employees;
using Marco.Pms.Model.Entitlements; using Marco.Pms.Model.Entitlements;
using Marco.Pms.Model.MongoDBModels.Project; using Marco.Pms.Model.MongoDBModels.Project;
using Marco.Pms.Model.Projects; using Marco.Pms.Model.Projects;
using Marco.Pms.Model.TenantModels;
using Marco.Pms.Model.Utilities; using Marco.Pms.Model.Utilities;
using Marco.Pms.Model.ViewModels.Employee; using Marco.Pms.Model.ViewModels.Employee;
using Marco.Pms.Model.ViewModels.Projects; using Marco.Pms.Model.ViewModels.Projects;
@ -227,10 +228,6 @@ namespace Marco.Pms.Services.Service
else else
{ {
projectVM = _mapper.Map<ProjectVM>(projectDetails); projectVM = _mapper.Map<ProjectVM>(projectDetails);
if (projectVM.ProjectStatus != null)
{
projectVM.ProjectStatus.TenantId = tenantId;
}
} }
if (projectVM == null) if (projectVM == null)
@ -895,6 +892,73 @@ namespace Marco.Pms.Services.Service
return ApiResponse<List<ProjectAllocationVM>>.SuccessResponse(resultVm, "Assignments managed successfully.", 200); return ApiResponse<List<ProjectAllocationVM>>.SuccessResponse(resultVm, "Assignments managed successfully.", 200);
} }
public async Task<ApiResponse<object>> GetProjectByEmployeeBasicAsync(Guid employeeId, Guid tenantId, Employee loggedInEmployee)
{
// Log the start of the method execution with key input parameters
_logger.LogInfo("Fetching projects for EmployeeId: {EmployeeId}, TenantId: {TenantId} by User: {UserId}",
employeeId, tenantId, loggedInEmployee.Id);
try
{
// Retrieve project allocations linked to the specified employee and tenant
var projectAllocation = await _context.ProjectAllocations
.AsNoTracking() // Optimization: no tracking since entities are not updated
.Include(pa => pa.Project) // Include related Project data
.Include(pa => pa.Employee).ThenInclude(e => e!.JobRole) // Include related Employee and their JobRole
.Where(pa => pa.EmployeeId == employeeId
&& pa.TenantId == tenantId
&& pa.Project != null
&& pa.Employee != null)
.Select(pa => new
{
ProjectName = pa.Project!.Name,
ProjectShortName = pa.Project.ShortName,
AssignedDate = pa.AllocationDate,
RemovedDate = pa.ReAllocationDate,
Designation = pa.Employee!.JobRole!.Name,
DesignationId = pa.JobRoleId
})
.ToListAsync();
var designationIds = projectAllocation.Select(pa => pa.DesignationId).ToList();
var designations = await _context.JobRoles.Where(jr => designationIds.Contains(jr.Id)).ToListAsync();
var response = projectAllocation.Select(pa =>
{
var designation = designations.FirstOrDefault(jr => jr.Id == pa.DesignationId);
return new ProjectHisteryVM
{
ProjectName = pa.ProjectName,
ProjectShortName = pa.ProjectShortName,
AssignedDate = pa.AssignedDate,
RemovedDate = pa.RemovedDate,
Designation = designation?.Name
};
}).ToList();
// Log successful retrieval including count of records
_logger.LogInfo("Successfully fetched {Count} projects for EmployeeId: {EmployeeId}",
projectAllocation.Count, employeeId);
return ApiResponse<object>.SuccessResponse(
response,
$"{response.Count} project assignments fetched for employee.",
200);
}
catch (Exception ex)
{
// Log the exception with stack trace for debugging
_logger.LogError(ex, "Error occurred while fetching projects for EmployeeId: {EmployeeId}, TenantId: {TenantId}",
employeeId, tenantId);
return ApiResponse<object>.ErrorResponse(
"An error occurred while fetching project assignments.",
500);
}
}
#endregion #endregion
#region =================================================================== Project InfraStructure Get APIs =================================================================== #region =================================================================== Project InfraStructure Get APIs ===================================================================
@ -1025,6 +1089,83 @@ namespace Marco.Pms.Services.Service
} }
} }
/// <summary>
/// Retrieves tasks assigned to a specific employee within a date range for a tenant.
/// </summary>
/// <param name="employeeId">The ID of the employee to filter tasks.</param>
/// <param name="fromDate">The start date to filter task assignments.</param>
/// <param name="toDate">The end date to filter task assignments.</param>
/// <param name="tenantId">The tenant ID to filter tasks.</param>
/// <param name="loggedInEmployee">The employee requesting the data (for authorization/logging).</param>
/// <returns>An ApiResponse containing the task details.</returns>
public async Task<ApiResponse<object>> GetTasksByEmployeeAsync(Guid employeeId, DateTime fromDate, DateTime toDate, Guid tenantId, Employee loggedInEmployee)
{
_logger.LogInfo("Fetching tasks for EmployeeId: {EmployeeId} from {FromDate} to {ToDate} for TenantId: {TenantId}",
employeeId, fromDate, toDate, tenantId);
try
{
// Query TaskMembers with related necessary fields in one projection to minimize DB calls and data size
var taskData = await _context.TaskMembers
.Where(tm => tm.EmployeeId == employeeId &&
tm.TenantId == tenantId &&
tm.TaskAllocation != null &&
tm.TaskAllocation.AssignmentDate.Date >= fromDate.Date &&
tm.TaskAllocation.AssignmentDate.Date <= toDate.Date)
.Select(tm => new
{
AssignmentDate = tm.TaskAllocation!.AssignmentDate,
PlannedTask = tm.TaskAllocation.PlannedTask,
CompletedTask = tm.TaskAllocation.CompletedTask,
ProjectId = tm.TaskAllocation.WorkItem!.WorkArea!.Floor!.Building!.ProjectId,
BuildingName = tm.TaskAllocation.WorkItem.WorkArea.Floor.Building!.Name,
FloorName = tm.TaskAllocation.WorkItem.WorkArea.Floor.FloorName,
AreaName = tm.TaskAllocation.WorkItem.WorkArea.AreaName,
ActivityName = tm.TaskAllocation.WorkItem.ActivityMaster!.ActivityName,
ActivityUnit = tm.TaskAllocation.WorkItem.ActivityMaster.UnitOfMeasurement
})
.OrderByDescending(t => t.AssignmentDate)
.ToListAsync();
_logger.LogInfo("Retrieved {TaskCount} tasks for EmployeeId: {EmployeeId}", taskData.Count, employeeId);
// Extract distinct project IDs to fetch project details efficiently
var distinctProjectIds = taskData.Select(t => t.ProjectId).Distinct().ToList();
var projects = await _context.Projects
.Where(p => distinctProjectIds.Contains(p.Id))
.Select(p => new { p.Id, p.Name })
.ToListAsync();
// Prepare the response
var response = taskData.Select(t =>
{
var project = projects.FirstOrDefault(p => p.Id == t.ProjectId);
return new
{
ProjectName = project?.Name ?? "Unknown Project",
t.AssignmentDate,
t.PlannedTask,
t.CompletedTask,
Location = $"{t.BuildingName} > {t.FloorName} > {t.AreaName}",
ActivityName = t.ActivityName,
ActivityUnit = t.ActivityUnit
};
}).ToList();
_logger.LogInfo("Successfully prepared task response for EmployeeId: {EmployeeId}", employeeId);
return ApiResponse<object>.SuccessResponse(response, "Task fetched successfully", 200);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error while fetching tasks for EmployeeId: {EmployeeId}", employeeId);
return ApiResponse<object>.ErrorResponse("An error occurred while fetching the tasks.", 500);
}
}
#endregion #endregion
#region =================================================================== Project Infrastructre Manage APIs =================================================================== #region =================================================================== Project Infrastructre Manage APIs ===================================================================

View File

@ -20,8 +20,11 @@ namespace Marco.Pms.Services.Service.ServiceInterfaces
Task<ApiResponse<List<ProjectAllocationVM>>> ManageAllocationAsync(List<ProjectAllocationDot> projectAllocationDots, Guid tenantId, Employee loggedInEmployee); Task<ApiResponse<List<ProjectAllocationVM>>> ManageAllocationAsync(List<ProjectAllocationDot> projectAllocationDots, Guid tenantId, Employee loggedInEmployee);
Task<ApiResponse<object>> GetProjectsByEmployeeAsync(Guid employeeId, Guid tenantId, Employee loggedInEmployee); Task<ApiResponse<object>> GetProjectsByEmployeeAsync(Guid employeeId, Guid tenantId, Employee loggedInEmployee);
Task<ApiResponse<List<ProjectAllocationVM>>> AssigneProjectsToEmployeeAsync(List<ProjectsAllocationDto> projectAllocationDtos, Guid employeeId, Guid tenantId, Employee loggedInEmployee); Task<ApiResponse<List<ProjectAllocationVM>>> AssigneProjectsToEmployeeAsync(List<ProjectsAllocationDto> projectAllocationDtos, Guid employeeId, Guid tenantId, Employee loggedInEmployee);
Task<ApiResponse<object>> GetProjectByEmployeeBasicAsync(Guid employeeId, Guid tenantId, Employee loggedInEmployee);
Task<ApiResponse<object>> GetInfraDetailsAsync(Guid projectId, Guid tenantId, Employee loggedInEmployee); Task<ApiResponse<object>> GetInfraDetailsAsync(Guid projectId, Guid tenantId, Employee loggedInEmployee);
Task<ApiResponse<object>> GetWorkItemsAsync(Guid workAreaId, Guid tenantId, Employee loggedInEmployee); Task<ApiResponse<object>> GetWorkItemsAsync(Guid workAreaId, Guid tenantId, Employee loggedInEmployee);
Task<ApiResponse<object>> GetTasksByEmployeeAsync(Guid employeeId, DateTime fromDate, DateTime toDate, Guid tenantId, Employee loggedInEmployee);
Task<ServiceResponse> ManageProjectInfraAsync(List<InfraDto> infraDtos, Guid tenantId, Employee loggedInEmployee); Task<ServiceResponse> ManageProjectInfraAsync(List<InfraDto> infraDtos, Guid tenantId, Employee loggedInEmployee);
Task<ApiResponse<List<WorkItemVM>>> CreateProjectTaskAsync(List<WorkItemDto> workItemDtos, Guid tenantId, Employee loggedInEmployee); Task<ApiResponse<List<WorkItemVM>>> CreateProjectTaskAsync(List<WorkItemDto> workItemDtos, Guid tenantId, Employee loggedInEmployee);
Task<ServiceResponse> DeleteProjectTaskAsync(Guid id, Guid tenantId, Employee loggedInEmployee); Task<ServiceResponse> DeleteProjectTaskAsync(Guid id, Guid tenantId, Employee loggedInEmployee);

View File

@ -1,54 +1,54 @@
{ {
"Cors": { "Cors": {
"AllowedOrigins": "*", "AllowedOrigins": "*",
"AllowedMethods": "*", "AllowedMethods": "*",
"AllowedHeaders": "*" "AllowedHeaders": "*"
}, },
"Environment": { "Environment": {
"Name": "Development", "Name": "Development",
"Title": "Dev" "Title": "Dev"
}, },
"ConnectionStrings": { "ConnectionStrings": {
"DefaultConnectionString": "Server=147.93.98.152;User ID=devuser;Password=AppUser@123$;Database=MarcoBMS1" "DefaultConnectionString": "Server=147.93.98.152;User ID=devuser;Password=AppUser@123$;Database=MarcoBMS1"
}, },
"SmtpSettings": { "SmtpSettings": {
"SmtpServer": "smtp.gmail.com", "SmtpServer": "smtp.gmail.com",
"Port": 587, "Port": 587,
"SenderName": "MarcoAIOT", "SenderName": "MarcoAIOT",
"SenderEmail": "marcoioitsoft@gmail.com", "SenderEmail": "marcoioitsoft@gmail.com",
"Password": "qrtq wfuj hwpp fhqr" "Password": "qrtq wfuj hwpp fhqr"
}, },
//"SmtpSettings": { //"SmtpSettings": {
// "SmtpServer": "mail.marcoaiot.com", // "SmtpServer": "mail.marcoaiot.com",
// "Port": 587, // "Port": 587,
// "SenderName": "MarcoAIOT", // "SenderName": "MarcoAIOT",
// "SenderEmail": "ashutosh.nehete@marcoaiot.com", // "SenderEmail": "ashutosh.nehete@marcoaiot.com",
// "Password": "Reset@123" // "Password": "Reset@123"
//}, //},
"AppSettings": { "AppSettings": {
"WebFrontendUrl": "http://localhost:5173", "WebFrontendUrl": "http://localhost:5173",
"ImagesBaseUrl": "http://localhost:5173" "ImagesBaseUrl": "http://localhost:5173"
}, },
"Jwt": { "Jwt": {
"Issuer": "http://localhost:5246", "Issuer": "http://localhost:5246",
"Audience": "http://localhost:5246", "Audience": "http://localhost:5246",
"Key": "sworffishhkjfa9dnfdndfu33infnajfj", "Key": "sworffishhkjfa9dnfdndfu33infnajfj",
"ExpiresInMinutes": 60, "ExpiresInMinutes": 60,
"RefreshTokenExpiresInDays": 7 "RefreshTokenExpiresInDays": 7
}, },
"MailingList": { "MailingList": {
"RequestDemoReceivers": "ashutosh.nehete@marcoaiot.com;vikas@marcoaiot.com;umesh@marcoait.com" "RequestDemoReceivers": "ashutosh.nehete@marcoaiot.com;vikas@marcoaiot.com;umesh@marcoait.com"
//"ProjectStatisticsReceivers": "ashutosh.nehete@marcoaiot.com;vikas@marcoaiot.com;umesh@marcoait.com" //"ProjectStatisticsReceivers": "ashutosh.nehete@marcoaiot.com;vikas@marcoaiot.com;umesh@marcoait.com"
}, },
"AWS": { "AWS": {
"AccessKey": "AKIARZDBH3VDMSUUY2FX", "AccessKey": "AKIARZDBH3VDMSUUY2FX",
"SecretKey": "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", "SecretKey": "NTS5XXgZINQbU6ctpNuLXtIY/Qk9GCgD9Rr5yNJP",
"Region": "us-east-1", "Region": "us-east-1",
"BucketName": "testenv-marco-pms-documents" "BucketName": "testenv-marco-pms-documents"
}, },
"MongoDB": { "MongoDB": {
"SerilogDatabaseUrl": "mongodb://localhost:27017/DotNetLogs", "SerilogDatabaseUrl": "mongodb://localhost:27017/DotNetLogs",
"ConnectionString": "mongodb://localhost:27017/MarcoBMS_Caches?socketTimeoutMS=500&serverSelectionTimeoutMS=500&connectTimeoutMS=500", "ConnectionString": "mongodb://localhost:27017/MarcoBMS_Caches?socketTimeoutMS=500&serverSelectionTimeoutMS=500&connectTimeoutMS=500",
"ModificationConnectionString": "mongodb://localhost:27017/ModificationLog?socketTimeoutMS=500&serverSelectionTimeoutMS=500&connectTimeoutMS=500" "ModificationConnectionString": "mongodb://devuser:DevPass123@147.93.98.152:27017/MarcoBMSLocalDev?authSource=admin&eplicaSet=rs01&directConnection=true"
} }
} }