Compare commits
59 Commits
Ashutosh_T
...
main
Author | SHA1 | Date | |
---|---|---|---|
7e20807325 | |||
186486d934 | |||
d07f0311ae | |||
2f6031e62c | |||
e02636b6b6 | |||
bad784e147 | |||
886d0bb3b1 | |||
74dd9eeb8d | |||
1939a63d9a | |||
c07db9f94d | |||
53da15416a | |||
a809bdd469 | |||
bb2c098cb9 | |||
bd3b70f4ea | |||
000cef3bb7 | |||
522deae8f7 | |||
9a8aa4f5ce | |||
af92ab977b | |||
c06dc8ebe7 | |||
8609db64d2 | |||
e831f50505 | |||
ba88fbced6 | |||
13d2e1cd7d | |||
26acfec408 | |||
3bd38f3c68 | |||
2e29dc9946 | |||
db752a4678 | |||
245182eb07 | |||
9daf76c6eb | |||
c33afa58c3 | |||
4ceb5c3cb2 | |||
d0dabf776b | |||
04223578ad | |||
f94a7de4ab | |||
eb3a65428e | |||
8ddb414e91 | |||
d8329f1fab | |||
d0912cca56 | |||
d957c692f1 | |||
40ca680f45 | |||
8dee7b0fa2 | |||
1e1fe6692d | |||
0df13975a7 | |||
8fb5aa632d | |||
7e4d4b0f5b | |||
64bde99ab5 | |||
90851a3349 | |||
2c0cd87227 | |||
c3670eaab9 | |||
1cd8203d27 | |||
5df66b79ca | |||
31da8a4402 | |||
27a2610388 | |||
cd175c4fdf | |||
d6145ee0ba | |||
04086ed7e0 | |||
a9b4b79ee4 | |||
70092e5601 | |||
33cd7113a7 |
6205
Marco.Pms.DataAccess/Migrations/20250927091428_Added_Forgin_Key_For_Approver.Designer.cs
generated
Normal file
6205
Marco.Pms.DataAccess/Migrations/20250927091428_Added_Forgin_Key_For_Approver.Designer.cs
generated
Normal file
File diff suppressed because one or more lines are too long
@ -0,0 +1,92 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Marco.Pms.DataAccess.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class Added_Forgin_Key_For_Approver : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_Attendes_Employees_EmployeeID",
|
||||
table: "Attendes");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "EmployeeID",
|
||||
table: "Attendes",
|
||||
newName: "EmployeeId");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "ApprovedBy",
|
||||
table: "Attendes",
|
||||
newName: "ApprovedById");
|
||||
|
||||
migrationBuilder.RenameIndex(
|
||||
name: "IX_Attendes_EmployeeID",
|
||||
table: "Attendes",
|
||||
newName: "IX_Attendes_EmployeeId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Attendes_ApprovedById",
|
||||
table: "Attendes",
|
||||
column: "ApprovedById");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_Attendes_Employees_ApprovedById",
|
||||
table: "Attendes",
|
||||
column: "ApprovedById",
|
||||
principalTable: "Employees",
|
||||
principalColumn: "Id");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_Attendes_Employees_EmployeeId",
|
||||
table: "Attendes",
|
||||
column: "EmployeeId",
|
||||
principalTable: "Employees",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_Attendes_Employees_ApprovedById",
|
||||
table: "Attendes");
|
||||
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_Attendes_Employees_EmployeeId",
|
||||
table: "Attendes");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_Attendes_ApprovedById",
|
||||
table: "Attendes");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "EmployeeId",
|
||||
table: "Attendes",
|
||||
newName: "EmployeeID");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "ApprovedById",
|
||||
table: "Attendes",
|
||||
newName: "ApprovedBy");
|
||||
|
||||
migrationBuilder.RenameIndex(
|
||||
name: "IX_Attendes_EmployeeId",
|
||||
table: "Attendes",
|
||||
newName: "IX_Attendes_EmployeeID");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_Attendes_Employees_EmployeeID",
|
||||
table: "Attendes",
|
||||
column: "EmployeeID",
|
||||
principalTable: "Employees",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
}
|
||||
}
|
||||
}
|
6209
Marco.Pms.DataAccess/Migrations/20251004112239_Added_ExpenceUID_In_Expense_Table.Designer.cs
generated
Normal file
6209
Marco.Pms.DataAccess/Migrations/20251004112239_Added_ExpenceUID_In_Expense_Table.Designer.cs
generated
Normal file
File diff suppressed because one or more lines are too long
@ -0,0 +1,29 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Marco.Pms.DataAccess.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class Added_ExpenceUID_In_Expense_Table : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "ExpenseUId",
|
||||
table: "Expenses",
|
||||
type: "longtext",
|
||||
nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "ExpenseUId",
|
||||
table: "Expenses");
|
||||
}
|
||||
}
|
||||
}
|
6226
Marco.Pms.DataAccess/Migrations/20251010062100_Added_Requested_In_Attendance_Table.Designer.cs
generated
Normal file
6226
Marco.Pms.DataAccess/Migrations/20251010062100_Added_Requested_In_Attendance_Table.Designer.cs
generated
Normal file
File diff suppressed because one or more lines are too long
@ -0,0 +1,70 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Marco.Pms.DataAccess.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class Added_Requested_In_Attendance_Table : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<DateTime>(
|
||||
name: "ApprovedAt",
|
||||
table: "Attendes",
|
||||
type: "datetime(6)",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<DateTime>(
|
||||
name: "RequestedAt",
|
||||
table: "Attendes",
|
||||
type: "datetime(6)",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<Guid>(
|
||||
name: "RequestedById",
|
||||
table: "Attendes",
|
||||
type: "char(36)",
|
||||
nullable: true,
|
||||
collation: "ascii_general_ci");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Attendes_RequestedById",
|
||||
table: "Attendes",
|
||||
column: "RequestedById");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_Attendes_Employees_RequestedById",
|
||||
table: "Attendes",
|
||||
column: "RequestedById",
|
||||
principalTable: "Employees",
|
||||
principalColumn: "Id");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_Attendes_Employees_RequestedById",
|
||||
table: "Attendes");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_Attendes_RequestedById",
|
||||
table: "Attendes");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "ApprovedAt",
|
||||
table: "Attendes");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "RequestedAt",
|
||||
table: "Attendes");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "RequestedById",
|
||||
table: "Attendes");
|
||||
}
|
||||
}
|
||||
}
|
@ -172,7 +172,10 @@ namespace Marco.Pms.DataAccess.Migrations
|
||||
b.Property<int>("Activity")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<Guid?>("ApprovedBy")
|
||||
b.Property<DateTime?>("ApprovedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<Guid?>("ApprovedById")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<DateTime>("AttendanceDate")
|
||||
@ -185,7 +188,7 @@ namespace Marco.Pms.DataAccess.Migrations
|
||||
b.Property<DateTime>("Date")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<Guid>("EmployeeID")
|
||||
b.Property<Guid>("EmployeeId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<DateTime?>("InTime")
|
||||
@ -200,12 +203,22 @@ namespace Marco.Pms.DataAccess.Migrations
|
||||
b.Property<Guid>("ProjectID")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<DateTime?>("RequestedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<Guid?>("RequestedById")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<Guid>("TenantId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("EmployeeID");
|
||||
b.HasIndex("ApprovedById");
|
||||
|
||||
b.HasIndex("EmployeeId");
|
||||
|
||||
b.HasIndex("RequestedById");
|
||||
|
||||
b.HasIndex("TenantId");
|
||||
|
||||
@ -1830,6 +1843,10 @@ namespace Marco.Pms.DataAccess.Migrations
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("ExpenseUId")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<Guid>("ExpensesTypeId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
@ -4701,10 +4718,18 @@ namespace Marco.Pms.DataAccess.Migrations
|
||||
{
|
||||
b.HasOne("Marco.Pms.Model.Employees.Employee", "Approver")
|
||||
.WithMany()
|
||||
.HasForeignKey("EmployeeID")
|
||||
.HasForeignKey("ApprovedById");
|
||||
|
||||
b.HasOne("Marco.Pms.Model.Employees.Employee", "Employee")
|
||||
.WithMany()
|
||||
.HasForeignKey("EmployeeId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Marco.Pms.Model.Employees.Employee", "RequestedBy")
|
||||
.WithMany()
|
||||
.HasForeignKey("RequestedById");
|
||||
|
||||
b.HasOne("Marco.Pms.Model.TenantModels.Tenant", "Tenant")
|
||||
.WithMany()
|
||||
.HasForeignKey("TenantId")
|
||||
@ -4713,6 +4738,10 @@ namespace Marco.Pms.DataAccess.Migrations
|
||||
|
||||
b.Navigation("Approver");
|
||||
|
||||
b.Navigation("Employee");
|
||||
|
||||
b.Navigation("RequestedBy");
|
||||
|
||||
b.Navigation("Tenant");
|
||||
});
|
||||
|
||||
|
@ -212,6 +212,48 @@ namespace Marco.Pms.Helpers.CacheHelper
|
||||
|
||||
return true;
|
||||
}
|
||||
public async Task<bool> ClearAllEmployeesFromCacheByOnlyEmployeeId(Guid employeeId)
|
||||
{
|
||||
var employeeIdString = employeeId.ToString();
|
||||
|
||||
try
|
||||
{
|
||||
var filter = Builders<EmployeePermissionMongoDB>.Filter.Eq(e => e.Id, employeeIdString);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
public async Task<bool> ClearAllEmployeesFromCacheByTenantId(Guid tenantId)
|
||||
{
|
||||
var tenantIdString = tenantId.ToString();
|
||||
|
||||
try
|
||||
{
|
||||
var filter = Builders<EmployeePermissionMongoDB>.Filter.Eq(e => e.TenantId, tenantIdString);
|
||||
|
||||
var result = await _collection.DeleteManyAsync(filter);
|
||||
|
||||
if (result.DeletedCount == 0)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error occured while deleting employee profile");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public async Task<bool> ClearAllEmployeesFromCacheByEmployeeIds(List<string> employeeIds, Guid tenantId)
|
||||
{
|
||||
var tenantIdString = tenantId.ToString();
|
||||
|
@ -1,8 +1,8 @@
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using Marco.Pms.Model.Dtos.Attendance;
|
||||
using Marco.Pms.Model.Dtos.Attendance;
|
||||
using Marco.Pms.Model.Employees;
|
||||
using Marco.Pms.Model.Utilities;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace Marco.Pms.Model.AttendanceModule
|
||||
{
|
||||
@ -10,9 +10,11 @@ namespace Marco.Pms.Model.AttendanceModule
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public string Comment { get; set; } = string.Empty;
|
||||
public Guid EmployeeID { get; set; }
|
||||
|
||||
public Guid EmployeeId { get; set; }
|
||||
|
||||
[ForeignKey("EmployeeId")]
|
||||
[ValidateNever]
|
||||
public Employee? Employee { get; set; }
|
||||
public DateTime Date { get; set; }
|
||||
public Guid ProjectID { get; set; }
|
||||
|
||||
@ -22,9 +24,17 @@ namespace Marco.Pms.Model.AttendanceModule
|
||||
public bool IsApproved { get; set; }
|
||||
public ATTENDANCE_MARK_TYPE Activity { get; set; }
|
||||
|
||||
public Guid? ApprovedBy { get; set; }
|
||||
[ForeignKey("EmployeeID")]
|
||||
public Guid? ApprovedById { get; set; }
|
||||
|
||||
[ForeignKey("ApprovedById")]
|
||||
[ValidateNever]
|
||||
public Employee? Approver { get; set; }
|
||||
public DateTime? RequestedAt { get; set; }
|
||||
public DateTime? ApprovedAt { get; set; }
|
||||
public Guid? RequestedById { get; set; }
|
||||
|
||||
[ForeignKey("RequestedById")]
|
||||
[ValidateNever]
|
||||
public Employee? RequestedBy { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
{
|
||||
public class CreateWorkStatusMasterDto
|
||||
{
|
||||
public string? Name { get; set; }
|
||||
public string? Description { get; set; }
|
||||
public required string Name { get; set; }
|
||||
public required string Description { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
public class UpdateWorkStatusMasterDto
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public string? Name { get; set; }
|
||||
public string? Description { get; set; }
|
||||
public required string Name { get; set; }
|
||||
public required string Description { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,5 @@
|
||||
public required Guid EmployeeId { get; set; }
|
||||
public required string MPIN { get; set; }
|
||||
public required string MPINToken { get; set; }
|
||||
public required string FcmToken { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -28,11 +28,13 @@
|
||||
public required string FirstName { get; set; }
|
||||
public required string LastName { get; set; }
|
||||
public required string PhoneNumber { get; set; }
|
||||
public string? Email { get; set; }
|
||||
public required DateTime JoiningDate { get; set; }
|
||||
public required string Gender { get; set; }
|
||||
public Guid JobRoleId { get; set; }
|
||||
public string? ProfileImage { get; set; }
|
||||
public required Guid OrganizationId { get; set; }
|
||||
public required bool HasApplicationAccess { get; set; }
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
{
|
||||
public class CreateContactCategoryDto
|
||||
{
|
||||
public string? Name { get; set; }
|
||||
public string? Description { get; set; }
|
||||
public required string Name { get; set; }
|
||||
public required string Description { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
{
|
||||
public class CreateContactTagDto
|
||||
{
|
||||
public string? Name { get; set; }
|
||||
public string? Description { get; set; }
|
||||
public required string Name { get; set; }
|
||||
public required string Description { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -2,8 +2,8 @@
|
||||
{
|
||||
public class UpdateContactCategoryDto
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public string? Name { get; set; }
|
||||
public string? Description { get; set; }
|
||||
public required Guid Id { get; set; }
|
||||
public required string Name { get; set; }
|
||||
public required string Description { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
public class UpdateContactTagDto
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public string? Name { get; set; }
|
||||
public string? Description { get; set; }
|
||||
public required string Name { get; set; }
|
||||
public required string Description { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,7 @@
|
||||
public Guid EmployeeId { get; set; }
|
||||
public Guid JobRoleId { get; set; }
|
||||
public Guid ProjectId { get; set; }
|
||||
public Guid ServiceId { get; set; }
|
||||
public Guid? ServiceId { get; set; }
|
||||
public bool Status { get; set; }
|
||||
}
|
||||
|
||||
@ -14,7 +14,7 @@
|
||||
{
|
||||
public Guid ProjectId { get; set; }
|
||||
public Guid JobRoleId { get; set; }
|
||||
public Guid ServiceId { get; set; }
|
||||
public Guid? ServiceId { get; set; }
|
||||
public bool Status { get; set; }
|
||||
}
|
||||
}
|
||||
|
224
Marco.Pms.Model/Entitlements/MenuStaticMaster.cs
Normal file
224
Marco.Pms.Model/Entitlements/MenuStaticMaster.cs
Normal file
@ -0,0 +1,224 @@
|
||||
using Marco.Pms.Model.AppMenu;
|
||||
|
||||
namespace Marco.Pms.Model.Entitlements
|
||||
{
|
||||
public static class MenuStaticMaster
|
||||
{
|
||||
public static readonly MenuSection menu = new MenuSection
|
||||
{
|
||||
Header = "Main Navigation",
|
||||
Title = "Main Menu",
|
||||
Items = new List<MenuItem>
|
||||
{
|
||||
new MenuItem
|
||||
{
|
||||
Text = "Dashboard",
|
||||
Icon = "bx bx-home",
|
||||
Available = true,
|
||||
Link = "/dashboard",
|
||||
PermissionIds = new List<string>(),
|
||||
Submenu = new List<SubMenuItem>()
|
||||
},
|
||||
new MenuItem
|
||||
{
|
||||
Text = "Projects",
|
||||
Icon = "bx bx-building-house",
|
||||
Available = true,
|
||||
Link = "",
|
||||
PermissionIds = new List<string>
|
||||
{
|
||||
"6ea44136-987e-44ba-9e5d-1cf8f5837ebc",
|
||||
"172fc9b6-755b-4f62-ab26-55c34a330614",
|
||||
"b94802ce-0689-4643-9e1d-11c86950c35b",
|
||||
"8d7cc6e3-9147-41f7-aaa7-fa507e450bd4",
|
||||
"cf2825ad-453b-46aa-91d9-27c124d63373",
|
||||
"9fcc5f87-25e3-4846-90ac-67a71ab92e3c",
|
||||
"08752f33-3b29-4816-b76b-ea8a968ed3c5",
|
||||
"6a32379b-8b3f-49a6-8c48-4b7ac1b55dc2",
|
||||
"db4e40c5-2ba9-4b6d-b8a6-a16a250ff99c"
|
||||
},
|
||||
Submenu = new List<SubMenuItem>
|
||||
{
|
||||
new SubMenuItem
|
||||
{
|
||||
Text = "Project List",
|
||||
Available = true,
|
||||
Link = "/projects",
|
||||
PermissionIds = new List<string>
|
||||
{
|
||||
"6ea44136-987e-44ba-9e5d-1cf8f5837ebc",
|
||||
"172fc9b6-755b-4f62-ab26-55c34a330614",
|
||||
"b94802ce-0689-4643-9e1d-11c86950c35b",
|
||||
"8d7cc6e3-9147-41f7-aaa7-fa507e450bd4",
|
||||
"cf2825ad-453b-46aa-91d9-27c124d63373",
|
||||
"9fcc5f87-25e3-4846-90ac-67a71ab92e3c",
|
||||
"08752f33-3b29-4816-b76b-ea8a968ed3c5"
|
||||
}
|
||||
},
|
||||
new SubMenuItem
|
||||
{
|
||||
Text = "Daily Task Planning",
|
||||
Available = true,
|
||||
Link = "/activities/task",
|
||||
PermissionIds = new List<string>
|
||||
{
|
||||
"8d7cc6e3-9147-41f7-aaa7-fa507e450bd4",
|
||||
"9fcc5f87-25e3-4846-90ac-67a71ab92e3c",
|
||||
"6a32379b-8b3f-49a6-8c48-4b7ac1b55dc2"
|
||||
}
|
||||
},
|
||||
new SubMenuItem
|
||||
{
|
||||
Text = "Daily Progress Report",
|
||||
Available = true,
|
||||
Link = "/activities/records",
|
||||
PermissionIds = new List<string>
|
||||
{
|
||||
"9fcc5f87-25e3-4846-90ac-67a71ab92e3c",
|
||||
"6a32379b-8b3f-49a6-8c48-4b7ac1b55dc2",
|
||||
"db4e40c5-2ba9-4b6d-b8a6-a16a250ff99c"
|
||||
}
|
||||
},
|
||||
new SubMenuItem
|
||||
{
|
||||
Text = "Image Gallary",
|
||||
Available = true,
|
||||
Link = "/gallary",
|
||||
PermissionIds = new List<string>()
|
||||
},
|
||||
new SubMenuItem
|
||||
{
|
||||
Text = "Organizations",
|
||||
Available = true,
|
||||
Link = "/organizations",
|
||||
PermissionIds = new List<string>
|
||||
{
|
||||
"068cb3c1-49c5-4746-9f29-1fce16e820ac",
|
||||
"c1ae1363-ab8a-4bd9-a9d1-8c2c6083873a",
|
||||
"7a6cf830-0008-4e03-b31d-0d050cb634f4"
|
||||
}
|
||||
},
|
||||
new SubMenuItem
|
||||
{
|
||||
Text = "Project Report",
|
||||
Available = true,
|
||||
Link = "/activities/reports",
|
||||
PermissionIds = new List<string>()
|
||||
},
|
||||
}
|
||||
},
|
||||
new MenuItem
|
||||
{
|
||||
Text = "Employees",
|
||||
Icon = "bx bx-user",
|
||||
Available = true,
|
||||
Link = "/employees",
|
||||
PermissionIds = new List<string>
|
||||
{
|
||||
"60611762-7f8a-4fb5-b53f-b1139918796b",
|
||||
"b82d2b7e-0d52-45f3-997b-c008ea460e7f",
|
||||
"a97d366a-c2bb-448d-be93-402bd2324566",
|
||||
"fbd213e0-0250-46f1-9f5f-4b2a1e6e76a3"
|
||||
},
|
||||
Submenu = new List<SubMenuItem>()
|
||||
},
|
||||
new MenuItem
|
||||
{
|
||||
Text = "Attendance",
|
||||
Icon = "bx bx-list-ul",
|
||||
Available = true,
|
||||
Link = "/activities/Attendance",
|
||||
PermissionIds = new List<string>
|
||||
{
|
||||
"915e6bff-65f6-4e3f-aea8-3fd217d3ea9e",
|
||||
"57802c4a-00aa-4a1f-a048-fd2f70dd44b6",
|
||||
"ccb0589f-712b-43de-92ed-5b6088e7dc4e"
|
||||
},
|
||||
Submenu = new List<SubMenuItem>()
|
||||
},
|
||||
new MenuItem
|
||||
{
|
||||
Text = "Directory",
|
||||
Icon = "bx bx-group",
|
||||
Available = true,
|
||||
Link = "/directory",
|
||||
PermissionIds = new List<string>
|
||||
{
|
||||
"4286a13b-bb40-4879-8c6d-18e9e393beda",
|
||||
"62668630-13ce-4f52-a0f0-db38af2230c5",
|
||||
"0f919170-92d4-4337-abd3-49b66fc871bb"
|
||||
},
|
||||
Submenu = new List<SubMenuItem>()
|
||||
},
|
||||
new MenuItem
|
||||
{
|
||||
Text = "Expense",
|
||||
Icon = "bx bx-receipt",
|
||||
Available = true,
|
||||
Link = "/expenses",
|
||||
PermissionIds = new List<string>
|
||||
{
|
||||
"385be49f-8fde-440e-bdbc-3dffeb8dd116",
|
||||
"01e06444-9ca7-4df4-b900-8c3fa051b92f",
|
||||
"0f57885d-bcb2-4711-ac95-d841ace6d5a7",
|
||||
"1f4bda08-1873-449a-bb66-3e8222bd871b",
|
||||
"eaafdd76-8aac-45f9-a530-315589c6deca",
|
||||
"ea5a1529-4ee8-4828-80ea-0e23c9d4dd11",
|
||||
"ea5a1529-4ee8-4828-80ea-0e23c9d4dd11"
|
||||
},
|
||||
Submenu = new List<SubMenuItem>()
|
||||
},
|
||||
new MenuItem
|
||||
{
|
||||
Text = "Administration",
|
||||
Icon = "bx bx-box",
|
||||
Available = true,
|
||||
Link = "",
|
||||
PermissionIds = new List<string>
|
||||
{
|
||||
"5ffbafe0-7ab0-48b1-bb50-c1bf76b65f9d",
|
||||
"588a8824-f924-4955-82d8-fc51956cf323",
|
||||
"d032cb1a-3f30-462c-bef0-7ace73a71c0b",
|
||||
"00e20637-ce8d-4417-bec4-9b31b5e65092",
|
||||
"647145c6-2108-4c98-aab4-178602236e55"
|
||||
},
|
||||
Submenu = new List<SubMenuItem>
|
||||
{
|
||||
new SubMenuItem
|
||||
{
|
||||
Text = "Tenant",
|
||||
Available = true,
|
||||
Link = "/tenants",
|
||||
PermissionIds = new List<string>
|
||||
{
|
||||
"d032cb1a-3f30-462c-bef0-7ace73a71c0b",
|
||||
"00e20637-ce8d-4417-bec4-9b31b5e65092",
|
||||
"647145c6-2108-4c98-aab4-178602236e55"
|
||||
}
|
||||
},
|
||||
new SubMenuItem
|
||||
{
|
||||
Text = "Masters",
|
||||
Available = true,
|
||||
Link = "/masters",
|
||||
PermissionIds = new List<string>
|
||||
{
|
||||
"5ffbafe0-7ab0-48b1-bb50-c1bf76b65f9d",
|
||||
"588a8824-f924-4955-82d8-fc51956cf323"
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
new MenuItem
|
||||
{
|
||||
Text = "Inventory",
|
||||
Icon = "bx bx-store",
|
||||
Available = true,
|
||||
Link = "/inventory",
|
||||
PermissionIds = new List<string>(),
|
||||
Submenu = new List<SubMenuItem>()
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
@ -54,6 +54,7 @@ namespace Marco.Pms.Model.Expenses
|
||||
public DateTime CreatedAt { get; set; }
|
||||
public string? TransactionId { get; set; }
|
||||
public string Description { get; set; } = string.Empty;
|
||||
public string ExpenseUId { get; set; } = string.Empty;
|
||||
public string? Location { get; set; }
|
||||
public string? GSTNumber { get; set; }
|
||||
public string SupplerName { get; set; } = string.Empty;
|
||||
|
@ -5,6 +5,7 @@
|
||||
public List<Guid>? BuildingIds { get; set; }
|
||||
public List<Guid>? FloorIds { get; set; }
|
||||
public List<Guid>? ActivityIds { get; set; }
|
||||
public List<Guid>? ServiceIds { get; set; }
|
||||
public DateTime? dateFrom { get; set; }
|
||||
public DateTime? dateTo { get; set; }
|
||||
}
|
||||
|
@ -36,7 +36,9 @@ namespace Marco.Pms.Model.Mapper
|
||||
IsRootUser = model.ApplicationUser?.IsRootUser ?? false,
|
||||
IsSystem = model.IsSystem,
|
||||
JoiningDate = model.JoiningDate,
|
||||
TenantId = model.TenantId ?? Guid.Empty
|
||||
TenantId = model.TenantId ?? Guid.Empty,
|
||||
HasApplicationAccess = model.HasApplicationAccess,
|
||||
OrganizationId = model.OrganizationId
|
||||
};
|
||||
}
|
||||
public static BasicEmployeeVM ToBasicEmployeeVMFromEmployee(this Employee employee)
|
||||
|
@ -19,6 +19,7 @@ namespace Marco.Pms.Model.MongoDBModels.Expenses
|
||||
public DateTime CreatedAt { get; set; }
|
||||
public DateTime ExpireAt { get; set; } = DateTime.UtcNow.Date.AddDays(1);
|
||||
public string SupplerName { get; set; } = string.Empty;
|
||||
public string? ExpenseUId { get; set; }
|
||||
public double Amount { get; set; }
|
||||
public ExpensesStatusMasterMongoDB Status { get; set; } = new ExpensesStatusMasterMongoDB();
|
||||
public List<ExpensesStatusMasterMongoDB> NextStatus { get; set; } = new List<ExpensesStatusMasterMongoDB>();
|
||||
|
@ -1,4 +1,5 @@
|
||||
using Marco.Pms.Model.Dtos.Attendance;
|
||||
using Marco.Pms.Model.ViewModels.Activities;
|
||||
|
||||
namespace Marco.Pms.Model.ViewModels.AttendanceVM
|
||||
{
|
||||
@ -6,15 +7,20 @@ namespace Marco.Pms.Model.ViewModels.AttendanceVM
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public Guid EmployeeId { get; set; }
|
||||
public Guid ProjectId { get; set; }
|
||||
public string? FirstName { get; set; }
|
||||
public string? LastName { get; set; }
|
||||
public string? EmployeeAvatar { get; set; }
|
||||
public string? OrganizationName { get; set; }
|
||||
public string? ProjectName { get; set; }
|
||||
public DateTime? CheckInTime { get; set; }
|
||||
public DateTime? CheckOutTime { get; set; }
|
||||
public DateTime? RequestedAt { get; set; }
|
||||
public DateTime? ApprovedAt { get; set; }
|
||||
public string? JobRoleName { get; set; }
|
||||
public ATTENDANCE_MARK_TYPE Activity { get; set; }
|
||||
|
||||
public BasicEmployeeVM? Approver { get; set; }
|
||||
public BasicEmployeeVM? RequestedBy { get; set; }
|
||||
public Guid? DocumentId { get; set; }
|
||||
public string? ThumbPreSignedUrl { get; set; }
|
||||
public string? PreSignedUrl { get; set; }
|
||||
|
@ -25,6 +25,8 @@
|
||||
public Guid TenantId { get; set; }
|
||||
public bool IsSystem { get; set; }
|
||||
public string? JobRole { get; set; }
|
||||
public bool HasApplicationAccess { get; set; }
|
||||
public Guid OrganizationId { get; set; }
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ namespace Marco.Pms.Model.ViewModels.Expenses
|
||||
public DateTime TransactionDate { get; set; }
|
||||
public DateTime CreatedAt { get; set; }
|
||||
public string SupplerName { get; set; } = string.Empty;
|
||||
public string? ExpenseUId { get; set; }
|
||||
public double Amount { get; set; }
|
||||
public ExpensesStatusMasterVM? Status { get; set; }
|
||||
public List<ExpensesStatusMasterVM>? NextStatus { get; set; }
|
||||
|
@ -18,6 +18,7 @@ namespace Marco.Pms.Model.ViewModels.Expanses
|
||||
public DateTime TransactionDate { get; set; }
|
||||
public DateTime CreatedAt { get; set; }
|
||||
public string SupplerName { get; set; } = string.Empty;
|
||||
public string? ExpenseUId { get; set; }
|
||||
public string Description { get; set; } = string.Empty;
|
||||
public string TransactionId { get; set; } = string.Empty;
|
||||
public double Amount { get; set; }
|
||||
|
@ -1,4 +1,5 @@
|
||||
using Marco.Pms.Model.ViewModels.Activities;
|
||||
using Marco.Pms.Model.Master;
|
||||
using Marco.Pms.Model.ViewModels.Activities;
|
||||
|
||||
namespace Marco.Pms.Model.ViewModels.Organization
|
||||
{
|
||||
@ -19,6 +20,7 @@ namespace Marco.Pms.Model.ViewModels.Organization
|
||||
public DateTime? UpdatedAt { get; set; }
|
||||
public bool IsActive { get; set; }
|
||||
public List<ProjectServiceMappingVM>? Projects { get; set; }
|
||||
public List<GlobalServiceMaster>? Services { get; set; }
|
||||
public string? logoImage { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ namespace Marco.Pms.Model.ViewModels.Organization
|
||||
public string? ContactPerson { get; set; }
|
||||
public double SPRID { get; set; }
|
||||
public string? logoImage { get; set; }
|
||||
public string? OrganizationType { get; set; }
|
||||
public DateTime AssignedDate { get; set; }
|
||||
public BasicEmployeeVM? AssignedBy { get; set; }
|
||||
public ServiceMasterVM? Service { get; set; }
|
||||
|
@ -7,15 +7,18 @@
|
||||
public required string TimeStamp { get; set; }
|
||||
public int TodaysAttendances { get; set; }
|
||||
public int TotalEmployees { get; set; }
|
||||
public double AttendancePercentage { get; set; }
|
||||
public int RegularizationPending { get; set; }
|
||||
public int CheckoutPending { get; set; }
|
||||
public double TotalPlannedWork { get; set; }
|
||||
public double TotalCompletedWork { get; set; }
|
||||
public double CompletionStatus { get; set; }
|
||||
public double TotalPlannedTask { get; set; }
|
||||
public double TotalCompletedTask { get; set; }
|
||||
public double CompletionStatus { get; set; }
|
||||
public double TaskPercentage { get; set; }
|
||||
public int ReportPending { get; set; }
|
||||
public int TodaysAssignTasks { get; set; }
|
||||
public int TodaysCompletedTasks { get; set; }
|
||||
public List<TeamOnSite> TeamOnSite { get; set; } = new List<TeamOnSite>();
|
||||
public List<PerformedTask> PerformedTasks { get; set; } = new List<PerformedTask>();
|
||||
public List<PerformedAttendance> PerformedAttendance { get; set; } = new List<PerformedAttendance>();
|
||||
|
@ -456,6 +456,14 @@ namespace Marco.Pms.Services.Controllers
|
||||
// Step 2: Fetch all menu sections for the tenant
|
||||
var menus = await _sideBarMenuHelper.GetAllMenuSectionsAsync(tenantId);
|
||||
|
||||
if (!(menus?.Any() ?? false))
|
||||
{
|
||||
menus = new List<MenuSection>
|
||||
{
|
||||
MenuStaticMaster.menu
|
||||
};
|
||||
}
|
||||
|
||||
foreach (var menu in menus)
|
||||
{
|
||||
var allowedItems = new List<MenuItem>();
|
||||
@ -632,6 +640,13 @@ namespace Marco.Pms.Services.Controllers
|
||||
{
|
||||
// Step 2: Fetch all menu sections for the tenant
|
||||
var menus = await _sideBarMenuHelper.GetAllMenuSectionsAsync(tenantId);
|
||||
if (!(menus?.Any() ?? false))
|
||||
{
|
||||
menus = new List<MenuSection>
|
||||
{
|
||||
MenuStaticMaster.menu
|
||||
};
|
||||
}
|
||||
List<MenuSectionApplicationVM> response = new List<MenuSectionApplicationVM>();
|
||||
|
||||
foreach (var menu in menus)
|
||||
@ -718,7 +733,42 @@ namespace Marco.Pms.Services.Controllers
|
||||
menu.Items = allowedItems;
|
||||
}
|
||||
|
||||
if (await _permissions.HasPermission(PermissionsMaster.ViewDocument, employeeId))
|
||||
var viewDocumentTask = Task.Run(async () =>
|
||||
{
|
||||
using var taskScope = _serviceScopeFactory.CreateScope();
|
||||
var permissions = taskScope.ServiceProvider.GetRequiredService<PermissionServices>();
|
||||
return await permissions.HasPermission(PermissionsMaster.ViewDocument, employeeId);
|
||||
});
|
||||
|
||||
var uploadDocumentTask = Task.Run(async () =>
|
||||
{
|
||||
using var taskScope = _serviceScopeFactory.CreateScope();
|
||||
var permissions = taskScope.ServiceProvider.GetRequiredService<PermissionServices>();
|
||||
return await permissions.HasPermission(PermissionsMaster.UploadDocument, employeeId);
|
||||
});
|
||||
|
||||
var verifyDocumentTask = Task.Run(async () =>
|
||||
{
|
||||
using var taskScope = _serviceScopeFactory.CreateScope();
|
||||
var permissions = taskScope.ServiceProvider.GetRequiredService<PermissionServices>();
|
||||
return await permissions.HasPermission(PermissionsMaster.VerifyDocument, employeeId);
|
||||
});
|
||||
|
||||
var downloadDocumentTask = Task.Run(async () =>
|
||||
{
|
||||
using var taskScope = _serviceScopeFactory.CreateScope();
|
||||
var permissions = taskScope.ServiceProvider.GetRequiredService<PermissionServices>();
|
||||
return await permissions.HasPermission(PermissionsMaster.DownloadDocument, employeeId);
|
||||
});
|
||||
|
||||
await Task.WhenAll(viewDocumentTask, uploadDocumentTask, verifyDocumentTask, downloadDocumentTask);
|
||||
|
||||
var viewDocument = viewDocumentTask.Result;
|
||||
var uploadDocument = uploadDocumentTask.Result;
|
||||
var verifyDocument = verifyDocumentTask.Result;
|
||||
var downloadDocument = downloadDocumentTask.Result;
|
||||
|
||||
if (viewDocument || uploadDocument || verifyDocument || downloadDocument)
|
||||
{
|
||||
response.Add(new MenuSectionApplicationVM
|
||||
{
|
||||
|
@ -1,4 +1,5 @@
|
||||
using Marco.Pms.DataAccess.Data;
|
||||
using AutoMapper;
|
||||
using Marco.Pms.DataAccess.Data;
|
||||
using Marco.Pms.Model.AttendanceModule;
|
||||
using Marco.Pms.Model.Dtos.Attendance;
|
||||
using Marco.Pms.Model.Employees;
|
||||
@ -6,6 +7,7 @@ using Marco.Pms.Model.Entitlements;
|
||||
using Marco.Pms.Model.Mapper;
|
||||
using Marco.Pms.Model.Projects;
|
||||
using Marco.Pms.Model.Utilities;
|
||||
using Marco.Pms.Model.ViewModels.Activities;
|
||||
using Marco.Pms.Model.ViewModels.AttendanceVM;
|
||||
using Marco.Pms.Services.Hubs;
|
||||
using Marco.Pms.Services.Service;
|
||||
@ -28,48 +30,41 @@ namespace MarcoBMS.Services.Controllers
|
||||
public class AttendanceController : ControllerBase
|
||||
{
|
||||
private readonly ApplicationDbContext _context;
|
||||
private readonly EmployeeHelper _employeeHelper;
|
||||
private readonly IProjectServices _projectServices;
|
||||
private readonly IServiceScopeFactory _serviceScopeFactory;
|
||||
private readonly UserHelper _userHelper;
|
||||
private readonly S3UploadService _s3Service;
|
||||
private readonly PermissionServices _permission;
|
||||
private readonly ILoggingService _logger;
|
||||
private readonly IHubContext<MarcoHub> _signalR;
|
||||
private readonly IFirebaseService _firebase;
|
||||
private readonly Guid tenantId;
|
||||
private readonly IMapper _mapper;
|
||||
|
||||
public AttendanceController(
|
||||
ApplicationDbContext context, EmployeeHelper employeeHelper, IProjectServices projectServices, UserHelper userHelper,
|
||||
S3UploadService s3Service, ILoggingService logger, PermissionServices permission, IHubContext<MarcoHub> signalR, IFirebaseService firebase)
|
||||
ApplicationDbContext context,
|
||||
UserHelper userHelper,
|
||||
IServiceScopeFactory serviceScopeFactory,
|
||||
ILoggingService logger,
|
||||
PermissionServices permission,
|
||||
IMapper mapper)
|
||||
{
|
||||
_context = context;
|
||||
_employeeHelper = employeeHelper;
|
||||
_projectServices = projectServices;
|
||||
_serviceScopeFactory = serviceScopeFactory;
|
||||
_userHelper = userHelper;
|
||||
_s3Service = s3Service;
|
||||
_logger = logger;
|
||||
_permission = permission;
|
||||
_signalR = signalR;
|
||||
_firebase = firebase;
|
||||
}
|
||||
|
||||
private Guid GetTenantId()
|
||||
{
|
||||
return _userHelper.GetTenantId();
|
||||
//var tenant = User.FindFirst("TenantId")?.Value;
|
||||
//return (tenant != null ? Convert.ToInt32(tenant) : 1);
|
||||
_mapper = mapper;
|
||||
tenantId = userHelper.GetTenantId();
|
||||
}
|
||||
|
||||
[HttpGet("log/attendance/{attendanceid}")]
|
||||
|
||||
public async Task<IActionResult> GetAttendanceLogById(Guid attendanceid)
|
||||
{
|
||||
Guid TenantId = GetTenantId();
|
||||
using var scope = _serviceScopeFactory.CreateScope();
|
||||
var _s3Service = scope.ServiceProvider.GetRequiredService<S3UploadService>();
|
||||
|
||||
List<AttendanceLog> lstAttendance = await _context.AttendanceLogs
|
||||
.Include(a => a.Document)
|
||||
.Include(a => a.Employee)
|
||||
.Include(a => a.UpdatedByEmployee)
|
||||
.Where(c => c.AttendanceId == attendanceid && c.TenantId == TenantId)
|
||||
.Where(c => c.AttendanceId == attendanceid && c.TenantId == tenantId)
|
||||
.ToListAsync();
|
||||
|
||||
List<AttendanceLogVM> attendanceLogVMs = new List<AttendanceLogVM>();
|
||||
@ -85,30 +80,42 @@ namespace MarcoBMS.Services.Controllers
|
||||
}
|
||||
|
||||
[HttpGet("log/employee/{employeeId}")]
|
||||
public async Task<IActionResult> GetAttendanceLogByEmployeeId(Guid employeeId, [FromQuery] string? dateFrom = null, [FromQuery] string? dateTo = null)
|
||||
public async Task<IActionResult> GetAttendanceLogByEmployeeId(Guid employeeId, [FromQuery] DateTime? dateFrom = null, [FromQuery] DateTime? dateTo = null)
|
||||
{
|
||||
Guid TenantId = GetTenantId();
|
||||
DateTime fromDate = new DateTime();
|
||||
DateTime toDate = new DateTime();
|
||||
|
||||
if (dateFrom != null && DateTime.TryParse(dateFrom, out fromDate) == false)
|
||||
{
|
||||
_logger.LogWarning("User sent Invalid from Date while featching attendance logs");
|
||||
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid Date", "Invalid Date", 400));
|
||||
}
|
||||
if (dateTo != null && DateTime.TryParse(dateTo, out toDate) == false)
|
||||
{
|
||||
_logger.LogWarning("User sent Invalid to Date while featching attendance logs");
|
||||
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid Date", "Invalid Date", 400));
|
||||
}
|
||||
|
||||
if (employeeId == Guid.Empty)
|
||||
{
|
||||
_logger.LogWarning("The employee Id sent by user is empty");
|
||||
return BadRequest(ApiResponse<object>.ErrorResponse("Employee ID is required and must not be Empty.", "Employee ID is required and must not be empty.", 400));
|
||||
}
|
||||
List<Attendance> attendances = await _context.Attendes.Where(c => c.EmployeeID == employeeId && c.TenantId == TenantId && c.AttendanceDate.Date >= fromDate && c.AttendanceDate.Date <= toDate).ToListAsync();
|
||||
Employee? employee = await _context.Employees.Include(e => e.JobRole).FirstOrDefaultAsync(e => e.Id == employeeId && e.TenantId == TenantId && e.IsActive);
|
||||
|
||||
Employee? employee = await _context.Employees.Include(e => e.JobRole).FirstOrDefaultAsync(e => e.Id == employeeId && e.TenantId == tenantId);
|
||||
if (employee == null)
|
||||
{
|
||||
_logger.LogWarning("Employee {EmployeeId} not found", employeeId);
|
||||
return NotFound(ApiResponse<object>.ErrorResponse("Employee not found", "Employee not found in database", 404));
|
||||
}
|
||||
|
||||
if (!dateFrom.HasValue)
|
||||
{
|
||||
dateFrom = DateTime.UtcNow;
|
||||
}
|
||||
if (!dateTo.HasValue)
|
||||
{
|
||||
var days = 0 - 7;
|
||||
dateTo = dateFrom.Value.AddDays(days);
|
||||
}
|
||||
|
||||
List<Attendance> attendances = await _context.Attendes
|
||||
.Include(a => a.RequestedBy)
|
||||
.ThenInclude(e => e!.JobRole)
|
||||
.Include(a => a.RequestedBy)
|
||||
.ThenInclude(e => e!.JobRole)
|
||||
.Where(c => c.EmployeeId == employeeId && c.TenantId == tenantId && c.AttendanceDate.Date >= dateFrom && c.AttendanceDate.Date <= dateTo).ToListAsync();
|
||||
|
||||
var projectIds = attendances.Select(a => a.ProjectID).Distinct().ToList();
|
||||
|
||||
var projects = await _context.Projects.Where(p => projectIds.Contains(p.Id) && p.TenantId == tenantId).ToListAsync();
|
||||
|
||||
List<EmployeeAttendanceVM> results = new List<EmployeeAttendanceVM>();
|
||||
|
||||
if (employee != null)
|
||||
@ -121,11 +128,17 @@ namespace MarcoBMS.Services.Controllers
|
||||
EmployeeId = employee.Id,
|
||||
FirstName = employee.FirstName,
|
||||
LastName = employee.LastName,
|
||||
ProjectId = attendance.ProjectID,
|
||||
ProjectName = projects.Where(p => p.Id == attendance.ProjectID).Select(p => p.Name).FirstOrDefault(),
|
||||
CheckInTime = attendance.InTime,
|
||||
CheckOutTime = attendance.OutTime,
|
||||
JobRoleName = employee.JobRole != null ? employee.JobRole.Name : "",
|
||||
Activity = attendance.Activity,
|
||||
EmployeeAvatar = null
|
||||
EmployeeAvatar = null,
|
||||
RequestedAt = attendance.RequestedAt,
|
||||
RequestedBy = _mapper.Map<BasicEmployeeVM>(attendance.RequestedBy),
|
||||
ApprovedAt = attendance.ApprovedAt,
|
||||
Approver = _mapper.Map<BasicEmployeeVM>(attendance.Approver)
|
||||
};
|
||||
results.Add(result);
|
||||
}
|
||||
@ -145,11 +158,12 @@ namespace MarcoBMS.Services.Controllers
|
||||
/// <returns></returns>
|
||||
|
||||
[HttpGet("project/log")]
|
||||
|
||||
public async Task<IActionResult> EmployeeAttendanceByDateRange([FromQuery] Guid projectId, [FromQuery] Guid? organizationId, [FromQuery] string? dateFrom = null, [FromQuery] string? dateTo = null)
|
||||
{
|
||||
Guid tenantId = GetTenantId();
|
||||
var LoggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
|
||||
using var scope = _serviceScopeFactory.CreateScope();
|
||||
var _projectServices = scope.ServiceProvider.GetRequiredService<IProjectServices>();
|
||||
|
||||
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
|
||||
|
||||
var project = await _context.Projects.AsNoTracking().FirstOrDefaultAsync(p => p.Id == projectId && p.TenantId == tenantId);
|
||||
if (project == null)
|
||||
@ -158,13 +172,13 @@ namespace MarcoBMS.Services.Controllers
|
||||
return NotFound(ApiResponse<object>.ErrorResponse("Project not found."));
|
||||
}
|
||||
|
||||
var hasTeamAttendancePermission = await _permission.HasPermission(PermissionsMaster.TeamAttendance, LoggedInEmployee.Id);
|
||||
var hasSelfAttendancePermission = await _permission.HasPermission(PermissionsMaster.SelfAttendance, LoggedInEmployee.Id);
|
||||
var hasProjectPermission = await _permission.HasProjectPermission(LoggedInEmployee, projectId);
|
||||
var hasTeamAttendancePermission = await _permission.HasPermission(PermissionsMaster.TeamAttendance, loggedInEmployee.Id);
|
||||
var hasSelfAttendancePermission = await _permission.HasPermission(PermissionsMaster.SelfAttendance, loggedInEmployee.Id);
|
||||
var hasProjectPermission = await _permission.HasProjectPermission(loggedInEmployee, projectId);
|
||||
|
||||
if (!hasProjectPermission)
|
||||
{
|
||||
_logger.LogWarning("Employee {EmployeeId} tries to access attendance of project {ProjectId}, but don't have access", LoggedInEmployee.Id, projectId);
|
||||
_logger.LogWarning("Employee {EmployeeId} tries to access attendance of project {ProjectId}, but don't have access", loggedInEmployee.Id, projectId);
|
||||
return Unauthorized(ApiResponse<object>.ErrorResponse("Unauthorized access", "Unauthorized access", 404));
|
||||
}
|
||||
|
||||
@ -197,7 +211,13 @@ namespace MarcoBMS.Services.Controllers
|
||||
|
||||
if (hasTeamAttendancePermission)
|
||||
{
|
||||
List<Attendance> lstAttendance = await _context.Attendes.Where(c => c.ProjectID == projectId && c.AttendanceDate.Date >= fromDate.Date && c.AttendanceDate.Date <= toDate.Date && c.TenantId == tenantId).ToListAsync();
|
||||
List<Attendance> lstAttendance = await _context.Attendes
|
||||
.Include(a => a.RequestedBy)
|
||||
.ThenInclude(e => e!.JobRole)
|
||||
.Include(a => a.Approver)
|
||||
.ThenInclude(e => e!.JobRole)
|
||||
.Where(c => c.ProjectID == projectId && c.AttendanceDate.Date >= fromDate.Date && c.AttendanceDate.Date <= toDate.Date && c.TenantId == tenantId)
|
||||
.ToListAsync();
|
||||
|
||||
|
||||
List<ProjectAllocation> projectteam = await _projectServices.GetTeamByProject(tenantId, projectId, organizationId, true);
|
||||
@ -209,9 +229,13 @@ namespace MarcoBMS.Services.Controllers
|
||||
Id = attendance.Id,
|
||||
CheckInTime = attendance.InTime,
|
||||
CheckOutTime = attendance.OutTime,
|
||||
Activity = attendance.Activity
|
||||
Activity = attendance.Activity,
|
||||
ApprovedAt = attendance.ApprovedAt,
|
||||
Approver = _mapper.Map<BasicEmployeeVM>(attendance.Approver),
|
||||
RequestedAt = attendance.RequestedAt,
|
||||
RequestedBy = _mapper.Map<BasicEmployeeVM>(attendance.RequestedBy)
|
||||
};
|
||||
teamMember = projectteam.Find(x => x.EmployeeId == attendance.EmployeeID);
|
||||
teamMember = projectteam.Find(x => x.EmployeeId == attendance.EmployeeId);
|
||||
if (teamMember != null)
|
||||
{
|
||||
result1.EmployeeAvatar = null;
|
||||
@ -222,6 +246,8 @@ namespace MarcoBMS.Services.Controllers
|
||||
result1.LastName = teamMember.Employee.LastName;
|
||||
result1.JobRoleName = teamMember.Employee.JobRole != null ? teamMember.Employee.JobRole.Name : null;
|
||||
result1.OrganizationName = teamMember.Employee.Organization?.Name;
|
||||
result1.ProjectId = projectId;
|
||||
result1.ProjectName = teamMember.Project?.Name;
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -239,13 +265,23 @@ namespace MarcoBMS.Services.Controllers
|
||||
else if (hasSelfAttendancePermission)
|
||||
{
|
||||
List<Attendance> lstAttendances = await _context.Attendes
|
||||
.Where(c => c.ProjectID == projectId && c.EmployeeID == LoggedInEmployee.Id && c.AttendanceDate.Date >= fromDate.Date && c.AttendanceDate.Date <= toDate.Date && c.TenantId == tenantId)
|
||||
.Include(a => a.RequestedBy)
|
||||
.ThenInclude(e => e!.JobRole)
|
||||
.Include(a => a.Approver)
|
||||
.ThenInclude(e => e!.JobRole)
|
||||
.Where(c => c.ProjectID == projectId && c.EmployeeId == loggedInEmployee.Id && c.AttendanceDate.Date >= fromDate.Date &&
|
||||
c.AttendanceDate.Date <= toDate.Date && c.TenantId == tenantId)
|
||||
.ToListAsync();
|
||||
|
||||
var projectAllocationQuery = _context.ProjectAllocations
|
||||
.Include(pa => pa.Project)
|
||||
.Include(pa => pa.Employee)
|
||||
.ThenInclude(e => e!.Organization)
|
||||
.Where(pa => pa.ProjectId == projectId && pa.EmployeeId == LoggedInEmployee.Id && pa.TenantId == tenantId && pa.IsActive);
|
||||
.Include(pa => pa.Employee)
|
||||
.ThenInclude(e => e!.JobRole)
|
||||
.Where(pa => pa.EmployeeId == loggedInEmployee.Id && pa.TenantId == tenantId && pa.IsActive &&
|
||||
pa.ProjectId == projectId && pa.Project != null &&
|
||||
pa.Employee != null && pa.Employee.Organization != null && pa.Employee.JobRole != null);
|
||||
|
||||
if (organizationId.HasValue)
|
||||
{
|
||||
@ -267,9 +303,15 @@ namespace MarcoBMS.Services.Controllers
|
||||
LastName = projectAllocation.Employee?.LastName,
|
||||
JobRoleName = projectAllocation.Employee?.JobRole?.Name,
|
||||
OrganizationName = projectAllocation.Employee?.Organization?.Name,
|
||||
ProjectId = attendance.ProjectID,
|
||||
ProjectName = projectAllocation.Project?.Name,
|
||||
CheckInTime = attendance.InTime,
|
||||
CheckOutTime = attendance.OutTime,
|
||||
Activity = attendance.Activity
|
||||
Activity = attendance.Activity,
|
||||
ApprovedAt = attendance.ApprovedAt,
|
||||
Approver = _mapper.Map<BasicEmployeeVM>(attendance.Approver),
|
||||
RequestedAt = attendance.RequestedAt,
|
||||
RequestedBy = _mapper.Map<BasicEmployeeVM>(attendance.RequestedBy)
|
||||
};
|
||||
result.Add(result1);
|
||||
}
|
||||
@ -291,7 +333,6 @@ namespace MarcoBMS.Services.Controllers
|
||||
/// <returns>An IActionResult containing a list of employee attendance records or an error response.</returns>
|
||||
public async Task<IActionResult> EmployeeAttendanceByProjectAsync([FromQuery] Guid projectId, [FromQuery] Guid? organizationId, [FromQuery] bool includeInactive, [FromQuery] string? date = null)
|
||||
{
|
||||
var tenantId = GetTenantId();
|
||||
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
|
||||
|
||||
// --- 1. Initial Validation and Permission Checks ---
|
||||
@ -353,7 +394,9 @@ namespace MarcoBMS.Services.Controllers
|
||||
[HttpGet("regularize")]
|
||||
public async Task<IActionResult> GetRequestRegularizeAttendance([FromQuery] Guid projectId, [FromQuery] Guid? organizationId, [FromQuery] bool IncludeInActive)
|
||||
{
|
||||
Guid TenantId = GetTenantId();
|
||||
using var scope = _serviceScopeFactory.CreateScope();
|
||||
var _projectServices = scope.ServiceProvider.GetRequiredService<IProjectServices>();
|
||||
|
||||
Employee LoggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
|
||||
var result = new List<EmployeeAttendanceVM>();
|
||||
var hasProjectPermission = await _permission.HasProjectPermission(LoggedInEmployee, projectId);
|
||||
@ -364,15 +407,20 @@ namespace MarcoBMS.Services.Controllers
|
||||
return Unauthorized(ApiResponse<object>.ErrorResponse("Unauthorized access", "Unauthorized access", 404));
|
||||
}
|
||||
|
||||
List<Attendance> lstAttendance = await _context.Attendes.Where(c => c.ProjectID == projectId && c.Activity == ATTENDANCE_MARK_TYPE.REQUEST_REGULARIZE && c.TenantId == TenantId).ToListAsync();
|
||||
List<Attendance> lstAttendance = await _context.Attendes
|
||||
.Include(a => a.RequestedBy)
|
||||
.ThenInclude(e => e!.JobRole)
|
||||
.Include(a => a.Approver)
|
||||
.ThenInclude(e => e!.JobRole)
|
||||
.Where(c => c.ProjectID == projectId && c.Activity == ATTENDANCE_MARK_TYPE.REQUEST_REGULARIZE && c.TenantId == tenantId)
|
||||
.ToListAsync();
|
||||
|
||||
List<ProjectAllocation> projectteam = await _projectServices.GetTeamByProject(TenantId, projectId, organizationId, true);
|
||||
List<ProjectAllocation> projectteam = await _projectServices.GetTeamByProject(tenantId, projectId, organizationId, true);
|
||||
var idList = projectteam.Select(p => p.EmployeeId).ToList();
|
||||
var jobRole = await _context.JobRoles.ToListAsync();
|
||||
|
||||
foreach (Attendance attende in lstAttendance)
|
||||
{
|
||||
var teamMember = projectteam.Find(m => m.EmployeeId == attende.EmployeeID);
|
||||
var teamMember = projectteam.Find(m => m.EmployeeId == attende.EmployeeId);
|
||||
if (teamMember != null && teamMember.Employee != null && teamMember.Employee.JobRole != null)
|
||||
{
|
||||
var result1 = new EmployeeAttendanceVM()
|
||||
@ -382,11 +430,17 @@ namespace MarcoBMS.Services.Controllers
|
||||
CheckOutTime = attende.OutTime,
|
||||
Activity = attende.Activity,
|
||||
EmployeeAvatar = null,
|
||||
EmployeeId = attende.EmployeeID,
|
||||
EmployeeId = attende.EmployeeId,
|
||||
FirstName = teamMember.Employee.FirstName,
|
||||
LastName = teamMember.Employee.LastName,
|
||||
JobRoleName = teamMember.Employee.JobRole.Name,
|
||||
OrganizationName = teamMember.Employee.Organization?.Name
|
||||
OrganizationName = teamMember.Employee.Organization?.Name,
|
||||
ProjectId = projectId,
|
||||
ProjectName = teamMember.Project?.Name,
|
||||
ApprovedAt = attende.ApprovedAt,
|
||||
Approver = _mapper.Map<BasicEmployeeVM>(attende.Approver),
|
||||
RequestedAt = attende.RequestedAt,
|
||||
RequestedBy = _mapper.Map<BasicEmployeeVM>(attende.RequestedBy)
|
||||
};
|
||||
result.Add(result1);
|
||||
}
|
||||
@ -416,13 +470,17 @@ namespace MarcoBMS.Services.Controllers
|
||||
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid data", errors, 400));
|
||||
}
|
||||
|
||||
Guid TenantId = GetTenantId();
|
||||
var currentEmployee = await _userHelper.GetCurrentEmployeeAsync();
|
||||
using var scope = _serviceScopeFactory.CreateScope();
|
||||
var _signalR = scope.ServiceProvider.GetRequiredService<IHubContext<MarcoHub>>();
|
||||
var _employeeHelper = scope.ServiceProvider.GetRequiredService<EmployeeHelper>();
|
||||
var _firebase = scope.ServiceProvider.GetRequiredService<IFirebaseService>();
|
||||
|
||||
var currentEmployee = await _userHelper.GetCurrentEmployeeAsync();
|
||||
using var transaction = await _context.Database.BeginTransactionAsync();
|
||||
|
||||
try
|
||||
{
|
||||
Attendance? attendance = await _context.Attendes.FirstOrDefaultAsync(a => a.Id == recordAttendanceDot.Id && a.TenantId == TenantId); ;
|
||||
Attendance? attendance = await _context.Attendes.FirstOrDefaultAsync(a => a.Id == recordAttendanceDot.Id && a.TenantId == tenantId); ;
|
||||
|
||||
if (recordAttendanceDot.MarkTime == null)
|
||||
{
|
||||
@ -450,10 +508,6 @@ namespace MarcoBMS.Services.Controllers
|
||||
{
|
||||
attendance.IsApproved = true;
|
||||
attendance.Activity = ATTENDANCE_MARK_TYPE.REGULARIZE;
|
||||
|
||||
|
||||
//string timeString = "10:30 PM"; // Format: "hh:mm tt"
|
||||
|
||||
attendance.OutTime = finalDateTime;
|
||||
}
|
||||
else if (recordAttendanceDot.Action == ATTENDANCE_MARK_TYPE.REQUEST_REGULARIZE)
|
||||
@ -464,6 +518,8 @@ namespace MarcoBMS.Services.Controllers
|
||||
{
|
||||
attendance.OutTime = finalDateTime;
|
||||
attendance.Activity = ATTENDANCE_MARK_TYPE.REQUEST_REGULARIZE;
|
||||
attendance.RequestedById = currentEmployee.Id;
|
||||
attendance.RequestedAt = DateTime.UtcNow;
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -476,13 +532,16 @@ namespace MarcoBMS.Services.Controllers
|
||||
{
|
||||
attendance.IsApproved = true;
|
||||
attendance.Activity = ATTENDANCE_MARK_TYPE.REGULARIZE;
|
||||
attendance.ApprovedBy = currentEmployee.Id;
|
||||
attendance.ApprovedById = currentEmployee.Id;
|
||||
attendance.ApprovedAt = DateTime.UtcNow;
|
||||
// do nothing
|
||||
}
|
||||
else if (recordAttendanceDot.Action == ATTENDANCE_MARK_TYPE.REGULARIZE_REJECT)
|
||||
{
|
||||
attendance.IsApproved = false;
|
||||
attendance.Activity = ATTENDANCE_MARK_TYPE.REGULARIZE_REJECT;
|
||||
attendance.ApprovedById = currentEmployee.Id;
|
||||
attendance.ApprovedAt = DateTime.UtcNow;
|
||||
// do nothing
|
||||
}
|
||||
attendance.Date = DateTime.UtcNow;
|
||||
@ -493,11 +552,11 @@ namespace MarcoBMS.Services.Controllers
|
||||
else
|
||||
{
|
||||
attendance = new Attendance();
|
||||
attendance.TenantId = TenantId;
|
||||
attendance.TenantId = tenantId;
|
||||
attendance.AttendanceDate = recordAttendanceDot.Date;
|
||||
// attendance.Activity = recordAttendanceDot.Action;
|
||||
attendance.Comment = recordAttendanceDot.Comment;
|
||||
attendance.EmployeeID = recordAttendanceDot.EmployeeID;
|
||||
attendance.EmployeeId = recordAttendanceDot.EmployeeID;
|
||||
attendance.ProjectID = recordAttendanceDot.ProjectID;
|
||||
attendance.Date = DateTime.UtcNow;
|
||||
|
||||
@ -525,7 +584,7 @@ namespace MarcoBMS.Services.Controllers
|
||||
Latitude = recordAttendanceDot.Latitude,
|
||||
Longitude = recordAttendanceDot.Longitude,
|
||||
|
||||
TenantId = TenantId,
|
||||
TenantId = tenantId,
|
||||
UpdatedBy = currentEmployee.Id,
|
||||
UpdatedOn = recordAttendanceDot.Date
|
||||
};
|
||||
@ -572,7 +631,7 @@ namespace MarcoBMS.Services.Controllers
|
||||
|
||||
var name = $"{vm.FirstName} {vm.LastName}";
|
||||
|
||||
await _firebase.SendAttendanceMessageAsync(attendance.ProjectID, name, recordAttendanceDot.Action, attendance.EmployeeID, TenantId);
|
||||
await _firebase.SendAttendanceMessageAsync(attendance.ProjectID, name, recordAttendanceDot.Action, attendance.EmployeeId, tenantId);
|
||||
|
||||
});
|
||||
|
||||
@ -608,7 +667,12 @@ namespace MarcoBMS.Services.Controllers
|
||||
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid data", errors, 400));
|
||||
}
|
||||
|
||||
Guid tenantId = GetTenantId();
|
||||
using var scope = _serviceScopeFactory.CreateScope();
|
||||
var _s3Service = scope.ServiceProvider.GetRequiredService<S3UploadService>();
|
||||
var _signalR = scope.ServiceProvider.GetRequiredService<IHubContext<MarcoHub>>();
|
||||
var _employeeHelper = scope.ServiceProvider.GetRequiredService<EmployeeHelper>();
|
||||
var _firebase = scope.ServiceProvider.GetRequiredService<IFirebaseService>();
|
||||
|
||||
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
|
||||
var batchId = Guid.NewGuid();
|
||||
|
||||
@ -640,7 +704,7 @@ namespace MarcoBMS.Services.Controllers
|
||||
TenantId = tenantId,
|
||||
AttendanceDate = recordAttendanceDot.Date,
|
||||
Comment = recordAttendanceDot.Comment,
|
||||
EmployeeID = recordAttendanceDot.EmployeeID,
|
||||
EmployeeId = recordAttendanceDot.EmployeeID,
|
||||
ProjectID = recordAttendanceDot.ProjectID,
|
||||
Date = DateTime.UtcNow,
|
||||
InTime = finalDateTime,
|
||||
@ -672,6 +736,8 @@ namespace MarcoBMS.Services.Controllers
|
||||
{
|
||||
attendance.OutTime = finalDateTime;
|
||||
attendance.Activity = ATTENDANCE_MARK_TYPE.REQUEST_REGULARIZE;
|
||||
attendance.RequestedById = loggedInEmployee.Id;
|
||||
attendance.RequestedAt = DateTime.UtcNow;
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -682,10 +748,14 @@ namespace MarcoBMS.Services.Controllers
|
||||
case ATTENDANCE_MARK_TYPE.REGULARIZE:
|
||||
attendance.IsApproved = true;
|
||||
attendance.Activity = ATTENDANCE_MARK_TYPE.REGULARIZE;
|
||||
attendance.ApprovedById = loggedInEmployee.Id;
|
||||
attendance.ApprovedAt = DateTime.UtcNow;
|
||||
break;
|
||||
case ATTENDANCE_MARK_TYPE.REGULARIZE_REJECT:
|
||||
attendance.IsApproved = false;
|
||||
attendance.Activity = ATTENDANCE_MARK_TYPE.REGULARIZE_REJECT;
|
||||
attendance.ApprovedById = loggedInEmployee.Id;
|
||||
attendance.ApprovedAt = DateTime.UtcNow;
|
||||
break;
|
||||
}
|
||||
|
||||
@ -781,7 +851,7 @@ namespace MarcoBMS.Services.Controllers
|
||||
|
||||
var name = $"{vm.FirstName} {vm.LastName}";
|
||||
|
||||
await _firebase.SendAttendanceMessageAsync(attendance.ProjectID, name, recordAttendanceDot.Action, attendance.EmployeeID, tenantId);
|
||||
await _firebase.SendAttendanceMessageAsync(attendance.ProjectID, name, recordAttendanceDot.Action, attendance.EmployeeId, tenantId);
|
||||
|
||||
});
|
||||
|
||||
@ -818,6 +888,7 @@ namespace MarcoBMS.Services.Controllers
|
||||
// This single query joins ProjectAllocations with Employees and performs a LEFT JOIN with Attendances.
|
||||
// This is far more efficient than fetching collections and joining them in memory.
|
||||
var query = _context.ProjectAllocations
|
||||
.Include(pa => pa.Project)
|
||||
.Include(pa => pa.Employee)
|
||||
.ThenInclude(e => e!.Organization)
|
||||
.Include(pa => pa.Employee)
|
||||
@ -834,7 +905,12 @@ namespace MarcoBMS.Services.Controllers
|
||||
query = query.Where(pa => pa.Employee != null && pa.Employee.OrganizationId == organizationId);
|
||||
}
|
||||
|
||||
List<Attendance> lstAttendance = await _context.Attendes.Where(c => c.ProjectID == projectId && c.AttendanceDate.Date == forDate && c.TenantId == tenantId).ToListAsync();
|
||||
List<Attendance> lstAttendance = await _context.Attendes
|
||||
.Include(a => a.RequestedBy)
|
||||
.ThenInclude(e => e!.JobRole)
|
||||
.Include(a => a.Approver)
|
||||
.ThenInclude(e => e!.JobRole)
|
||||
.Where(c => c.ProjectID == projectId && c.AttendanceDate.Date == forDate && c.TenantId == tenantId).ToListAsync();
|
||||
|
||||
var teamAttendance = await query
|
||||
.AsNoTracking()
|
||||
@ -851,18 +927,21 @@ namespace MarcoBMS.Services.Controllers
|
||||
LastName = teamMember.Employee?.LastName,
|
||||
OrganizationName = teamMember.Employee?.Organization?.Name,
|
||||
JobRoleName = teamMember.Employee?.JobRole?.Name,
|
||||
ProjectId = projectId,
|
||||
ProjectName = teamMember.Project?.Name
|
||||
};
|
||||
|
||||
//var member = emp.Where(e => e.Id == teamMember.EmployeeId);
|
||||
|
||||
|
||||
var attendance = lstAttendance.Find(x => x.EmployeeID == teamMember.EmployeeId) ?? new Attendance();
|
||||
var attendance = lstAttendance.Find(x => x.EmployeeId == teamMember.EmployeeId) ?? new Attendance();
|
||||
if (attendance != null)
|
||||
{
|
||||
result1.Id = attendance.Id;
|
||||
result1.CheckInTime = attendance.InTime;
|
||||
result1.CheckOutTime = attendance.OutTime;
|
||||
result1.Activity = attendance.Activity;
|
||||
result1.ApprovedAt = attendance.ApprovedAt;
|
||||
result1.Approver = _mapper.Map<BasicEmployeeVM>(attendance.Approver);
|
||||
result1.RequestedAt = attendance.RequestedAt;
|
||||
result1.RequestedBy = _mapper.Map<BasicEmployeeVM>(attendance.RequestedBy);
|
||||
}
|
||||
return result1;
|
||||
})
|
||||
@ -881,12 +960,21 @@ namespace MarcoBMS.Services.Controllers
|
||||
|
||||
// This query fetches the employee's project allocation and their attendance in a single trip.
|
||||
Attendance lstAttendance = await _context.Attendes
|
||||
.FirstOrDefaultAsync(c => c.ProjectID == projectId && c.EmployeeID == employeeId && c.AttendanceDate.Date == forDate && c.TenantId == tenantId) ?? new Attendance();
|
||||
.Include(a => a.RequestedBy)
|
||||
.ThenInclude(e => e!.JobRole)
|
||||
.Include(a => a.Approver)
|
||||
.ThenInclude(e => e!.JobRole)
|
||||
.FirstOrDefaultAsync(c => c.ProjectID == projectId && c.EmployeeId == employeeId && c.AttendanceDate.Date == forDate && c.TenantId == tenantId) ?? new Attendance();
|
||||
|
||||
var projectAllocationQuery = _context.ProjectAllocations
|
||||
.Include(pa => pa.Project)
|
||||
.Include(pa => pa.Employee)
|
||||
.ThenInclude(e => e!.Organization)
|
||||
.Where(pa => pa.ProjectId == projectId && pa.EmployeeId == employeeId && pa.TenantId == tenantId && pa.IsActive);
|
||||
.Include(pa => pa.Employee)
|
||||
.ThenInclude(e => e!.JobRole)
|
||||
.Where(pa => pa.EmployeeId == employeeId && pa.TenantId == tenantId && pa.IsActive &&
|
||||
pa.ProjectId == projectId && pa.Project != null &&
|
||||
pa.Employee != null && pa.Employee.Organization != null && pa.Employee.JobRole != null);
|
||||
|
||||
if (organizationId.HasValue)
|
||||
{
|
||||
@ -906,9 +994,15 @@ namespace MarcoBMS.Services.Controllers
|
||||
OrganizationName = projectAllocation.Employee?.Organization?.Name,
|
||||
LastName = projectAllocation.Employee?.LastName,
|
||||
JobRoleName = projectAllocation.Employee?.JobRole?.Name,
|
||||
ProjectId = projectId,
|
||||
ProjectName = projectAllocation.Project?.Name,
|
||||
CheckInTime = lstAttendance.InTime,
|
||||
CheckOutTime = lstAttendance.OutTime,
|
||||
Activity = lstAttendance.Activity
|
||||
Activity = lstAttendance.Activity,
|
||||
ApprovedAt = lstAttendance.ApprovedAt,
|
||||
Approver = _mapper.Map<BasicEmployeeVM>(lstAttendance.Approver),
|
||||
RequestedAt = lstAttendance.RequestedAt,
|
||||
RequestedBy = _mapper.Map<BasicEmployeeVM>(lstAttendance.RequestedBy)
|
||||
};
|
||||
result.Add(result1);
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ using Marco.Pms.Model.Employees;
|
||||
using Marco.Pms.Model.Entitlements;
|
||||
using Marco.Pms.Model.Utilities;
|
||||
using Marco.Pms.Model.ViewModels.Tenant;
|
||||
using Marco.Pms.Services.Helpers;
|
||||
using MarcoBMS.Services.Helpers;
|
||||
using MarcoBMS.Services.Service;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
@ -319,36 +320,6 @@ namespace MarcoBMS.Services.Controllers
|
||||
return Unauthorized(ApiResponse<object>.ErrorResponse("MPIN mismatch", "MPIN did not match", 401));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(verifyMPIN.FcmToken))
|
||||
{
|
||||
var existingFCMTokenMapping = await _context.FCMTokenMappings.Where(ft => ft.FcmToken == verifyMPIN.FcmToken).ToListAsync();
|
||||
|
||||
if (existingFCMTokenMapping.Any())
|
||||
{
|
||||
_context.FCMTokenMappings.RemoveRange(existingFCMTokenMapping);
|
||||
}
|
||||
|
||||
var fcmTokenMapping = new FCMTokenMapping
|
||||
{
|
||||
EmployeeId = requestEmployee.Id,
|
||||
FcmToken = verifyMPIN.FcmToken,
|
||||
ExpiredAt = DateTime.UtcNow.AddDays(6),
|
||||
TenantId = tenantId
|
||||
};
|
||||
_context.FCMTokenMappings.Add(fcmTokenMapping);
|
||||
_logger.LogInfo("New FCM Token registering for employee {EmployeeId}", requestEmployee.Id);
|
||||
try
|
||||
{
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Exception occured while saving FCM Token for employee {EmployeeId}", requestEmployee.Id);
|
||||
return StatusCode(500, ApiResponse<object>.ErrorResponse("Internal Error", ex.Message, 500));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Generate new tokens
|
||||
var jwtToken = _refreshTokenService.GenerateJwtToken(requestEmployee.Email, tenantId, requestEmployee.OrganizationId, _jwtSettings);
|
||||
var refreshToken = await _refreshTokenService.CreateRefreshToken(requestEmployee.ApplicationUserId, tenantId.ToString(), requestEmployee.OrganizationId, _jwtSettings);
|
||||
@ -368,6 +339,100 @@ namespace MarcoBMS.Services.Controllers
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("login-otp/v1")]
|
||||
public async Task<IActionResult> LoginWithOTP([FromBody] VerifyOTPDto verifyOTP)
|
||||
{
|
||||
await using var _context = await _dbContextFactory.CreateDbContextAsync();
|
||||
using var scope = _serviceScopeFactory.CreateScope();
|
||||
var _refreshTokenService = scope.ServiceProvider.GetRequiredService<RefreshTokenService>();
|
||||
|
||||
try
|
||||
{
|
||||
// Validate input
|
||||
if (string.IsNullOrWhiteSpace(verifyOTP.Email) ||
|
||||
string.IsNullOrWhiteSpace(verifyOTP.OTP) ||
|
||||
verifyOTP.OTP.Length != 4 ||
|
||||
!verifyOTP.OTP.All(char.IsDigit))
|
||||
{
|
||||
_logger.LogWarning("OTP login failed - invalid input provided");
|
||||
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid input", "Please provide a valid 4-digit OTP and Email", 400));
|
||||
}
|
||||
|
||||
// Fetch employee by email
|
||||
var requestEmployee = await _context.Employees
|
||||
.Include(e => e.ApplicationUser)
|
||||
.FirstOrDefaultAsync(e => e.Email == verifyOTP.Email && e.IsActive);
|
||||
|
||||
if (requestEmployee == null || string.IsNullOrWhiteSpace(requestEmployee.ApplicationUserId))
|
||||
{
|
||||
_logger.LogWarning("OTP login failed - user not found for email {Email}", verifyOTP.Email);
|
||||
return NotFound(ApiResponse<object>.ErrorResponse("User not found", "User not found", 404));
|
||||
}
|
||||
|
||||
Guid userId = Guid.Parse(requestEmployee.ApplicationUserId);
|
||||
|
||||
// Fetch most recent OTP
|
||||
var otpDetails = await _context.OTPDetails
|
||||
.Where(o => o.UserId == userId)
|
||||
.OrderByDescending(o => o.TimeStamp)
|
||||
.FirstOrDefaultAsync();
|
||||
|
||||
if (otpDetails == null)
|
||||
{
|
||||
_logger.LogWarning("OTP login failed - no OTP found for user {UserId}", userId);
|
||||
return NotFound(ApiResponse<object>.ErrorResponse("OTP not found", "No OTP was generated for this user", 404));
|
||||
}
|
||||
|
||||
// Validate OTP expiration
|
||||
var validUntil = otpDetails.TimeStamp.AddSeconds(otpDetails.ExpriesInSec);
|
||||
if (DateTime.UtcNow > validUntil || otpDetails.IsUsed)
|
||||
{
|
||||
_logger.LogWarning("OTP login failed - OTP expired for user {UserId}", userId);
|
||||
return BadRequest(ApiResponse<object>.ErrorResponse("OTP expired", "The OTP has expired, please request a new one", 400));
|
||||
}
|
||||
|
||||
// Match OTP
|
||||
if (otpDetails.OTP != verifyOTP.OTP)
|
||||
{
|
||||
_logger.LogWarning("OTP login failed - incorrect OTP entered for user {UserId}", userId);
|
||||
return Unauthorized(ApiResponse<object>.ErrorResponse("Invalid OTP", "OTP did not match", 401));
|
||||
}
|
||||
|
||||
// Generate access and refresh tokens
|
||||
//var accessToken = _refreshTokenService.GenerateJwtTokenWithOrganization(requestEmployee.ApplicationUser?.UserName, requestEmployee.OrganizationId, _jwtSettings);
|
||||
//var refreshToken = await _refreshTokenService.CreateRefreshTokenWithOrganization(requestEmployee.ApplicationUserId, requestEmployee.OrganizationId, _jwtSettings);
|
||||
|
||||
var tenant = await _context.Tenants.FirstOrDefaultAsync(t => t.OrganizationId == requestEmployee.OrganizationId);
|
||||
|
||||
var accessToken = _refreshTokenService.GenerateJwtToken(requestEmployee.ApplicationUser?.UserName,
|
||||
tenant?.Id ?? Guid.Empty, requestEmployee.OrganizationId, _jwtSettings);
|
||||
|
||||
var refreshToken = await _refreshTokenService.CreateRefreshToken(requestEmployee.ApplicationUserId,
|
||||
tenant?.Id.ToString(), requestEmployee.OrganizationId, _jwtSettings);
|
||||
|
||||
// Fetch MPIN token if exists
|
||||
var mpinDetails = await _context.MPINDetails
|
||||
.FirstOrDefaultAsync(p => p.UserId == userId);
|
||||
|
||||
// Build and return response
|
||||
var response = new
|
||||
{
|
||||
token = accessToken,
|
||||
refreshToken,
|
||||
mpinToken = mpinDetails?.MPINToken
|
||||
};
|
||||
otpDetails.IsUsed = true;
|
||||
await _context.SaveChangesAsync();
|
||||
_logger.LogInfo("OTP login successful for employee {EmployeeId}", requestEmployee.Id);
|
||||
return Ok(ApiResponse<object>.SuccessResponse(response, "User logged in successfully.", 200));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "An unexpected error occurred during OTP login for email {Email}", verifyOTP.Email ?? string.Empty);
|
||||
return StatusCode(500, ApiResponse<object>.ErrorResponse("Unexpected error", ex.Message, 500));
|
||||
}
|
||||
}
|
||||
|
||||
// new login APIs
|
||||
|
||||
[HttpPost("login")]
|
||||
@ -944,7 +1009,7 @@ namespace MarcoBMS.Services.Controllers
|
||||
}
|
||||
|
||||
[HttpPost("login-otp")]
|
||||
public async Task<IActionResult> LoginWithOTP([FromBody] VerifyOTPDto verifyOTP)
|
||||
public async Task<IActionResult> LoginWithOTPAsync([FromBody] VerifyOTPDto verifyOTP)
|
||||
{
|
||||
await using var _context = await _dbContextFactory.CreateDbContextAsync();
|
||||
using var scope = _serviceScopeFactory.CreateScope();
|
||||
@ -1405,6 +1470,9 @@ namespace MarcoBMS.Services.Controllers
|
||||
// Generate and store refresh token
|
||||
var refreshToken = await _refreshTokenService.CreateRefreshToken(loggedInEmployee.ApplicationUserId, tenantId.ToString(), loggedInEmployee.OrganizationId, _jwtSettings);
|
||||
|
||||
var _cache = scope.ServiceProvider.GetRequiredService<CacheUpdateHelper>();
|
||||
await _cache.ClearAllEmployeesFromCacheByOnlyEmployeeId(loggedInEmployee.Id);
|
||||
|
||||
_logger.LogInfo("Tenant selected and tokens generated for TenantId: {TenantId} and Employee: {EmployeeEmail}", tenantId, loggedInEmployee.Email ?? string.Empty);
|
||||
|
||||
// Return success response including tokens
|
||||
|
@ -235,7 +235,7 @@ namespace Marco.Pms.Services.Controllers
|
||||
int inTodays = await _context.Attendes
|
||||
.Where(a => a.InTime >= today && a.InTime < tomorrow &&
|
||||
finalProjectIds.Contains(a.ProjectID))
|
||||
.Select(a => a.EmployeeID)
|
||||
.Select(a => a.EmployeeId)
|
||||
.Distinct()
|
||||
.CountAsync();
|
||||
|
||||
@ -354,7 +354,7 @@ namespace Marco.Pms.Services.Controllers
|
||||
Guid tenantId = _userHelper.GetTenantId();
|
||||
var LoggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
|
||||
|
||||
var attendance = await _context.Attendes.Where(a => a.EmployeeID == LoggedInEmployee.Id && a.TenantId == tenantId).ToListAsync();
|
||||
var attendance = await _context.Attendes.Where(a => a.EmployeeId == LoggedInEmployee.Id && a.TenantId == tenantId).ToListAsync();
|
||||
if (attendance.Any())
|
||||
{
|
||||
var pendingRegularization = attendance.Where(a => a.Activity == ATTENDANCE_MARK_TYPE.REQUEST_REGULARIZE).ToList().Count;
|
||||
@ -395,12 +395,12 @@ namespace Marco.Pms.Services.Controllers
|
||||
var employeeIds = projectAllocation.Select(p => p.EmployeeId).Distinct().ToList();
|
||||
List<Employee>? employees = await _context.Employees.Where(e => employeeIds.Contains(e.Id)).ToListAsync();
|
||||
|
||||
var attendances = await _context.Attendes.Where(a => employeeIds.Contains(a.EmployeeID) && a.ProjectID == projectId && a.InTime.HasValue && a.InTime.Value.Date == currentDate.Date).ToListAsync();
|
||||
var attendances = await _context.Attendes.Where(a => employeeIds.Contains(a.EmployeeId) && a.ProjectID == projectId && a.InTime.HasValue && a.InTime.Value.Date == currentDate.Date).ToListAsync();
|
||||
List<EmployeeAttendanceVM> employeeAttendanceVMs = new List<EmployeeAttendanceVM>();
|
||||
foreach (var attendance in attendances)
|
||||
{
|
||||
|
||||
Employee? employee = employees.FirstOrDefault(e => e.Id == attendance.EmployeeID);
|
||||
Employee? employee = employees.FirstOrDefault(e => e.Id == attendance.EmployeeId);
|
||||
if (employee != null)
|
||||
{
|
||||
EmployeeAttendanceVM employeeAttendanceVM = new EmployeeAttendanceVM
|
||||
@ -579,7 +579,7 @@ namespace Marco.Pms.Services.Controllers
|
||||
.ToList();
|
||||
|
||||
int presentCount = attendances
|
||||
.Count(a => employeeIds.Contains(a.EmployeeID) && a.InTime!.Value.Date == date);
|
||||
.Count(a => employeeIds.Contains(a.EmployeeId) && a.InTime!.Value.Date == date);
|
||||
|
||||
overviewList.Add(new AttendanceOverviewVM
|
||||
{
|
||||
|
@ -33,11 +33,11 @@ namespace Marco.Pms.Services.Controllers
|
||||
#region =================================================================== Contact Get APIs ===================================================================
|
||||
|
||||
[HttpGet("list")]
|
||||
public async Task<IActionResult> GetContactList([FromQuery] string? search, [FromQuery] string? filter, [FromQuery] Guid? projectId, [FromQuery] bool active = true,
|
||||
public async Task<IActionResult> GetContactList([FromQuery] string? searchString, [FromQuery] string? filter, [FromQuery] Guid? projectId, [FromQuery] bool active = true,
|
||||
[FromQuery] int pageNumber = 1, [FromQuery] int pageSize = 20)
|
||||
{
|
||||
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
|
||||
var response = await _directoryService.GetListOfContactsAsync(search: search, filter: filter, projectId: projectId, active: active, pageSize: pageSize, pageNumber: pageNumber, tenantId, loggedInEmployee);
|
||||
var response = await _directoryService.GetListOfContactsAsync(search: searchString, filter: filter, projectId: projectId, active: active, pageSize: pageSize, pageNumber: pageNumber, tenantId, loggedInEmployee);
|
||||
|
||||
return StatusCode(response.StatusCode, response);
|
||||
|
||||
|
@ -329,10 +329,10 @@ namespace MarcoBMS.Services.Controllers
|
||||
|
||||
|
||||
[HttpGet("basic")]
|
||||
public async Task<IActionResult> GetEmployeesByProjectBasic(Guid? projectId, [FromQuery] string? searchString)
|
||||
public async Task<IActionResult> GetEmployeesByProjectBasic(Guid? projectId, [FromQuery] string? searchString, [FromQuery] bool allEmployee)
|
||||
{
|
||||
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
|
||||
var employeeQuery = _context.Employees.Where(e => e.TenantId == tenantId);
|
||||
var employeeQuery = _context.Employees.Where(e => e.IsActive);
|
||||
if (projectId != null && projectId != Guid.Empty)
|
||||
{
|
||||
var hasProjectPermission = await _permission.HasProjectPermission(loggedInEmployee, projectId.Value);
|
||||
@ -344,13 +344,26 @@ namespace MarcoBMS.Services.Controllers
|
||||
var employeeIds = await _context.ProjectAllocations.Where(pa => pa.ProjectId == projectId && pa.IsActive && pa.TenantId == tenantId).Select(p => p.EmployeeId).ToListAsync();
|
||||
employeeQuery = employeeQuery.Where(e => employeeIds.Contains(e.Id));
|
||||
}
|
||||
else
|
||||
{
|
||||
employeeQuery = employeeQuery.Where(e => e.OrganizationId == organizationId);
|
||||
}
|
||||
if (!string.IsNullOrWhiteSpace(searchString))
|
||||
{
|
||||
var searchStringLower = searchString.ToLower();
|
||||
employeeQuery = employeeQuery.Where(e => (e.FirstName + " " + e.LastName).ToLower().Contains(searchStringLower));
|
||||
}
|
||||
|
||||
var response = await employeeQuery.Take(10).Select(e => _mapper.Map<BasicEmployeeVM>(e)).ToListAsync();
|
||||
var query = employeeQuery.OrderBy(e => e.FirstName);
|
||||
|
||||
if (!allEmployee)
|
||||
{
|
||||
query = (IOrderedQueryable<Employee>)query.Take(10);
|
||||
}
|
||||
|
||||
var response = await query
|
||||
.Select(e => _mapper.Map<BasicEmployeeVM>(e))
|
||||
.ToListAsync();
|
||||
return Ok(ApiResponse<object>.SuccessResponse(response, $"{response.Count} records of employees fetched successfully", 200));
|
||||
}
|
||||
|
||||
@ -621,10 +634,10 @@ namespace MarcoBMS.Services.Controllers
|
||||
if (model.Id.HasValue && model.Id.Value != Guid.Empty)
|
||||
{
|
||||
existingEmployee = await _context.Employees
|
||||
.FirstOrDefaultAsync(e => e.Id == model.Id && e.OrganizationId == model.OrganizationId);
|
||||
.FirstOrDefaultAsync(e => e.Id == model.Id);
|
||||
if (existingEmployee == null)
|
||||
{
|
||||
_logger.LogInfo("Employee not found for update. Id={EmployeeId}, Org={OrgId}", model.Id, model.OrganizationId);
|
||||
_logger.LogInfo("Employee not found for update. Id={EmployeeId}", model.Id);
|
||||
return NotFound(ApiResponse<object>.ErrorResponse("Employee not found", "Employee not found in database", 404));
|
||||
}
|
||||
}
|
||||
@ -676,10 +689,10 @@ namespace MarcoBMS.Services.Controllers
|
||||
if (!string.IsNullOrWhiteSpace(model.Email))
|
||||
{
|
||||
var emailExists = await _context.Employees
|
||||
.AnyAsync(e => e.Email == model.Email && e.OrganizationId == model.OrganizationId);
|
||||
.AnyAsync(e => e.Email == model.Email);
|
||||
if (emailExists)
|
||||
{
|
||||
_logger.LogInfo("Employee email already exists in org. Email={Email}, Org={OrgId}", model.Email, model.OrganizationId);
|
||||
_logger.LogInfo("Employee email already exists. Email={Email}", model.Email);
|
||||
return StatusCode(403, ApiResponse<object>.ErrorResponse(
|
||||
"Employee with email already exists",
|
||||
"Employee with this email already exists", 403));
|
||||
@ -704,14 +717,6 @@ namespace MarcoBMS.Services.Controllers
|
||||
createdIdentityUser.Id, createdIdentityUser.Email);
|
||||
}
|
||||
|
||||
// Prepare reset link sender helper
|
||||
async Task SendResetIfApplicableAsync(ApplicationUser u, string firstName)
|
||||
{
|
||||
var token = await _userManager.GeneratePasswordResetTokenAsync(u);
|
||||
var resetLink = $"{_configuration["AppSettings:WebFrontendUrl"]}/reset-password?token={WebUtility.UrlEncode(token)}";
|
||||
await _emailSender.SendResetPasswordEmailOnRegister(u.Email ?? "", firstName, resetLink);
|
||||
_logger.LogInfo("Reset password email queued. Email={Email}", u.Email ?? "");
|
||||
}
|
||||
|
||||
Guid employeeId;
|
||||
EmployeeVM employeeVM;
|
||||
@ -915,6 +920,15 @@ namespace MarcoBMS.Services.Controllers
|
||||
}
|
||||
if (model.Id == null || model.Id == Guid.Empty)
|
||||
{
|
||||
var emailExists = await _context.Employees
|
||||
.AnyAsync(e => e.Email == model.Email && e.OrganizationId == model.OrganizationId);
|
||||
|
||||
if (emailExists && !string.IsNullOrWhiteSpace(model.Email))
|
||||
{
|
||||
_logger.LogInfo("Employee email already exists in org. Email={Email}, Org={OrgId}", model.Email, model.OrganizationId);
|
||||
return StatusCode(409, ApiResponse<object>.ErrorResponse("Employee with email already exists", "Employee with this email already exists", 409));
|
||||
}
|
||||
|
||||
// Create path: map only allowed fields
|
||||
var employee = new Employee
|
||||
{
|
||||
@ -922,15 +936,42 @@ namespace MarcoBMS.Services.Controllers
|
||||
TenantId = tenantId,
|
||||
FirstName = model.FirstName.Trim(),
|
||||
LastName = model.LastName?.Trim(),
|
||||
Email = model.Email,
|
||||
Gender = model.Gender,
|
||||
PhoneNumber = model.PhoneNumber,
|
||||
JoiningDate = model.JoiningDate,
|
||||
JobRoleId = model.JobRoleId,
|
||||
Photo = imageBytes,
|
||||
OrganizationId = model.OrganizationId
|
||||
OrganizationId = model.OrganizationId,
|
||||
HasApplicationAccess = model.HasApplicationAccess,
|
||||
};
|
||||
|
||||
await _context.Employees.AddAsync(employee);
|
||||
if (!string.IsNullOrWhiteSpace(model.Email) && model.HasApplicationAccess)
|
||||
{
|
||||
var existingUser = await _userManager.FindByEmailAsync(model.Email);
|
||||
if (existingUser == null)
|
||||
{
|
||||
existingUser = new ApplicationUser
|
||||
{
|
||||
UserName = model.Email,
|
||||
Email = model.Email,
|
||||
EmailConfirmed = true
|
||||
};
|
||||
var createResult = await _userManager.CreateAsync(existingUser, "User@123");
|
||||
if (!createResult.Succeeded)
|
||||
{
|
||||
_logger.LogWarning("Failed to create identity user for {Email}. Errors={Errors}",
|
||||
existingUser.Email,
|
||||
string.Join(", ", createResult.Errors.Select(e => $"{e.Code}:{e.Description}")));
|
||||
return BadRequest(ApiResponse<object>.ErrorResponse("Failed to create user", createResult.Errors, 400));
|
||||
}
|
||||
|
||||
await SendResetIfApplicableAsync(existingUser, employee.FirstName ?? "User");
|
||||
employee.ApplicationUserId = existingUser.Id;
|
||||
}
|
||||
}
|
||||
|
||||
_context.Employees.Add(employee);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
var employeeVM = _mapper.Map<EmployeeVM>(employee);
|
||||
@ -954,7 +995,7 @@ namespace MarcoBMS.Services.Controllers
|
||||
// Update path: fetch scoped to tenant
|
||||
var employeeId = model.Id.Value;
|
||||
var existingEmployee = await _context.Employees
|
||||
.FirstOrDefaultAsync(e => e.Id == employeeId && e.TenantId == tenantId); // tenant-safe lookup
|
||||
.FirstOrDefaultAsync(e => e.Id == employeeId); // tenant-safe lookup
|
||||
|
||||
if (existingEmployee is null)
|
||||
{
|
||||
@ -970,6 +1011,45 @@ namespace MarcoBMS.Services.Controllers
|
||||
existingEmployee.JoiningDate = model.JoiningDate;
|
||||
existingEmployee.JobRoleId = model.JobRoleId;
|
||||
existingEmployee.OrganizationId = model.OrganizationId;
|
||||
existingEmployee.HasApplicationAccess = model.HasApplicationAccess;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(existingEmployee.Email) && !string.IsNullOrWhiteSpace(model.Email))
|
||||
{
|
||||
var emailExists = await _context.Employees
|
||||
.AnyAsync(e => e.Email == model.Email);
|
||||
|
||||
if (emailExists)
|
||||
{
|
||||
_logger.LogInfo("Employee email already exists in org. Email={Email}, Org={OrgId}", model.Email, model.OrganizationId);
|
||||
return StatusCode(409, ApiResponse<object>.ErrorResponse("Employee with email already exists", "Employee with this email already exists", 409));
|
||||
}
|
||||
existingEmployee.Email = model.Email;
|
||||
}
|
||||
|
||||
if (model.HasApplicationAccess && !string.IsNullOrWhiteSpace(model.Email) && string.IsNullOrWhiteSpace(existingEmployee.ApplicationUserId))
|
||||
{
|
||||
var existingUser = await _userManager.FindByEmailAsync(model.Email);
|
||||
if (existingUser == null)
|
||||
{
|
||||
existingUser = new ApplicationUser
|
||||
{
|
||||
UserName = model.Email,
|
||||
Email = model.Email,
|
||||
EmailConfirmed = true
|
||||
};
|
||||
var createResult = await _userManager.CreateAsync(existingUser, "User@123");
|
||||
if (!createResult.Succeeded)
|
||||
{
|
||||
_logger.LogWarning("Failed to create identity user for {Email}. Errors={Errors}",
|
||||
existingUser.Email,
|
||||
string.Join(", ", createResult.Errors.Select(e => $"{e.Code}:{e.Description}")));
|
||||
return BadRequest(ApiResponse<object>.ErrorResponse("Failed to create user", createResult.Errors, 400));
|
||||
}
|
||||
|
||||
await SendResetIfApplicableAsync(existingUser, existingEmployee.FirstName ?? "User");
|
||||
existingEmployee.ApplicationUserId = existingUser.Id;
|
||||
}
|
||||
}
|
||||
|
||||
if (imageBytes != null)
|
||||
{
|
||||
@ -1031,7 +1111,7 @@ namespace MarcoBMS.Services.Controllers
|
||||
}
|
||||
}
|
||||
}
|
||||
var attendance = await _context.Attendes.Where(a => a.EmployeeID == employee.Id && (a.OutTime == null || a.Activity == ATTENDANCE_MARK_TYPE.REQUEST_REGULARIZE)).ToListAsync();
|
||||
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);
|
||||
@ -1173,5 +1253,14 @@ namespace MarcoBMS.Services.Controllers
|
||||
return info;
|
||||
|
||||
}
|
||||
|
||||
// Prepare reset link sender helper
|
||||
private async Task SendResetIfApplicableAsync(ApplicationUser u, string firstName)
|
||||
{
|
||||
var token = await _userManager.GeneratePasswordResetTokenAsync(u);
|
||||
var resetLink = $"{_configuration["AppSettings:WebFrontendUrl"]}/reset-password?token={WebUtility.UrlEncode(token)}";
|
||||
await _emailSender.SendResetPasswordEmailOnRegister(u.Email ?? "", firstName, resetLink);
|
||||
_logger.LogInfo("Reset password email queued. Email={Email}", u.Email ?? "");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -106,7 +106,7 @@ namespace Marco.Pms.Services.Controllers
|
||||
t.WorkItem.WorkArea != null &&
|
||||
t.WorkItem.WorkArea.Floor != null &&
|
||||
t.WorkItem.WorkArea.Floor.Building != null &&
|
||||
t.WorkItem.WorkArea.Floor.Building.ProjectId != projectId &&
|
||||
t.WorkItem.WorkArea.Floor.Building.ProjectId == projectId &&
|
||||
t.TenantId == tenantId);
|
||||
|
||||
// Step 4: Extract filter values
|
||||
|
@ -9,6 +9,7 @@ using Marco.Pms.Model.ViewModels.Activities;
|
||||
using Marco.Pms.Model.ViewModels.Master;
|
||||
using Marco.Pms.Model.ViewModels.Organization;
|
||||
using Marco.Pms.Model.ViewModels.Projects;
|
||||
using Marco.Pms.Services.Helpers;
|
||||
using Marco.Pms.Services.Service;
|
||||
using MarcoBMS.Services.Helpers;
|
||||
using MarcoBMS.Services.Service;
|
||||
@ -30,6 +31,7 @@ namespace Marco.Pms.Services.Controllers
|
||||
private readonly UserHelper _userHelper;
|
||||
private readonly Guid tenantId;
|
||||
private readonly IMapper _mapper;
|
||||
private readonly Guid loggedOrganizationId;
|
||||
private readonly ILoggingService _logger;
|
||||
|
||||
private static readonly Guid PMCProvider = Guid.Parse("b1877a3b-8832-47b1-bbe3-dc7e98672f49");
|
||||
@ -47,6 +49,7 @@ namespace Marco.Pms.Services.Controllers
|
||||
_userHelper = userHelper ?? throw new ArgumentNullException(nameof(userHelper));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
_mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
|
||||
loggedOrganizationId = _userHelper.GetCurrentOrganizationId();
|
||||
tenantId = userHelper.GetTenantId();
|
||||
}
|
||||
#region =================================================================== Get Functions ===================================================================
|
||||
@ -74,10 +77,36 @@ namespace Marco.Pms.Services.Controllers
|
||||
else
|
||||
{
|
||||
// Get organization IDs mapped to current tenant that are active
|
||||
var organizationIds = await _context.TenantOrgMappings
|
||||
var organizationIdsTask = Task.Run(async () =>
|
||||
{
|
||||
await using var context = await _dbContextFactory.CreateDbContextAsync();
|
||||
return await context.TenantOrgMappings
|
||||
.Where(to => to.TenantId == tenantId && to.IsActive)
|
||||
.Select(to => to.OrganizationId)
|
||||
.ToListAsync();
|
||||
});
|
||||
|
||||
var tenantTask = Task.Run(async () =>
|
||||
{
|
||||
await using var context = await _dbContextFactory.CreateDbContextAsync();
|
||||
return await context.Tenants
|
||||
.FirstOrDefaultAsync(t => t.Id == tenantId && t.IsActive);
|
||||
});
|
||||
|
||||
await Task.WhenAll(organizationIdsTask, tenantTask);
|
||||
|
||||
var organizationIds = organizationIdsTask.Result;
|
||||
var tenant = tenantTask.Result;
|
||||
|
||||
if (tenant == null)
|
||||
{
|
||||
_logger.LogWarning("Tenant {TenantId} is not found", tenantId);
|
||||
return NotFound(ApiResponse<object>.ErrorResponse("Tenant not found", "Tenant not found in database", 404));
|
||||
}
|
||||
|
||||
organizationIds.Add(tenant.OrganizationId);
|
||||
|
||||
organizationIds = organizationIds.Distinct().ToList();
|
||||
|
||||
organizationQuery = organizationQuery.Where(o => organizationIds.Contains(o.Id));
|
||||
_logger.LogDebug("Filtering organizations by tenant's mapped IDs count: {Count}", organizationIds.Count);
|
||||
@ -101,6 +130,14 @@ namespace Marco.Pms.Services.Controllers
|
||||
.Take(pageSize)
|
||||
.ToListAsync();
|
||||
|
||||
if (!organizations.Any() && !sprid.HasValue)
|
||||
{
|
||||
organizations = await _context.Tenants.AsNoTracking()
|
||||
.Include(t => t.Organization)
|
||||
.Where(t => t.Id == tenantId && t.OrganizationId == loggedInEmployee.OrganizationId && t.Organization != null && t.IsActive)
|
||||
.Select(t => t.Organization!).ToListAsync();
|
||||
}
|
||||
|
||||
// Collect creator and updater employee IDs
|
||||
var createdByIds = organizations.Where(o => o.CreatedById != null).Select(o => o.CreatedById!.Value).Distinct().ToList();
|
||||
var updatedByIds = organizations.Where(o => o.UpdatedById != null).Select(o => o.UpdatedById!.Value).Distinct().ToList();
|
||||
@ -233,6 +270,7 @@ namespace Marco.Pms.Services.Controllers
|
||||
response.CreatedBy = _mapper.Map<BasicEmployeeVM>(createdByEmployee);
|
||||
response.UpdatedBy = _mapper.Map<BasicEmployeeVM>(updatedByEmployee);
|
||||
response.Projects = _mapper.Map<List<ProjectServiceMappingVM>>(projectOrgMappings.Select(po => po.ProjectService).ToList());
|
||||
response.Services = orgServiceMappings.Where(os => os.Service != null).Select(os => os.Service!).ToList();
|
||||
|
||||
_logger.LogInfo("Fetched organization details for OrganizationId: {OrganizationId}, Employee count: {EmployeeCount}, App user count: {AppUserCount}, Project count: {ProjectCount}",
|
||||
id, activeEmployeeCount, activeApplicationUserCount, response.Projects.Count);
|
||||
@ -634,6 +672,8 @@ namespace Marco.Pms.Services.Controllers
|
||||
Service = _mapper.Map<ServiceMasterVM>(s)
|
||||
}).ToList();
|
||||
|
||||
await AssignApplicationRoleToOrganization(organization.Id, project.TenantId);
|
||||
|
||||
return Ok(ApiResponse<object>.SuccessResponse(response, "Organization successfully assigned to the project", 200));
|
||||
}
|
||||
catch (DbUpdateException dbEx)
|
||||
@ -682,8 +722,14 @@ namespace Marco.Pms.Services.Controllers
|
||||
return NotFound(ApiResponse<object>.ErrorResponse("Organization not found", "Organization not found in database", 404));
|
||||
}
|
||||
|
||||
if (organizationTenantMapping == null)
|
||||
if (organizationTenantMapping != null)
|
||||
{
|
||||
_logger.LogInfo("Organization {OrganizationId} is already assigned to tenant {TenantId}. No action taken.", organizationId, tenantId);
|
||||
// Commit transaction anyway to complete scope cleanly (optional)
|
||||
await transaction.RollbackAsync();
|
||||
return StatusCode(409, ApiResponse<object>.ErrorResponse("Organization is already assigned to tenant", "Organization is already assigned to tenant", 409));
|
||||
}
|
||||
|
||||
// Create new tenant-organization mapping if none exists
|
||||
var newMapping = new TenantOrgMapping
|
||||
{
|
||||
@ -699,17 +745,13 @@ namespace Marco.Pms.Services.Controllers
|
||||
await transaction.CommitAsync();
|
||||
|
||||
_logger.LogInfo("Assigned organization {OrganizationId} to tenant {TenantId} successfully.", organizationId, tenantId);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogInfo("Organization {OrganizationId} is already assigned to tenant {TenantId}. No action taken.", organizationId, tenantId);
|
||||
// Commit transaction anyway to complete scope cleanly (optional)
|
||||
await transaction.CommitAsync();
|
||||
}
|
||||
|
||||
|
||||
// Prepare response view model
|
||||
var response = _mapper.Map<BasicOrganizationVm>(organization);
|
||||
|
||||
await AssignApplicationRoleToOrganization(organization.Id, tenantId);
|
||||
|
||||
return Ok(ApiResponse<object>.SuccessResponse(response, "Organization has been assigned to tenant", 200));
|
||||
}
|
||||
catch (DbUpdateException dbEx)
|
||||
@ -740,7 +782,7 @@ namespace Marco.Pms.Services.Controllers
|
||||
// Get the current logged-in employee
|
||||
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
|
||||
|
||||
_logger.LogDebug("Started updating service provider OrganizationId: {OrganizationId} by EmployeeId: {EmployeeId}",
|
||||
_logger.LogDebug("Started updating organization OrganizationId: {OrganizationId} by EmployeeId: {EmployeeId}",
|
||||
id, loggedInEmployee.Id);
|
||||
|
||||
// Check if the user is a tenant-level employee and restrict editing to their own org
|
||||
@ -823,10 +865,14 @@ namespace Marco.Pms.Services.Controllers
|
||||
|
||||
var response = _mapper.Map<OrganizationVM>(organization);
|
||||
|
||||
_logger.LogInfo("Successfully updated service provider OrganizationId: {OrganizationId} by EmployeeId: {EmployeeId}",
|
||||
var createdByEmployee = await _context.Employees.Include(e => e.JobRole).FirstOrDefaultAsync(e => e.Id == organization.CreatedById);
|
||||
response.CreatedBy = _mapper.Map<BasicEmployeeVM>(createdByEmployee);
|
||||
response.UpdatedBy = _mapper.Map<BasicEmployeeVM>(loggedInEmployee);
|
||||
|
||||
_logger.LogInfo("Successfully updated organization OrganizationId: {OrganizationId} by EmployeeId: {EmployeeId}",
|
||||
id, loggedInEmployee.Id);
|
||||
|
||||
return Ok(ApiResponse<object>.SuccessResponse(response, "Successfully updated the service provider", 200));
|
||||
return Ok(ApiResponse<object>.SuccessResponse(response, "Organization updated Successfully", 200));
|
||||
}
|
||||
catch (DbUpdateException dbEx)
|
||||
{
|
||||
@ -899,45 +945,98 @@ namespace Marco.Pms.Services.Controllers
|
||||
#endregion
|
||||
|
||||
#region =================================================================== Helper Functions ===================================================================
|
||||
//private ServicesProviderFilter? TryDeserializeServicesProviderFilter(string? filter)
|
||||
//{
|
||||
// if (string.IsNullOrWhiteSpace(filter))
|
||||
// {
|
||||
// return null;
|
||||
// }
|
||||
|
||||
// var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
|
||||
// ServicesProviderFilter? documentFilter = null;
|
||||
private async Task AssignApplicationRoleToOrganization(Guid organizationId, Guid tenantId)
|
||||
{
|
||||
if (loggedOrganizationId == organizationId)
|
||||
{
|
||||
return;
|
||||
}
|
||||
await using var _context = await _dbContextFactory.CreateDbContextAsync();
|
||||
using var scope = _serviceScope.CreateScope();
|
||||
|
||||
// try
|
||||
// {
|
||||
// // First, try to deserialize directly. This is the expected case (e.g., from a web client).
|
||||
// documentFilter = JsonSerializer.Deserialize<ServicesProviderFilter>(filter, options);
|
||||
// }
|
||||
// catch (JsonException ex)
|
||||
// {
|
||||
// _logger.LogError(ex, "[{MethodName}] Failed to directly deserialize filter. Attempting to unescape and re-parse. Filter: {Filter}", nameof(TryDeserializeServicesProviderFilter), filter);
|
||||
var rootEmployee = await _context.Employees
|
||||
.Include(e => e.ApplicationUser)
|
||||
.FirstOrDefaultAsync(e => e.ApplicationUser != null && e.ApplicationUser.IsRootUser.HasValue && e.ApplicationUser.IsRootUser.Value && e.OrganizationId == organizationId && e.IsPrimary);
|
||||
if (rootEmployee == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
string serviceProviderRoleName = "Service Provider Role";
|
||||
|
||||
// // If direct deserialization fails, it might be an escaped string (common with tools like Postman or some mobile clients).
|
||||
// try
|
||||
// {
|
||||
// // Unescape the string first, then deserialize the result.
|
||||
// string unescapedJsonString = JsonSerializer.Deserialize<string>(filter, options) ?? "";
|
||||
// if (!string.IsNullOrWhiteSpace(unescapedJsonString))
|
||||
// {
|
||||
// documentFilter = JsonSerializer.Deserialize<ServicesProviderFilter>(unescapedJsonString, options);
|
||||
// }
|
||||
// }
|
||||
// catch (JsonException ex1)
|
||||
// {
|
||||
// // If both attempts fail, log the final error and return null.
|
||||
// _logger.LogError(ex1, "[{MethodName}] All attempts to deserialize the filter failed. Filter will be ignored. Filter: {Filter}", nameof(TryDeserializeServicesProviderFilter), filter);
|
||||
// return null;
|
||||
// }
|
||||
// }
|
||||
// return documentFilter;
|
||||
//}
|
||||
var serviceProviderRole = await _context.ApplicationRoles.FirstOrDefaultAsync(ar => ar.Role == serviceProviderRoleName && ar.TenantId == tenantId);
|
||||
if (serviceProviderRole == null)
|
||||
{
|
||||
serviceProviderRole = new Model.Roles.ApplicationRole
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Role = serviceProviderRoleName,
|
||||
Description = serviceProviderRoleName,
|
||||
IsSystem = true,
|
||||
TenantId = tenantId
|
||||
};
|
||||
_context.ApplicationRoles.Add(serviceProviderRole);
|
||||
|
||||
var rolePermissionMappigs = new List<RolePermissionMappings> {
|
||||
new RolePermissionMappings
|
||||
{
|
||||
ApplicationRoleId = serviceProviderRole.Id,
|
||||
FeaturePermissionId = PermissionsMaster.ViewProject
|
||||
},
|
||||
new RolePermissionMappings
|
||||
{
|
||||
ApplicationRoleId = serviceProviderRole.Id,
|
||||
FeaturePermissionId = PermissionsMaster.ViewProjectInfra
|
||||
},
|
||||
new RolePermissionMappings
|
||||
{
|
||||
ApplicationRoleId = serviceProviderRole.Id,
|
||||
FeaturePermissionId = PermissionsMaster.ViewTask
|
||||
},
|
||||
new RolePermissionMappings
|
||||
{
|
||||
ApplicationRoleId = serviceProviderRole.Id,
|
||||
FeaturePermissionId = PermissionsMaster.ViewAllEmployees
|
||||
},
|
||||
new RolePermissionMappings
|
||||
{
|
||||
ApplicationRoleId = serviceProviderRole.Id,
|
||||
FeaturePermissionId = PermissionsMaster.TeamAttendance
|
||||
},
|
||||
new RolePermissionMappings
|
||||
{
|
||||
ApplicationRoleId = serviceProviderRole.Id,
|
||||
FeaturePermissionId = PermissionsMaster.AssignRoles
|
||||
},
|
||||
new RolePermissionMappings
|
||||
{
|
||||
ApplicationRoleId = serviceProviderRole.Id,
|
||||
FeaturePermissionId = PermissionsMaster.ManageProjectInfra
|
||||
},
|
||||
new RolePermissionMappings
|
||||
{
|
||||
ApplicationRoleId = serviceProviderRole.Id,
|
||||
FeaturePermissionId = PermissionsMaster.AssignAndReportProgress
|
||||
},
|
||||
new RolePermissionMappings
|
||||
{
|
||||
ApplicationRoleId = serviceProviderRole.Id,
|
||||
FeaturePermissionId = PermissionsMaster.AddAndEditTask
|
||||
}
|
||||
};
|
||||
_context.RolePermissionMappings.AddRange(rolePermissionMappigs);
|
||||
}
|
||||
_context.EmployeeRoleMappings.Add(new EmployeeRoleMapping
|
||||
{
|
||||
EmployeeId = rootEmployee.Id,
|
||||
RoleId = serviceProviderRole.Id,
|
||||
IsEnabled = true,
|
||||
TenantId = tenantId
|
||||
});
|
||||
|
||||
var _cache = scope.ServiceProvider.GetRequiredService<CacheUpdateHelper>();
|
||||
await _cache.ClearAllPermissionIdsByEmployeeID(rootEmployee.Id, tenantId);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
@ -560,6 +560,13 @@ namespace MarcoBMS.Services.Controllers
|
||||
var response = await _projectServices.GetAssignedOrganizationsToProjectAsync(projectId, tenantId, loggedInEmployee);
|
||||
return StatusCode(response.StatusCode, response);
|
||||
}
|
||||
[HttpGet("get/assigned/organization/dropdown/{projectId}")]
|
||||
public async Task<IActionResult> GetAssignedOrganizationsToProjectForDropdownAsync(Guid projectId)
|
||||
{
|
||||
Employee loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
|
||||
var response = await _projectServices.GetAssignedOrganizationsToProjectForDropdownAsync(projectId, tenantId, loggedInEmployee);
|
||||
return StatusCode(response.StatusCode, response);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
using Marco.Pms.DataAccess.Data;
|
||||
using Marco.Pms.Model.Dtos.Attendance;
|
||||
using Marco.Pms.Model.Dtos.Mail;
|
||||
using Marco.Pms.Model.Mail;
|
||||
using Marco.Pms.Model.MongoDBModels.Utility;
|
||||
@ -10,7 +11,6 @@ using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using MongoDB.Driver;
|
||||
using System.Data;
|
||||
using System.Globalization;
|
||||
using System.Net.Mail;
|
||||
@ -22,27 +22,25 @@ namespace Marco.Pms.Services.Controllers
|
||||
[Authorize]
|
||||
public class ReportController : ControllerBase
|
||||
{
|
||||
private readonly IDbContextFactory<ApplicationDbContext> _dbContextFactory;
|
||||
private readonly IServiceScopeFactory _serviceScopeFactory;
|
||||
private readonly ApplicationDbContext _context;
|
||||
private readonly IEmailSender _emailSender;
|
||||
private readonly ILoggingService _logger;
|
||||
private readonly UserHelper _userHelper;
|
||||
private readonly IWebHostEnvironment _env;
|
||||
private readonly ReportHelper _reportHelper;
|
||||
private readonly IConfiguration _configuration;
|
||||
private readonly CacheUpdateHelper _cache;
|
||||
private readonly IServiceScopeFactory _serviceScopeFactory;
|
||||
public ReportController(ApplicationDbContext context, IEmailSender emailSender, ILoggingService logger, UserHelper userHelper,
|
||||
IWebHostEnvironment env, ReportHelper reportHelper, IConfiguration configuration, CacheUpdateHelper cache, IServiceScopeFactory serviceScopeFactory)
|
||||
public ReportController(IDbContextFactory<ApplicationDbContext> dbContextFactory,
|
||||
ApplicationDbContext context,
|
||||
ILoggingService logger,
|
||||
UserHelper userHelper,
|
||||
IConfiguration configuration,
|
||||
IServiceScopeFactory serviceScopeFactory)
|
||||
{
|
||||
_context = context;
|
||||
_emailSender = emailSender;
|
||||
_logger = logger;
|
||||
_userHelper = userHelper;
|
||||
_env = env;
|
||||
_reportHelper = reportHelper;
|
||||
_configuration = configuration;
|
||||
_cache = cache;
|
||||
_serviceScopeFactory = serviceScopeFactory;
|
||||
_dbContextFactory = dbContextFactory ?? throw new ArgumentNullException(nameof(dbContextFactory));
|
||||
_serviceScopeFactory = serviceScopeFactory ?? throw new ArgumentNullException(nameof(serviceScopeFactory));
|
||||
_context = context ?? throw new ArgumentNullException(nameof(context));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
_userHelper = userHelper ?? throw new ArgumentNullException(nameof(userHelper));
|
||||
_configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -435,10 +433,12 @@ namespace Marco.Pms.Services.Controllers
|
||||
200));
|
||||
}
|
||||
|
||||
|
||||
[HttpGet("report-mail")]
|
||||
public async Task<IActionResult> GetProjectStatisticsFromCache()
|
||||
{
|
||||
using var scope = _serviceScopeFactory.CreateScope();
|
||||
var _cache = scope.ServiceProvider.GetRequiredService<CacheUpdateHelper>();
|
||||
|
||||
var mailList = await _cache.GetProjectReportMail(false);
|
||||
if (mailList == null)
|
||||
{
|
||||
@ -447,5 +447,92 @@ namespace Marco.Pms.Services.Controllers
|
||||
|
||||
return Ok(ApiResponse<object>.SuccessResponse(mailList, "Fetched list of mail body successfully", 200));
|
||||
}
|
||||
|
||||
[HttpGet("report-attendance")]
|
||||
public async Task<IActionResult> GetAttendanceReportAsync([FromQuery] bool isCurrentMonth = false)
|
||||
{
|
||||
Guid tenantId = _userHelper.GetTenantId();
|
||||
using var scope = _serviceScopeFactory.CreateScope();
|
||||
|
||||
DateTime today = DateTime.Today;
|
||||
DateTime firstDayOfMonth = new DateTime(today.Year, today.Month, 1);
|
||||
DateTime firstDayOfNextMonth = firstDayOfMonth.AddMonths(1);
|
||||
|
||||
if (!isCurrentMonth)
|
||||
{
|
||||
firstDayOfNextMonth = firstDayOfMonth;
|
||||
firstDayOfMonth = firstDayOfMonth.AddMonths(-1);
|
||||
}
|
||||
|
||||
// Generate list of all dates in the month
|
||||
var allDates = Enumerable.Range(0, (firstDayOfNextMonth - firstDayOfMonth).Days)
|
||||
.Select(offset => firstDayOfMonth.AddDays(offset))
|
||||
.ToList();
|
||||
|
||||
var attendancesTask = Task.Run(async () =>
|
||||
{
|
||||
await using var context = await _dbContextFactory.CreateDbContextAsync();
|
||||
return await context.Attendes
|
||||
.Where(a => a.AttendanceDate >= firstDayOfMonth && a.AttendanceDate < firstDayOfNextMonth && a.Employee != null && a.TenantId == tenantId)
|
||||
.GroupBy(a => a.ProjectID)
|
||||
.ToListAsync();
|
||||
});
|
||||
|
||||
var projectAllocationTask = Task.Run(async () =>
|
||||
{
|
||||
await using var context = await _dbContextFactory.CreateDbContextAsync();
|
||||
return await context.ProjectAllocations
|
||||
.Include(pa => pa.Employee)
|
||||
.Where(pa => pa.TenantId == tenantId && pa.IsActive)
|
||||
.ToListAsync();
|
||||
});
|
||||
|
||||
await Task.WhenAll(attendancesTask, projectAllocationTask);
|
||||
|
||||
var attendances = attendancesTask.Result;
|
||||
var projectAllocations = projectAllocationTask.Result;
|
||||
|
||||
var result = attendances.Select(g =>
|
||||
{
|
||||
var projectAllocation = projectAllocations.Where(pa => pa.ProjectId == g.Key && pa.Employee != null).ToList();
|
||||
var projectAttendance = projectAllocation.Select(pa =>
|
||||
{
|
||||
var attendances = g.Where(a => a.EmployeeId == pa.EmployeeId).ToList();
|
||||
var attendanceDate = attendances.Select(a => a.AttendanceDate.Date).ToList();
|
||||
return new
|
||||
{
|
||||
FirstName = pa.Employee!.FirstName,
|
||||
LastName = pa.Employee.LastName,
|
||||
Attendances = allDates.Select(d =>
|
||||
{
|
||||
var attendance = attendances.FirstOrDefault(a => a.AttendanceDate.Date == d);
|
||||
return new
|
||||
{
|
||||
AttendanceDate = d,
|
||||
CheckIn = attendance?.InTime,
|
||||
CheckOut = attendance?.OutTime,
|
||||
Activity = attendance?.Activity,
|
||||
IsApproved = attendance?.ApprovedById.HasValue,
|
||||
};
|
||||
}).ToList(),
|
||||
CheckInCheckOutDone = attendances.Where(a => a.InTime.HasValue && a.OutTime.HasValue && a.Activity == ATTENDANCE_MARK_TYPE.REGULARIZE).Count(),
|
||||
CheckInDone = attendances.Where(a => a.InTime.HasValue).Count(),
|
||||
CheckOutPending = attendances.Where(a => a.InTime.HasValue && !a.OutTime.HasValue).Count(),
|
||||
RejectedRegularize = attendances.Where(a => a.Activity == ATTENDANCE_MARK_TYPE.REGULARIZE_REJECT).Count(),
|
||||
AbsentAttendance = allDates.Where(d => !attendanceDate.Contains(d) && d.DayOfWeek != DayOfWeek.Sunday).Count()
|
||||
};
|
||||
}).OrderBy(ar => ar.FirstName).ThenBy(ar => ar.LastName).ToList();
|
||||
|
||||
return new
|
||||
{
|
||||
ProjectName = _context.Projects.Where(p => p.Id == g.Key && p.TenantId == tenantId).Select(p => p.Name).FirstOrDefault(),
|
||||
ProjectAttendance = projectAttendance
|
||||
};
|
||||
}).ToList();
|
||||
|
||||
var response = result.OrderBy(r => r.ProjectName).ToList();
|
||||
|
||||
return Ok(ApiResponse<object>.SuccessResponse(response, "Attendance Report fetched successfully", 200));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -457,7 +457,7 @@ namespace MarcoBMS.Services.Controllers
|
||||
t.WorkItem.WorkArea != null &&
|
||||
t.WorkItem.WorkArea.Floor != null &&
|
||||
t.WorkItem.WorkArea.Floor.Building != null &&
|
||||
t.WorkItem.WorkArea.Floor.Building.ProjectId != projectId &&
|
||||
t.WorkItem.WorkArea.Floor.Building.ProjectId == projectId &&
|
||||
t.TenantId == tenantId);
|
||||
|
||||
var taskFilter = TryDeserializeFilter(filter);
|
||||
@ -482,6 +482,13 @@ namespace MarcoBMS.Services.Controllers
|
||||
taskAllocationQuery = taskAllocationQuery.Where(t => t.WorkItem != null &&
|
||||
taskFilter.ActivityIds.Contains(t.WorkItem.ActivityId));
|
||||
}
|
||||
if (taskFilter.ServiceIds?.Any() ?? false)
|
||||
{
|
||||
taskAllocationQuery = taskAllocationQuery.Where(t => t.WorkItem != null &&
|
||||
t.WorkItem.ActivityMaster != null &&
|
||||
t.WorkItem.ActivityMaster.ActivityGroup != null &&
|
||||
taskFilter.ServiceIds.Contains(t.WorkItem.ActivityMaster.ActivityGroup.ServiceId));
|
||||
}
|
||||
if (taskFilter.dateFrom.HasValue && taskFilter.dateTo.HasValue)
|
||||
{
|
||||
taskAllocationQuery = taskAllocationQuery.Where(t => t.AssignmentDate.Date >= taskFilter.dateFrom.Value.Date &&
|
||||
@ -745,6 +752,97 @@ namespace MarcoBMS.Services.Controllers
|
||||
return Ok(ApiResponse<object>.SuccessResponse(taskVM, "Success", 200));
|
||||
}
|
||||
|
||||
[HttpGet("filter/{projectId}")]
|
||||
public async Task<IActionResult> GetTaskFilterObject(Guid projectId)
|
||||
{
|
||||
// Get the current tenant from claims/context
|
||||
Guid tenantId = GetTenantId();
|
||||
|
||||
// Log API invocation with the project and tenant for traceability
|
||||
_logger.LogInfo("Fetching filter objects for ProjectId={ProjectId}, TenantId={TenantId}", projectId, tenantId);
|
||||
|
||||
try
|
||||
{
|
||||
// AsNoTracking for improved performance—no intention to update these records
|
||||
// Only fetch & project properties actually required (DTO projection)
|
||||
var tasks = await _context.TaskAllocations
|
||||
.Include(t => t.WorkItem)
|
||||
.ThenInclude(wi => wi!.WorkArea)
|
||||
.ThenInclude(wa => wa!.Floor)
|
||||
.ThenInclude(f => f!.Building)
|
||||
.Include(t => t.WorkItem)
|
||||
.ThenInclude(wi => wi!.ActivityMaster)
|
||||
.ThenInclude(a => a!.ActivityGroup)
|
||||
.ThenInclude(ag => ag!.Service)
|
||||
.Where(t => t.WorkItem != null &&
|
||||
t.WorkItem.WorkArea != null &&
|
||||
t.WorkItem.WorkArea.Floor != null &&
|
||||
t.WorkItem.WorkArea.Floor.Building != null &&
|
||||
t.WorkItem.WorkArea.Floor.Building.ProjectId == projectId &&
|
||||
t.TenantId == tenantId).ToListAsync();
|
||||
|
||||
// Distinct by Id (since projection doesn't guarantee uniqueness across different allocations)
|
||||
var buildings = tasks.Where(t => t.WorkItem != null && t.WorkItem.WorkArea != null && t.WorkItem.WorkArea.Floor != null && t.WorkItem.WorkArea.Floor.Building != null)
|
||||
.Select(t => t.WorkItem!.WorkArea!.Floor!.Building!)
|
||||
.Select(b => new
|
||||
{
|
||||
Id = b.Id,
|
||||
Name = b.Name
|
||||
}).Distinct().ToList();
|
||||
|
||||
var floors = tasks.Where(t => t.WorkItem != null && t.WorkItem.WorkArea != null && t.WorkItem.WorkArea.Floor != null)
|
||||
.Select(t => t.WorkItem!.WorkArea!.Floor!)
|
||||
.Select(f => new
|
||||
{
|
||||
Id = f.Id,
|
||||
Name = f.FloorName,
|
||||
BuildingId = f.BuildingId
|
||||
}).Distinct().ToList();
|
||||
|
||||
var activities = tasks.Where(t => t.WorkItem != null &&
|
||||
t.WorkItem.ActivityMaster != null &&
|
||||
t.WorkItem.ActivityMaster.ActivityGroup != null &&
|
||||
t.WorkItem.ActivityMaster.ActivityGroup.Service != null)
|
||||
.Select(t => t.WorkItem!.ActivityMaster!)
|
||||
.Select(a => new
|
||||
{
|
||||
Id = a.Id,
|
||||
Name = a.ActivityName
|
||||
}).Distinct().ToList();
|
||||
|
||||
var services = tasks.Where(t => t.WorkItem != null &&
|
||||
t.WorkItem.ActivityMaster != null &&
|
||||
t.WorkItem.ActivityMaster.ActivityGroup != null &&
|
||||
t.WorkItem.ActivityMaster.ActivityGroup.Service != null)
|
||||
.Select(t => t.WorkItem!.ActivityMaster!.ActivityGroup!.Service!)
|
||||
.Select(s => new
|
||||
{
|
||||
Id = s.Id,
|
||||
Name = s.Name
|
||||
}).Distinct().ToList();
|
||||
|
||||
var response = new
|
||||
{
|
||||
Buildings = buildings,
|
||||
Floors = floors,
|
||||
Activities = activities,
|
||||
Services = services
|
||||
};
|
||||
|
||||
_logger.LogInfo("Successfully fetched filter objects for ProjectId={ProjectId}, TenantId={TenantId}", projectId, tenantId);
|
||||
|
||||
// Use DTO in API response for clarity and easier frontend usage
|
||||
return Ok(ApiResponse<object>.SuccessResponse(response, "Filter object for task fetched successfully", 200));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to fetch filter objects for ProjectId={ProjectId}, TenantId={TenantId}", projectId, tenantId);
|
||||
// Return a standard error result
|
||||
return StatusCode(500, ApiResponse<object>.ErrorResponse("Failed to fetch filter object.", 500));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Approves a reported task after validation, updates status, and stores attachments/comments.
|
||||
/// </summary>
|
||||
|
@ -1013,10 +1013,6 @@ namespace Marco.Pms.Services.Controllers
|
||||
|
||||
try
|
||||
{
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
await ClearPermissionForTenant();
|
||||
});
|
||||
var features = await _featureDetailsHelper.GetFeatureDetails(subscriptionPlan.FeaturesId);
|
||||
if (features == null)
|
||||
{
|
||||
@ -1069,7 +1065,7 @@ namespace Marco.Pms.Services.Controllers
|
||||
// Get root employee and role for this tenant
|
||||
var rootEmployee = await _context.Employees
|
||||
.Include(e => e.ApplicationUser)
|
||||
.FirstOrDefaultAsync(e => e.ApplicationUser != null && (e.ApplicationUser.IsRootUser ?? false) && e.TenantId == model.TenantId);
|
||||
.FirstOrDefaultAsync(e => e.ApplicationUser != null && (e.ApplicationUser.IsRootUser ?? false) && e.OrganizationId == tenant.OrganizationId);
|
||||
|
||||
if (rootEmployee == null)
|
||||
{
|
||||
@ -1127,6 +1123,9 @@ namespace Marco.Pms.Services.Controllers
|
||||
_logger.LogInfo("Removed {Count} role permission mappings for role {RoleId}", deleteMappings.Count, roleId);
|
||||
}
|
||||
|
||||
var _cache = scope.ServiceProvider.GetRequiredService<CacheUpdateHelper>();
|
||||
await _cache.ClearAllEmployeesFromCacheByTenantId(tenant.Id);
|
||||
|
||||
var _masteData = scope.ServiceProvider.GetRequiredService<MasterDataService>();
|
||||
|
||||
if (features.Modules?.ProjectManagement?.Enabled ?? false)
|
||||
@ -1325,10 +1324,6 @@ namespace Marco.Pms.Services.Controllers
|
||||
_logger.LogInfo("Subscription plan changed: Tenant={TenantId}, NewPlan={PlanId}",
|
||||
model.TenantId, model.PlanId);
|
||||
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
await ClearPermissionForTenant();
|
||||
});
|
||||
|
||||
// 8. Update tenant permissions based on subscription features.
|
||||
var features = await _featureDetailsHelper.GetFeatureDetails(subscriptionPlan.FeaturesId);
|
||||
@ -1363,7 +1358,7 @@ namespace Marco.Pms.Services.Controllers
|
||||
// 8c. Find root employee & role for this tenant.
|
||||
var rootEmployee = await context.Employees
|
||||
.Include(e => e.ApplicationUser)
|
||||
.FirstOrDefaultAsync(e => e.ApplicationUser != null && (e.ApplicationUser.IsRootUser ?? false) && e.TenantId == model.TenantId);
|
||||
.FirstOrDefaultAsync(e => e.ApplicationUser != null && (e.ApplicationUser.IsRootUser ?? false) && e.OrganizationId == tenant.OrganizationId);
|
||||
|
||||
if (rootEmployee == null)
|
||||
{
|
||||
@ -1374,7 +1369,8 @@ namespace Marco.Pms.Services.Controllers
|
||||
|
||||
var rootRoleId = await context.EmployeeRoleMappings
|
||||
.AsNoTracking()
|
||||
.Where(er => er.EmployeeId == rootEmployee.Id && er.TenantId == model.TenantId)
|
||||
.Include(er => er.Role)
|
||||
.Where(er => er.EmployeeId == rootEmployee.Id && er.TenantId == model.TenantId && er.Role != null && er.Role.Role == "Super User")
|
||||
.Select(er => er.RoleId)
|
||||
.FirstOrDefaultAsync();
|
||||
|
||||
@ -1439,6 +1435,9 @@ namespace Marco.Pms.Services.Controllers
|
||||
_logger.LogInfo("Permissions revoked: {Count} for Role={RoleId}", mappingsToRemove.Count, rootRoleId);
|
||||
}
|
||||
|
||||
var _cache = scope.ServiceProvider.GetRequiredService<CacheUpdateHelper>();
|
||||
await _cache.ClearAllEmployeesFromCacheByTenantId(tenant.Id);
|
||||
|
||||
var _masteData = scope.ServiceProvider.GetRequiredService<MasterDataService>();
|
||||
|
||||
if (features.Modules?.ProjectManagement?.Enabled ?? false)
|
||||
@ -1823,19 +1822,6 @@ namespace Marco.Pms.Services.Controllers
|
||||
return ApiResponse<SubscriptionPlanVM>.SuccessResponse(VM, "Success", 200);
|
||||
}
|
||||
|
||||
private async Task ClearPermissionForTenant()
|
||||
{
|
||||
await using var _context = await _dbContextFactory.CreateDbContextAsync();
|
||||
using var scope = _serviceScopeFactory.CreateScope();
|
||||
|
||||
var _cache = scope.ServiceProvider.GetRequiredService<CacheUpdateHelper>();
|
||||
var _cacheLogger = scope.ServiceProvider.GetRequiredService<ILoggingService>();
|
||||
|
||||
var employeeIds = await _context.Employees.Where(e => e.TenantId == tenantId).Select(e => e.Id).ToListAsync();
|
||||
await _cache.ClearAllEmployeesFromCacheByEmployeeIds(employeeIds, tenantId);
|
||||
_cacheLogger.LogInfo("{EmployeeCount} number of employee deleted", employeeIds.Count);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
@ -956,6 +956,28 @@ namespace Marco.Pms.Services.Helpers
|
||||
_logger.LogError(ex, "Error occured while deleting all employees from Cache");
|
||||
}
|
||||
}
|
||||
public async Task ClearAllEmployeesFromCacheByOnlyEmployeeId(Guid employeeId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var response = await _employeeCache.ClearAllEmployeesFromCacheByOnlyEmployeeId(employeeId);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error occured while deleting all employees from Cache");
|
||||
}
|
||||
}
|
||||
public async Task ClearAllEmployeesFromCacheByTenantId(Guid tenantId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var response = await _employeeCache.ClearAllEmployeesFromCacheByTenantId(tenantId);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error occured while deleting all employees from Cache");
|
||||
}
|
||||
}
|
||||
public async Task ClearAllEmployees()
|
||||
{
|
||||
try
|
||||
|
@ -71,11 +71,11 @@ namespace Marco.Pms.Services.Helpers
|
||||
.Where(a => a.ProjectID == projectId && a.InTime != null && a.InTime.Value.Date == reportDate)
|
||||
.ToListAsync();
|
||||
|
||||
var checkedInEmployeeIds = attendances.Select(a => a.EmployeeID).Distinct().ToHashSet();
|
||||
var checkoutPendingIds = attendances.Where(a => a.OutTime == null).Select(a => a.EmployeeID).Distinct().ToHashSet();
|
||||
var checkedInEmployeeIds = attendances.Select(a => a.EmployeeId).Distinct().ToHashSet();
|
||||
var checkoutPendingIds = attendances.Where(a => a.OutTime == null).Select(a => a.EmployeeId).Distinct().ToHashSet();
|
||||
var regularizationIds = attendances
|
||||
.Where(a => a.Activity == ATTENDANCE_MARK_TYPE.REQUEST_REGULARIZE)
|
||||
.Select(a => a.EmployeeID).Distinct().ToHashSet();
|
||||
.Select(a => a.EmployeeId).Distinct().ToHashSet();
|
||||
|
||||
// Preload buildings, floors, areas
|
||||
List<BuildingMongoDBVM>? buildings = null;
|
||||
@ -190,6 +190,7 @@ namespace Marco.Pms.Services.Helpers
|
||||
double totalCompletedWork = workItems.Sum(w => w.CompletedWork);
|
||||
|
||||
var todayAssignedTasks = tasks.Where(t => t.AssignmentDate.Date == reportDate).ToList();
|
||||
var todaysCompletedTasks = tasks.Where(t => t.AssignmentDate.Date == reportDate && t.ReportedById != null).ToList();
|
||||
var reportPending = tasks.Where(t => t.ReportedDate == null).ToList();
|
||||
|
||||
double totalPlannedTask = todayAssignedTasks.Sum(t => t.PlannedTask);
|
||||
@ -246,7 +247,7 @@ namespace Marco.Pms.Services.Helpers
|
||||
// Attendance details
|
||||
var performedAttendance = attendances.Select(att =>
|
||||
{
|
||||
var alloc = projectAllocations.FirstOrDefault(p => p.EmployeeId == att.EmployeeID);
|
||||
var alloc = projectAllocations.FirstOrDefault(p => p.EmployeeId == att.EmployeeId);
|
||||
var role = jobRoles.FirstOrDefault(r => r.Id == alloc?.JobRoleId);
|
||||
string name = $"{alloc?.Employee?.FirstName ?? ""} {alloc?.Employee?.LastName ?? ""}";
|
||||
|
||||
@ -263,14 +264,17 @@ namespace Marco.Pms.Services.Helpers
|
||||
// Fill report
|
||||
statisticReport.TodaysAttendances = checkedInEmployeeIds.Count;
|
||||
statisticReport.TotalEmployees = assignedEmployeeIds.Count;
|
||||
statisticReport.AttendancePercentage = assignedEmployeeIds.Count > 0 ? (checkedInEmployeeIds.Count / assignedEmployeeIds.Count) * 100 : 0;
|
||||
statisticReport.RegularizationPending = regularizationIds.Count;
|
||||
statisticReport.CheckoutPending = checkoutPendingIds.Count;
|
||||
statisticReport.TotalPlannedWork = totalPlannedWork;
|
||||
statisticReport.TotalCompletedWork = totalCompletedWork;
|
||||
statisticReport.CompletionStatus = totalPlannedWork > 0 ? (totalCompletedWork / totalPlannedWork) * 100 : 0;
|
||||
statisticReport.TotalPlannedTask = totalPlannedTask;
|
||||
statisticReport.TotalCompletedTask = totalCompletedTask;
|
||||
statisticReport.CompletionStatus = totalPlannedWork > 0 ? totalCompletedWork / totalPlannedWork : 0;
|
||||
statisticReport.AttendancePercentage = totalCompletedTask > 0 ? (totalCompletedTask / totalPlannedTask) * 100 : 0;
|
||||
statisticReport.TodaysAssignTasks = todayAssignedTasks.Count;
|
||||
statisticReport.TodaysCompletedTasks = todaysCompletedTasks.Count;
|
||||
statisticReport.ReportPending = reportPending.Count;
|
||||
statisticReport.TeamOnSite = teamOnSite;
|
||||
statisticReport.PerformedTasks = performedTasks;
|
||||
@ -326,11 +330,11 @@ namespace Marco.Pms.Services.Helpers
|
||||
.Where(a => a.ProjectID == projectId && a.InTime != null && a.InTime.Value.Date == reportDate)
|
||||
.ToListAsync();
|
||||
|
||||
var checkedInEmployeeIds = attendances.Select(a => a.EmployeeID).Distinct().ToHashSet();
|
||||
var checkoutPendingIds = attendances.Where(a => a.OutTime == null).Select(a => a.EmployeeID).Distinct().ToHashSet();
|
||||
var checkedInEmployeeIds = attendances.Select(a => a.EmployeeId).Distinct().ToHashSet();
|
||||
var checkoutPendingIds = attendances.Where(a => a.OutTime == null).Select(a => a.EmployeeId).Distinct().ToHashSet();
|
||||
var regularizationIds = attendances
|
||||
.Where(a => a.Activity == ATTENDANCE_MARK_TYPE.REQUEST_REGULARIZE)
|
||||
.Select(a => a.EmployeeID).Distinct().ToHashSet();
|
||||
.Select(a => a.EmployeeId).Distinct().ToHashSet();
|
||||
|
||||
// Preload buildings, floors, areas
|
||||
List<BuildingMongoDBVM>? buildings = null;
|
||||
@ -501,7 +505,7 @@ namespace Marco.Pms.Services.Helpers
|
||||
// Attendance details
|
||||
var performedAttendance = attendances.Select(att =>
|
||||
{
|
||||
var alloc = projectAllocations.FirstOrDefault(p => p.EmployeeId == att.EmployeeID);
|
||||
var alloc = projectAllocations.FirstOrDefault(p => p.EmployeeId == att.EmployeeId);
|
||||
var role = jobRoles.FirstOrDefault(r => r.Id == alloc?.JobRoleId);
|
||||
string name = $"{alloc?.Employee?.FirstName ?? ""} {alloc?.Employee?.LastName ?? ""}";
|
||||
|
||||
|
@ -37,7 +37,8 @@ namespace MarcoBMS.Services.Helpers
|
||||
// --- Step 1: Define the subquery using the main thread's context ---
|
||||
// This is safe because the query is not executed yet.
|
||||
var employeeRoleIdsQuery = _context.EmployeeRoleMappings
|
||||
.Where(erm => erm.EmployeeId == EmployeeId && erm.IsEnabled)
|
||||
.AsNoTracking()
|
||||
.Where(erm => erm.EmployeeId == EmployeeId && erm.IsEnabled && erm.TenantId == tenantId)
|
||||
.Select(erm => erm.RoleId);
|
||||
|
||||
// --- Step 2: Asynchronously update the cache using the DbContextFactory ---
|
||||
@ -50,7 +51,8 @@ namespace MarcoBMS.Services.Helpers
|
||||
|
||||
// Now, re-create and execute the query using this new, isolated context.
|
||||
var roleIds = await contextForCache.EmployeeRoleMappings
|
||||
.Where(erm => erm.EmployeeId == EmployeeId && erm.IsEnabled)
|
||||
.AsNoTracking()
|
||||
.Where(erm => erm.EmployeeId == EmployeeId && erm.IsEnabled && erm.TenantId == tenantId)
|
||||
.Select(erm => erm.RoleId)
|
||||
.ToListAsync();
|
||||
|
||||
@ -73,9 +75,12 @@ namespace MarcoBMS.Services.Helpers
|
||||
var roleIds = await employeeRoleIdsQuery.ToListAsync();
|
||||
|
||||
var permissionIds = await _context.RolePermissionMappings
|
||||
.AsNoTracking()
|
||||
.Where(rp => roleIds.Contains(rp.ApplicationRoleId)).Select(rp => rp.FeaturePermissionId).ToListAsync();
|
||||
|
||||
var permissions = await _context.FeaturePermissions.Include(f => f.Feature)
|
||||
var permissions = await _context.FeaturePermissions
|
||||
.AsNoTracking()
|
||||
.Include(f => f.Feature)
|
||||
.Where(fp => permissionIds.Contains(fp.Id))
|
||||
.Distinct()
|
||||
.ToListAsync();
|
||||
|
@ -36,7 +36,7 @@ namespace MarcoBMS.Services.Helpers
|
||||
var tenantId = _httpContextAccessor.HttpContext?.User.FindFirst("TenantId")?.Value;
|
||||
if (tenantId != null)
|
||||
{
|
||||
return await _context.Tenants.FirstOrDefaultAsync(t => t.Id == Guid.Parse(tenantId));
|
||||
return await _context.Tenants.AsNoTracking().FirstOrDefaultAsync(t => t.Id == Guid.Parse(tenantId));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@ -54,7 +54,7 @@ namespace MarcoBMS.Services.Helpers
|
||||
{
|
||||
var user = await GetCurrentUserAsync();
|
||||
if (user == null) return new Employee { };
|
||||
var Employee = await _context.Employees.Include(e => e.JobRole).FirstOrDefaultAsync(e => e.ApplicationUserId == user.Id && e.IsActive);
|
||||
var Employee = await _context.Employees.AsNoTracking().Include(a => a.ApplicationUser).Include(e => e.JobRole).FirstOrDefaultAsync(e => e.ApplicationUserId == user.Id && e.IsActive);
|
||||
return Employee ?? new Employee { };
|
||||
}
|
||||
|
||||
|
@ -1,10 +1,8 @@
|
||||
using MarcoBMS.Services.Service;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
|
||||
namespace Marco.Pms.Services.Hubs
|
||||
{
|
||||
[Authorize]
|
||||
public class MarcoHub : Hub
|
||||
{
|
||||
private readonly ILoggingService _logger;
|
||||
|
@ -379,6 +379,27 @@ namespace Marco.Pms.Services.MappingProfiles
|
||||
|
||||
#endregion
|
||||
|
||||
#region ======================================================= Contact Category Master =======================================================
|
||||
CreateMap<CreateContactCategoryDto, ContactCategoryMaster>();
|
||||
CreateMap<UpdateContactCategoryDto, ContactCategoryMaster>();
|
||||
CreateMap<ContactCategoryMaster, ContactCategoryVM>();
|
||||
#endregion
|
||||
#region ======================================================= Contact Tag Master =======================================================
|
||||
CreateMap<CreateContactTagDto, ContactTagMaster>();
|
||||
CreateMap<UpdateContactTagDto, ContactTagMaster>();
|
||||
CreateMap<ContactTagMaster, ContactTagVM>();
|
||||
#endregion
|
||||
#region ======================================================= Expenses Status Master =======================================================
|
||||
#endregion
|
||||
#region ======================================================= Expenses Status Master =======================================================
|
||||
#endregion
|
||||
#region ======================================================= Expenses Status Master =======================================================
|
||||
#endregion
|
||||
#region ======================================================= Expenses Status Master =======================================================
|
||||
#endregion
|
||||
#region ======================================================= Expenses Status Master =======================================================
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
|
||||
#region ======================================================= Document =======================================================
|
||||
|
@ -956,10 +956,14 @@ namespace Marco.Pms.Services.Service
|
||||
try
|
||||
{
|
||||
var contact = _mapper.Map<Contact>(createContact);
|
||||
if (string.IsNullOrWhiteSpace(createContact.Name))
|
||||
if (string.IsNullOrWhiteSpace(createContact.Description))
|
||||
{
|
||||
contact.Description = string.Empty;
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(createContact.Designation))
|
||||
{
|
||||
contact.Designation = string.Empty;
|
||||
}
|
||||
contact.CreatedAt = DateTime.UtcNow;
|
||||
contact.CreatedById = loggedInEmployeeId;
|
||||
contact.TenantId = tenantId;
|
||||
@ -2031,9 +2035,11 @@ namespace Marco.Pms.Services.Service
|
||||
|
||||
try
|
||||
{
|
||||
var bucketIds = await _context.ContactBucketMappings.Where(cb => cb.ContactId == noteDto.ContactId).Select(cb => cb.BucketId).ToListAsync();
|
||||
var hasContactAccess = await _context.EmployeeBucketMappings.AnyAsync(eb => bucketIds.Contains(eb.BucketId) && eb.EmployeeId == loggedInEmployee.Id);
|
||||
if (!hasContactAccess)
|
||||
var (hasAdminPermission, hasManagerPermission, hasUserPermission) = await CheckPermissionsAsync(loggedInEmployee.Id);
|
||||
|
||||
var bucketIds = await _context.ContactBucketMappings.AsNoTracking().Where(cb => cb.ContactId == noteDto.ContactId).Select(cb => cb.BucketId).ToListAsync();
|
||||
var hasContactAccess = await _context.EmployeeBucketMappings.AsNoTracking().AnyAsync(eb => bucketIds.Contains(eb.BucketId) && eb.EmployeeId == loggedInEmployee.Id);
|
||||
if (!hasAdminPermission && !hasContactAccess)
|
||||
{
|
||||
_logger.LogWarning("Employee {EmployeeId} does not have permission to delete contact {ContactId}",
|
||||
loggedInEmployee.Id, noteDto.ContactId);
|
||||
@ -2265,9 +2271,11 @@ namespace Marco.Pms.Services.Service
|
||||
return ApiResponse<object>.ErrorResponse("Note not found", "Note not found", 404);
|
||||
}
|
||||
|
||||
var bucketIds = await _context.ContactBucketMappings.Where(cb => cb.ContactId == note.ContactId).Select(cb => cb.BucketId).ToListAsync();
|
||||
var hasContactAccess = await _context.EmployeeBucketMappings.AnyAsync(eb => bucketIds.Contains(eb.BucketId) && eb.EmployeeId == loggedInEmployee.Id);
|
||||
if (hasContactAccess)
|
||||
var (hasAdminPermission, hasManagerPermission, hasUserPermission) = await CheckPermissionsAsync(loggedInEmployee.Id);
|
||||
|
||||
var bucketIds = await _context.ContactBucketMappings.AsNoTracking().Where(cb => cb.ContactId == note.ContactId).Select(cb => cb.BucketId).ToListAsync();
|
||||
var hasContactAccess = await _context.EmployeeBucketMappings.AsNoTracking().AnyAsync(eb => bucketIds.Contains(eb.BucketId) && eb.EmployeeId == loggedInEmployee.Id);
|
||||
if (!hasAdminPermission && !hasContactAccess)
|
||||
{
|
||||
_logger.LogWarning("Employee {EmployeeId} does not have permission to delete contact {ContactId}",
|
||||
loggedInEmployee.Id, note.ContactId);
|
||||
|
@ -23,6 +23,7 @@ using MarcoBMS.Services.Service;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Text.Json;
|
||||
using System.Text.RegularExpressions;
|
||||
using Document = Marco.Pms.Model.DocumentManager.Document;
|
||||
|
||||
namespace Marco.Pms.Services.Service
|
||||
@ -119,8 +120,12 @@ namespace Marco.Pms.Services.Service
|
||||
// 2. --- Deserialize Filter and Apply ---
|
||||
ExpensesFilter? expenseFilter = TryDeserializeFilter(filter);
|
||||
|
||||
var (totalPages, totalCount, cacheList) = await _cache.GetExpenseListAsync(tenantId, loggedInEmployeeId, hasViewAllPermissionTask.Result, hasViewSelfPermissionTask.Result,
|
||||
pageNumber, pageSize, expenseFilter, searchString);
|
||||
//var (totalPages, totalCount, cacheList) = await _cache.GetExpenseListAsync(tenantId, loggedInEmployeeId, hasViewAllPermissionTask.Result, hasViewSelfPermissionTask.Result,
|
||||
// pageNumber, pageSize, expenseFilter, searchString);
|
||||
|
||||
List<ExpenseDetailsMongoDB>? cacheList = null;
|
||||
var totalPages = 0;
|
||||
var totalCount = 0;
|
||||
|
||||
// 3. --- Build Base Query and Apply Permissions ---
|
||||
// Start with a base IQueryable. Filters will be chained onto this.
|
||||
@ -268,7 +273,8 @@ namespace Marco.Pms.Services.Service
|
||||
{
|
||||
try
|
||||
{
|
||||
var expenseDetails = await _cache.GetExpenseDetailsById(id, tenantId);
|
||||
//ExpenseDetailsMongoDB? expenseDetails = await _cache.GetExpenseDetailsById(id, tenantId);
|
||||
ExpenseDetailsMongoDB? expenseDetails = null;
|
||||
if (expenseDetails == null)
|
||||
{
|
||||
var expense = await _context.Expenses.AsNoTracking().FirstOrDefaultAsync(e => e.Id == id && e.TenantId == tenantId);
|
||||
@ -313,6 +319,13 @@ namespace Marco.Pms.Services.Service
|
||||
{
|
||||
status.PermissionIds = permissionStatusMappings.Where(ps => ps.StatusId == status.Id).Select(ps => ps.PermissionIds).FirstOrDefault();
|
||||
}
|
||||
int index = vm.NextStatus.FindIndex(ns => ns.DisplayName == "Reject");
|
||||
if (index > -1)
|
||||
{
|
||||
var item = vm.NextStatus[index];
|
||||
vm.NextStatus.RemoveAt(index);
|
||||
vm.NextStatus.Insert(0, item);
|
||||
}
|
||||
}
|
||||
vm.ExpensesReimburse = _mapper.Map<ExpensesReimburseVM>(expensesReimburse);
|
||||
|
||||
@ -482,6 +495,15 @@ namespace Marco.Pms.Services.Service
|
||||
await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
|
||||
return await dbContext.PaymentModeMatser.AsNoTracking().FirstOrDefaultAsync(pm => pm.Id == dto.PaymentModeId);
|
||||
});
|
||||
var expenseUIdTask = Task.Run(async () =>
|
||||
{
|
||||
await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
|
||||
var result = await dbContext.Expenses
|
||||
.Where(e => !string.IsNullOrWhiteSpace(e.ExpenseUId)).ToListAsync();
|
||||
return result
|
||||
.Select(e => ExtractNumber(e.ExpenseUId))
|
||||
.OrderByDescending(id => id).FirstOrDefault();
|
||||
});
|
||||
var statusMappingTask = Task.Run(async () =>
|
||||
{
|
||||
await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
|
||||
@ -501,10 +523,7 @@ namespace Marco.Pms.Services.Service
|
||||
|
||||
|
||||
// Await all prerequisite checks at once.
|
||||
await Task.WhenAll(
|
||||
hasUploadPermissionTask, hasProjectPermissionTask,
|
||||
projectTask, expenseTypeTask, paymentModeTask, statusMappingTask, paidByTask
|
||||
);
|
||||
await Task.WhenAll(hasUploadPermissionTask, hasProjectPermissionTask, projectTask, expenseTypeTask, paymentModeTask, statusMappingTask, paidByTask, expenseUIdTask);
|
||||
|
||||
// 2. Aggregate and Check Results
|
||||
if (!await hasUploadPermissionTask || !await hasProjectPermissionTask)
|
||||
@ -514,11 +533,12 @@ namespace Marco.Pms.Services.Service
|
||||
}
|
||||
|
||||
var validationErrors = new List<string>();
|
||||
var project = await projectTask;
|
||||
var expenseType = await expenseTypeTask;
|
||||
var paymentMode = await paymentModeTask;
|
||||
var statusMapping = await statusMappingTask;
|
||||
var paidBy = await paidByTask;
|
||||
var project = projectTask.Result;
|
||||
var expenseType = expenseTypeTask.Result;
|
||||
var paymentMode = paymentModeTask.Result;
|
||||
var statusMapping = statusMappingTask.Result;
|
||||
var paidBy = paidByTask.Result;
|
||||
var lastExpenseUId = expenseUIdTask.Result;
|
||||
|
||||
if (project == null) validationErrors.Add("Project not found.");
|
||||
if (paidBy == null) validationErrors.Add("Paid by employee not found");
|
||||
@ -534,9 +554,10 @@ namespace Marco.Pms.Services.Service
|
||||
_logger.LogWarning("Expense creation failed due to validation errors: {ValidationErrors}", errorMessage);
|
||||
return ApiResponse<object>.ErrorResponse("Invalid input data.", errorMessage, 400);
|
||||
}
|
||||
|
||||
var currentexpenseUId = (lastExpenseUId + 1).ToString("D5");
|
||||
// 3. Entity Creation
|
||||
var expense = _mapper.Map<Expenses>(dto);
|
||||
expense.ExpenseUId = $"EX-{currentexpenseUId}";
|
||||
expense.CreatedById = loggedInEmployee.Id;
|
||||
expense.CreatedAt = DateTime.UtcNow;
|
||||
expense.TenantId = tenantId;
|
||||
@ -1079,6 +1100,13 @@ namespace Marco.Pms.Services.Service
|
||||
#endregion
|
||||
|
||||
#region =================================================================== Helper Functions ===================================================================
|
||||
|
||||
private int ExtractNumber(string id)
|
||||
{
|
||||
// Extract trailing number; handles EX_0001, EX-0001, EX0001
|
||||
var m = Regex.Match(id ?? string.Empty, @"(\d+)$");
|
||||
return m.Success ? int.Parse(m.Value) : int.MinValue; // put invalid IDs at the bottom
|
||||
}
|
||||
private static object ExceptionMapper(Exception ex)
|
||||
{
|
||||
return new
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -79,6 +79,7 @@ namespace Marco.Pms.Services.Service
|
||||
{
|
||||
// Fetch permissions explicitly assigned to this employee in the project.
|
||||
var projectLevelPermissionIds = await _context.ProjectLevelPermissionMappings
|
||||
.AsNoTracking()
|
||||
.Where(pl => pl.ProjectId == projectId.Value && pl.EmployeeId == employeeId)
|
||||
.Select(pl => pl.PermissionId)
|
||||
.ToListAsync();
|
||||
@ -97,6 +98,7 @@ namespace Marco.Pms.Services.Service
|
||||
|
||||
// Get all feature permissions under those modules where the user didn't have explicit project-level grants.
|
||||
var allOverriddenPermissions = await _context.FeaturePermissions
|
||||
.AsNoTracking()
|
||||
.Where(fp => projectLevelModuleIds.Contains(fp.FeatureId) &&
|
||||
!projectLevelPermissionIds.Contains(fp.Id))
|
||||
.Select(fp => fp.Id)
|
||||
@ -138,12 +140,12 @@ namespace Marco.Pms.Services.Service
|
||||
var hasPermission = await HasPermission(PermissionsMaster.ManageProject, employeeId);
|
||||
if (hasPermission)
|
||||
{
|
||||
var projects = await _context.Projects.Where(c => c.TenantId == LoggedInEmployee.TenantId).ToListAsync();
|
||||
var projects = await _context.Projects.AsNoTracking().Where(c => c.TenantId == LoggedInEmployee.TenantId).ToListAsync();
|
||||
projectIds = projects.Select(p => p.Id).ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
var allocation = await _context.ProjectAllocations.Where(c => c.EmployeeId == employeeId && c.IsActive).ToListAsync();
|
||||
var allocation = await _context.ProjectAllocations.AsNoTracking().Where(c => c.EmployeeId == employeeId && c.IsActive).ToListAsync();
|
||||
if (!allocation.Any())
|
||||
{
|
||||
return false;
|
||||
|
@ -716,7 +716,7 @@ namespace Marco.Pms.Services.Service
|
||||
.Include(pa => pa.Employee)
|
||||
.ThenInclude(e => e!.Organization)
|
||||
.Include(pa => pa.Service)
|
||||
.Where(pa => pa.ProjectId == projectId && pa.TenantId == tenantId && pa.Service != null);
|
||||
.Where(pa => pa.ProjectId == projectId && pa.TenantId == tenantId);
|
||||
|
||||
// Conditionally apply the filter for active allocations.
|
||||
if (!includeInactive)
|
||||
@ -738,6 +738,7 @@ namespace Marco.Pms.Services.Service
|
||||
}
|
||||
|
||||
var allocations = await projectAllocationQuery
|
||||
.Where(pa => pa.Service != null)
|
||||
.Select(pa => new
|
||||
{
|
||||
// Fields from ProjectAllocation
|
||||
@ -753,8 +754,10 @@ namespace Marco.Pms.Services.Service
|
||||
LastName = pa.Employee.LastName,
|
||||
MiddleName = pa.Employee.MiddleName,
|
||||
|
||||
OrganizationId = pa.Employee.OrganizationId,
|
||||
OrganizationName = pa.Employee.Organization!.Name,
|
||||
|
||||
ServiceId = pa.ServiceId,
|
||||
ServiceName = pa.Service!.Name,
|
||||
|
||||
// Simplified JobRoleId logic: Use the allocation's role if it exists, otherwise fall back to the employee's default role.
|
||||
@ -1010,6 +1013,12 @@ namespace Marco.Pms.Services.Service
|
||||
}
|
||||
}
|
||||
|
||||
var selectedEmployee = await _context.Employees.FirstOrDefaultAsync(e => e.Id == employeeId);
|
||||
if (selectedEmployee == null)
|
||||
{
|
||||
_logger.LogWarning("Employee not found while assigning the projects to employee");
|
||||
return ApiResponse<List<ProjectAllocationVM>>.ErrorResponse("Employee not found", "Employee not found", 404);
|
||||
}
|
||||
|
||||
// --- Step 2: Fetch all relevant existing data in ONE database call ---
|
||||
var projectIdsInDto = allocationsDto.Select(p => p.ProjectId).ToList();
|
||||
@ -1025,6 +1034,11 @@ namespace Marco.Pms.Services.Service
|
||||
|
||||
var processedAllocations = new List<ProjectAllocation>();
|
||||
|
||||
var serviceProjects = await _context.ProjectOrgMappings
|
||||
.Include(ps => ps.ProjectService)
|
||||
.Where(ps => ps.ProjectService != null && projectIdsInDto.Contains(ps.ProjectService.ProjectId) &&
|
||||
ps.OrganizationId == selectedEmployee.OrganizationId && ps.TenantId == tenantId).ToListAsync();
|
||||
|
||||
// --- Step 3: Process all logic IN MEMORY, tracking changes ---
|
||||
foreach (var dto in allocationsDto)
|
||||
{
|
||||
@ -1046,11 +1060,13 @@ namespace Marco.Pms.Services.Service
|
||||
{
|
||||
if (existingAllocation == null)
|
||||
{
|
||||
var serviceProject = serviceProjects.FirstOrDefault(ps => ps.ProjectService != null && ps.ProjectService.ProjectId == dto.ProjectId);
|
||||
// Create a new allocation because an active one doesn't exist.
|
||||
var newAllocation = _mapper.Map<ProjectAllocation>(dto);
|
||||
newAllocation.EmployeeId = employeeId;
|
||||
newAllocation.TenantId = tenantId;
|
||||
newAllocation.AllocationDate = DateTime.UtcNow;
|
||||
newAllocation.ServiceId = dto.ServiceId ?? serviceProject?.ProjectService?.ServiceId;
|
||||
newAllocation.IsActive = true;
|
||||
_context.ProjectAllocations.Add(newAllocation);
|
||||
processedAllocations.Add(newAllocation);
|
||||
@ -2632,14 +2648,36 @@ namespace Marco.Pms.Services.Service
|
||||
var permissionService = scope.ServiceProvider.GetRequiredService<PermissionServices>();
|
||||
|
||||
// Retrieve the project by projectId and tenantId
|
||||
var project = await _context.Projects
|
||||
.AsNoTracking()
|
||||
.FirstOrDefaultAsync(p => p.Id == projectId && p.TenantId == tenantId);
|
||||
|
||||
if (project == null)
|
||||
var projectTask = Task.Run(async () =>
|
||||
{
|
||||
_logger.LogWarning("Project not found. ProjectId: {ProjectId}, TenantId: {TenantId}", projectId, tenantId);
|
||||
return ApiResponse<object>.ErrorResponse("Project not found", "Project not found in database", 404);
|
||||
await using var context = await _dbContextFactory.CreateDbContextAsync();
|
||||
return await context.Projects.AsNoTracking().Include(p => p.Promoter).Include(p => p.PMC).FirstOrDefaultAsync(p => p.Id == projectId && p.TenantId == tenantId);
|
||||
});
|
||||
|
||||
var tenantTask = Task.Run(async () =>
|
||||
{
|
||||
await using var context = await _dbContextFactory.CreateDbContextAsync();
|
||||
return await context.Tenants.AsNoTracking().Include(t => t.Organization).FirstOrDefaultAsync(t => t.Id == tenantId);
|
||||
});
|
||||
var projectServiceTask = Task.Run(async () =>
|
||||
{
|
||||
await using var context = await _dbContextFactory.CreateDbContextAsync();
|
||||
return await context.ProjectServiceMappings
|
||||
.AsNoTracking()
|
||||
.Include(ps => ps!.Service)
|
||||
.Where(ps => ps.ProjectId == projectId && ps.TenantId == tenantId).ToListAsync();
|
||||
});
|
||||
|
||||
await Task.WhenAll(projectTask, tenantTask, projectServiceTask);
|
||||
|
||||
var project = projectTask.Result;
|
||||
var tenant = tenantTask.Result;
|
||||
var projectService = projectServiceTask.Result;
|
||||
|
||||
if (project == null || tenant == null)
|
||||
{
|
||||
_logger.LogWarning("Project {ProjectId} not found in database for tenant {TenantId}", projectId, tenantId);
|
||||
return ApiResponse<object>.ErrorResponse("Project not found", "Project not found", 404);
|
||||
}
|
||||
|
||||
// Check if the logged in employee has permission to access the project
|
||||
@ -2651,20 +2689,29 @@ namespace Marco.Pms.Services.Service
|
||||
}
|
||||
|
||||
// Fetch all project-organization mappings with related service and organization data
|
||||
var projectOrgMappings = await _context.ProjectOrgMappings
|
||||
var projectOrgMappingsQuery = _context.ProjectOrgMappings
|
||||
.AsNoTracking()
|
||||
.Include(po => po.ProjectService)
|
||||
.ThenInclude(ps => ps!.Service)
|
||||
.Include(po => po.AssignedBy)
|
||||
.Include(po => po.OrganizationType)
|
||||
.Include(po => po.Organization)
|
||||
.Where(po => po.ProjectService != null
|
||||
&& po.ProjectService.ProjectId == projectId
|
||||
&& po.TenantId == tenantId)
|
||||
&& po.TenantId == tenantId);
|
||||
|
||||
if (loggedInEmployee.OrganizationId != project.PMCId && loggedInEmployee.OrganizationId != project.PromoterId && loggedInEmployee.OrganizationId != tenant.OrganizationId)
|
||||
{
|
||||
projectOrgMappingsQuery = projectOrgMappingsQuery.Where(po => po.ParentOrganizationId == loggedInEmployee.OrganizationId || po.OrganizationId == loggedInEmployee.OrganizationId);
|
||||
}
|
||||
|
||||
var projectOrgMappings = await projectOrgMappingsQuery
|
||||
.Distinct()
|
||||
.ToListAsync();
|
||||
|
||||
// Filter and map the data to the desired view model
|
||||
var response = projectOrgMappings
|
||||
.Where(po => po.Organization != null)
|
||||
.Where(po => po.Organization != null && po.OrganizationType != null)
|
||||
.Select(po => new ProjectOrganizationVM
|
||||
{
|
||||
Id = po.Organization!.Id,
|
||||
@ -2673,6 +2720,7 @@ namespace Marco.Pms.Services.Service
|
||||
ContactPerson = po.Organization.ContactPerson,
|
||||
SPRID = po.Organization.SPRID,
|
||||
logoImage = po.Organization.logoImage,
|
||||
OrganizationType = po.OrganizationType!.Name,
|
||||
AssignedBy = _mapper.Map<BasicEmployeeVM>(po.AssignedBy),
|
||||
Service = _mapper.Map<ServiceMasterVM>(po.ProjectService!.Service),
|
||||
AssignedDate = po.AssignedDate,
|
||||
@ -2680,6 +2728,189 @@ namespace Marco.Pms.Services.Service
|
||||
})
|
||||
.ToList();
|
||||
|
||||
if (loggedInEmployee.OrganizationId == project.PMCId || loggedInEmployee.OrganizationId == project.PromoterId || loggedInEmployee.OrganizationId == tenant.OrganizationId)
|
||||
{
|
||||
var pmc = project.PMC;
|
||||
var promoter = project.Promoter;
|
||||
var organization = tenant.Organization;
|
||||
|
||||
var employee = await _context.Employees.AsNoTracking().Include(e => e.JobRole).FirstOrDefaultAsync(e => e.OrganizationId == loggedInEmployee.OrganizationId && e.IsPrimary);
|
||||
|
||||
var assignedBy = _mapper.Map<BasicEmployeeVM>(employee);
|
||||
var assignedDate = project.StartDate.HasValue ? project.StartDate.Value : DateTime.UtcNow;
|
||||
var completionDate = project.EndDate;
|
||||
|
||||
if (!response.Any(r => r.Id == project.PMCId) && pmc != null)
|
||||
{
|
||||
response.Add(new ProjectOrganizationVM
|
||||
{
|
||||
Id = pmc.Id,
|
||||
Name = pmc.Name,
|
||||
Email = pmc.Email,
|
||||
ContactPerson = pmc.ContactPerson,
|
||||
SPRID = pmc.SPRID,
|
||||
logoImage = pmc.logoImage,
|
||||
OrganizationType = "PMC",
|
||||
AssignedBy = assignedBy,
|
||||
AssignedDate = assignedDate,
|
||||
CompletionDate = completionDate
|
||||
});
|
||||
}
|
||||
if (!response.Any(r => r.Id == project.PromoterId) && promoter != null)
|
||||
{
|
||||
response.Add(new ProjectOrganizationVM
|
||||
{
|
||||
Id = promoter.Id,
|
||||
Name = promoter.Name,
|
||||
Email = promoter.Email,
|
||||
ContactPerson = promoter.ContactPerson,
|
||||
SPRID = promoter.SPRID,
|
||||
logoImage = promoter.logoImage,
|
||||
OrganizationType = "Promotor",
|
||||
AssignedBy = assignedBy,
|
||||
AssignedDate = assignedDate,
|
||||
CompletionDate = completionDate
|
||||
});
|
||||
}
|
||||
if (!response.Any(r => r.Id == tenant.OrganizationId) && organization != null)
|
||||
{
|
||||
response.Add(new ProjectOrganizationVM
|
||||
{
|
||||
Id = organization.Id,
|
||||
Name = organization.Name,
|
||||
Email = organization.Email,
|
||||
ContactPerson = organization.ContactPerson,
|
||||
SPRID = organization.SPRID,
|
||||
logoImage = organization.logoImage,
|
||||
OrganizationType = "Primary",
|
||||
AssignedBy = assignedBy,
|
||||
AssignedDate = assignedDate,
|
||||
CompletionDate = completionDate
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_logger.LogInfo("Fetched {Count} assigned organizations for ProjectId: {ProjectId}", response.Count, projectId);
|
||||
|
||||
return ApiResponse<object>.SuccessResponse(response, "Successfully fetched the list of organizations assigned to the project", 200);
|
||||
}
|
||||
catch (DbUpdateException dbEx)
|
||||
{
|
||||
_logger.LogError(dbEx, "Database exception while fetching assigned organizations for ProjectId: {ProjectId}", projectId);
|
||||
return ApiResponse<object>.ErrorResponse("Internal error", "A database exception occurred", 500);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Unhandled exception while fetching assigned organizations for ProjectId: {ProjectId}", projectId);
|
||||
return ApiResponse<object>.ErrorResponse("Internal error", "An internal exception occurred", 500);
|
||||
}
|
||||
}
|
||||
public async Task<ApiResponse<object>> GetAssignedOrganizationsToProjectForDropdownAsync(Guid projectId, Guid tenantId, Employee loggedInEmployee)
|
||||
{
|
||||
_logger.LogDebug("Started fetching assigned organizations for ProjectId: {ProjectId} and TenantId: {TenantId} by user {UserId}",
|
||||
projectId, tenantId, loggedInEmployee.Id);
|
||||
|
||||
try
|
||||
{
|
||||
// Create a scoped PermissionServices instance for permission checks
|
||||
using var scope = _serviceScopeFactory.CreateScope();
|
||||
var permissionService = scope.ServiceProvider.GetRequiredService<PermissionServices>();
|
||||
|
||||
// Retrieve the project by projectId and tenantId
|
||||
var projectTask = Task.Run(async () =>
|
||||
{
|
||||
await using var context = await _dbContextFactory.CreateDbContextAsync();
|
||||
return await context.Projects.AsNoTracking().Include(p => p.Promoter).Include(p => p.PMC).FirstOrDefaultAsync(p => p.Id == projectId && p.TenantId == tenantId);
|
||||
});
|
||||
|
||||
var tenantTask = Task.Run(async () =>
|
||||
{
|
||||
await using var context = await _dbContextFactory.CreateDbContextAsync();
|
||||
return await context.Tenants.AsNoTracking().Include(t => t.Organization).FirstOrDefaultAsync(t => t.Id == tenantId);
|
||||
});
|
||||
var projectServiceTask = Task.Run(async () =>
|
||||
{
|
||||
await using var context = await _dbContextFactory.CreateDbContextAsync();
|
||||
return await context.ProjectServiceMappings
|
||||
.AsNoTracking()
|
||||
.Include(ps => ps!.Service)
|
||||
.Where(ps => ps.ProjectId == projectId && ps.TenantId == tenantId).ToListAsync();
|
||||
});
|
||||
|
||||
await Task.WhenAll(projectTask, tenantTask, projectServiceTask);
|
||||
|
||||
var project = projectTask.Result;
|
||||
var tenant = tenantTask.Result;
|
||||
var projectService = projectServiceTask.Result;
|
||||
|
||||
if (project == null || tenant == null)
|
||||
{
|
||||
_logger.LogWarning("Project {ProjectId} not found in database for tenant {TenantId}", projectId, tenantId);
|
||||
return ApiResponse<object>.ErrorResponse("Project not found", "Project not found", 404);
|
||||
}
|
||||
|
||||
// Check if the logged in employee has permission to access the project
|
||||
var hasPermission = await permissionService.HasProjectPermission(loggedInEmployee, projectId);
|
||||
if (!hasPermission)
|
||||
{
|
||||
_logger.LogWarning("Access denied for user {UserId} on project {ProjectId}", loggedInEmployee.Id, projectId);
|
||||
return ApiResponse<object>.ErrorResponse("Access Denied", "You do not have permission to access this project.", 403);
|
||||
}
|
||||
|
||||
// Fetch all project-organization mappings with related service and organization data
|
||||
var projectOrgMappingsQuery = _context.ProjectOrgMappings
|
||||
.AsNoTracking()
|
||||
.Include(po => po.ProjectService)
|
||||
.ThenInclude(ps => ps!.Service)
|
||||
.Include(po => po.AssignedBy)
|
||||
.Include(po => po.OrganizationType)
|
||||
.Include(po => po.Organization)
|
||||
.Where(po => po.ProjectService != null
|
||||
&& po.ProjectService.ProjectId == projectId
|
||||
&& po.TenantId == tenantId);
|
||||
|
||||
if (loggedInEmployee.OrganizationId != project.PMCId && loggedInEmployee.OrganizationId != project.PromoterId && loggedInEmployee.OrganizationId != tenant.OrganizationId)
|
||||
{
|
||||
projectOrgMappingsQuery = projectOrgMappingsQuery.Where(po => po.ParentOrganizationId == loggedInEmployee.OrganizationId || po.OrganizationId == loggedInEmployee.OrganizationId);
|
||||
}
|
||||
|
||||
var projectOrgMappings = await projectOrgMappingsQuery
|
||||
.Distinct()
|
||||
.ToListAsync();
|
||||
var organizations = projectOrgMappings.Select(po => po.Organization!).ToList();
|
||||
|
||||
if (loggedInEmployee.OrganizationId == project.PMCId || loggedInEmployee.OrganizationId == project.PromoterId || loggedInEmployee.OrganizationId == tenant.OrganizationId)
|
||||
{
|
||||
var pmc = project.PMC;
|
||||
var promoter = project.Promoter;
|
||||
var organization = tenant.Organization;
|
||||
|
||||
if (!organizations.Any(r => r.Id == project.PMCId) && pmc != null)
|
||||
{
|
||||
organizations.Add(pmc);
|
||||
}
|
||||
if (!organizations.Any(r => r.Id == project.PromoterId) && promoter != null)
|
||||
{
|
||||
organizations.Add(promoter);
|
||||
}
|
||||
if (!organizations.Any(r => r.Id == tenant.OrganizationId) && organization != null)
|
||||
{
|
||||
organizations.Add(organization);
|
||||
}
|
||||
}
|
||||
|
||||
organizations = organizations.DistinctBy(o => o.Id).ToList();
|
||||
|
||||
// Filter and map the data to the desired view model
|
||||
var response = organizations
|
||||
.Select(o => new ProjectOrganizationVM
|
||||
{
|
||||
Id = o.Id,
|
||||
Name = o.Name,
|
||||
SPRID = 0
|
||||
})
|
||||
.ToList();
|
||||
|
||||
_logger.LogInfo("Fetched {Count} assigned organizations for ProjectId: {ProjectId}", response.Count, projectId);
|
||||
|
||||
return ApiResponse<object>.SuccessResponse(response, "Successfully fetched the list of organizations assigned to the project", 200);
|
||||
@ -2696,6 +2927,7 @@ namespace Marco.Pms.Services.Service
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
#region =================================================================== Helper Functions ===================================================================
|
||||
@ -2713,9 +2945,14 @@ namespace Marco.Pms.Services.Service
|
||||
public async Task<List<ProjectAllocation>> GetTeamByProject(Guid TenantId, Guid ProjectId, Guid? OrganizationId, bool IncludeInactive)
|
||||
{
|
||||
var projectAllocationQuery = _context.ProjectAllocations
|
||||
.Include(pa => pa.Project)
|
||||
.Include(pa => pa.Employee)
|
||||
.ThenInclude(e => e!.Organization)
|
||||
.Where(pa => pa.TenantId == TenantId && pa.ProjectId == ProjectId);
|
||||
.Include(pa => pa.Employee)
|
||||
.ThenInclude(e => e!.JobRole)
|
||||
.Where(pa => pa.TenantId == TenantId &&
|
||||
pa.ProjectId == ProjectId && pa.Project != null &&
|
||||
pa.Employee != null && pa.Employee.Organization != null && pa.Employee.JobRole != null);
|
||||
if (!IncludeInactive)
|
||||
{
|
||||
projectAllocationQuery = projectAllocationQuery.Where(pa => pa.IsActive);
|
||||
|
@ -49,6 +49,7 @@ namespace Marco.Pms.Services.Service.ServiceInterfaces
|
||||
Task<ApiResponse<object>> DeassignServiceToProjectAsync(DeassignServiceDto model, Guid tenantId, Employee loggedInEmployee);
|
||||
|
||||
Task<ApiResponse<object>> GetAssignedOrganizationsToProjectAsync(Guid projectId, Guid tenantId, Employee loggedInEmployee);
|
||||
Task<ApiResponse<object>> GetAssignedOrganizationsToProjectForDropdownAsync(Guid projectId, Guid tenantId, Employee loggedInEmployee);
|
||||
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user