Merge pull request 'Firebase_Implementation' (#135) from Firebase_Implementation into main
Reviewed-on: #135
This commit is contained in:
commit
d0745e8688
@ -109,6 +109,8 @@ namespace Marco.Pms.DataAccess.Data
|
|||||||
public DbSet<StatusPermissionMapping> StatusPermissionMapping { get; set; }
|
public DbSet<StatusPermissionMapping> StatusPermissionMapping { get; set; }
|
||||||
public DbSet<ExpensesStatusMapping> ExpensesStatusMapping { get; set; }
|
public DbSet<ExpensesStatusMapping> ExpensesStatusMapping { get; set; }
|
||||||
|
|
||||||
|
public DbSet<FCMTokenMapping> FCMTokenMappings { get; set; }
|
||||||
|
|
||||||
public DbSet<EntityTypeMaster> EntityTypeMasters { get; set; }
|
public DbSet<EntityTypeMaster> EntityTypeMasters { get; set; }
|
||||||
public DbSet<DocumentTypeMaster> DocumentTypeMasters { get; set; }
|
public DbSet<DocumentTypeMaster> DocumentTypeMasters { get; set; }
|
||||||
public DbSet<DocumentCategoryMaster> DocumentCategoryMasters { get; set; }
|
public DbSet<DocumentCategoryMaster> DocumentCategoryMasters { get; set; }
|
||||||
@ -626,6 +628,7 @@ namespace Marco.Pms.DataAccess.Data
|
|||||||
Description = "Materials, equipment and supplies purchased for site operations.",
|
Description = "Materials, equipment and supplies purchased for site operations.",
|
||||||
NoOfPersonsRequired = false,
|
NoOfPersonsRequired = false,
|
||||||
IsActive = true,
|
IsActive = true,
|
||||||
|
IsAttachmentRequried = true,
|
||||||
TenantId = Guid.Parse("b3466e83-7e11-464c-b93a-daf047838b26")
|
TenantId = Guid.Parse("b3466e83-7e11-464c-b93a-daf047838b26")
|
||||||
},
|
},
|
||||||
new ExpensesTypeMaster
|
new ExpensesTypeMaster
|
||||||
@ -635,6 +638,7 @@ namespace Marco.Pms.DataAccess.Data
|
|||||||
Description = "Vehicle fuel, logistics services and delivery of goods or personnel.",
|
Description = "Vehicle fuel, logistics services and delivery of goods or personnel.",
|
||||||
NoOfPersonsRequired = false,
|
NoOfPersonsRequired = false,
|
||||||
IsActive = true,
|
IsActive = true,
|
||||||
|
IsAttachmentRequried = false,
|
||||||
TenantId = Guid.Parse("b3466e83-7e11-464c-b93a-daf047838b26")
|
TenantId = Guid.Parse("b3466e83-7e11-464c-b93a-daf047838b26")
|
||||||
},
|
},
|
||||||
new ExpensesTypeMaster
|
new ExpensesTypeMaster
|
||||||
@ -644,6 +648,7 @@ namespace Marco.Pms.DataAccess.Data
|
|||||||
Description = "Delivery of personnel.",
|
Description = "Delivery of personnel.",
|
||||||
NoOfPersonsRequired = true,
|
NoOfPersonsRequired = true,
|
||||||
IsActive = true,
|
IsActive = true,
|
||||||
|
IsAttachmentRequried = false,
|
||||||
TenantId = Guid.Parse("b3466e83-7e11-464c-b93a-daf047838b26")
|
TenantId = Guid.Parse("b3466e83-7e11-464c-b93a-daf047838b26")
|
||||||
},
|
},
|
||||||
new ExpensesTypeMaster
|
new ExpensesTypeMaster
|
||||||
@ -653,6 +658,7 @@ namespace Marco.Pms.DataAccess.Data
|
|||||||
Description = "Site setup costs including equipment deployment and temporary infrastructure.",
|
Description = "Site setup costs including equipment deployment and temporary infrastructure.",
|
||||||
NoOfPersonsRequired = false,
|
NoOfPersonsRequired = false,
|
||||||
IsActive = true,
|
IsActive = true,
|
||||||
|
IsAttachmentRequried = true,
|
||||||
TenantId = Guid.Parse("b3466e83-7e11-464c-b93a-daf047838b26")
|
TenantId = Guid.Parse("b3466e83-7e11-464c-b93a-daf047838b26")
|
||||||
},
|
},
|
||||||
new ExpensesTypeMaster
|
new ExpensesTypeMaster
|
||||||
@ -662,6 +668,7 @@ namespace Marco.Pms.DataAccess.Data
|
|||||||
Description = " Worker amenities like snacks, meals, safety gear, accommodation, medical support etc.",
|
Description = " Worker amenities like snacks, meals, safety gear, accommodation, medical support etc.",
|
||||||
NoOfPersonsRequired = true,
|
NoOfPersonsRequired = true,
|
||||||
IsActive = true,
|
IsActive = true,
|
||||||
|
IsAttachmentRequried = true,
|
||||||
TenantId = Guid.Parse("b3466e83-7e11-464c-b93a-daf047838b26")
|
TenantId = Guid.Parse("b3466e83-7e11-464c-b93a-daf047838b26")
|
||||||
},
|
},
|
||||||
new ExpensesTypeMaster
|
new ExpensesTypeMaster
|
||||||
@ -671,6 +678,7 @@ namespace Marco.Pms.DataAccess.Data
|
|||||||
Description = "Machinery servicing, electricity, water, and temporary office needs.",
|
Description = "Machinery servicing, electricity, water, and temporary office needs.",
|
||||||
NoOfPersonsRequired = false,
|
NoOfPersonsRequired = false,
|
||||||
IsActive = true,
|
IsActive = true,
|
||||||
|
IsAttachmentRequried = true,
|
||||||
TenantId = Guid.Parse("b3466e83-7e11-464c-b93a-daf047838b26")
|
TenantId = Guid.Parse("b3466e83-7e11-464c-b93a-daf047838b26")
|
||||||
},
|
},
|
||||||
new ExpensesTypeMaster
|
new ExpensesTypeMaster
|
||||||
@ -680,6 +688,7 @@ namespace Marco.Pms.DataAccess.Data
|
|||||||
Description = "Scheduled payments for external services or goods.",
|
Description = "Scheduled payments for external services or goods.",
|
||||||
NoOfPersonsRequired = false,
|
NoOfPersonsRequired = false,
|
||||||
IsActive = true,
|
IsActive = true,
|
||||||
|
IsAttachmentRequried = true,
|
||||||
TenantId = Guid.Parse("b3466e83-7e11-464c-b93a-daf047838b26")
|
TenantId = Guid.Parse("b3466e83-7e11-464c-b93a-daf047838b26")
|
||||||
},
|
},
|
||||||
new ExpensesTypeMaster
|
new ExpensesTypeMaster
|
||||||
@ -689,6 +698,7 @@ namespace Marco.Pms.DataAccess.Data
|
|||||||
Description = "Government fees, insurance, inspections and safety-related expenditures.",
|
Description = "Government fees, insurance, inspections and safety-related expenditures.",
|
||||||
NoOfPersonsRequired = false,
|
NoOfPersonsRequired = false,
|
||||||
IsActive = true,
|
IsActive = true,
|
||||||
|
IsAttachmentRequried = true,
|
||||||
TenantId = Guid.Parse("b3466e83-7e11-464c-b93a-daf047838b26")
|
TenantId = Guid.Parse("b3466e83-7e11-464c-b93a-daf047838b26")
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
4471
Marco.Pms.DataAccess/Migrations/20250813060211_Added_FCMTokenMApping_Table.Designer.cs
generated
Normal file
4471
Marco.Pms.DataAccess/Migrations/20250813060211_Added_FCMTokenMApping_Table.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,49 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Marco.Pms.DataAccess.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class Added_FCMTokenMApping_Table : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "FCMTokenMappings",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
|
||||||
|
EmployeeId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
|
||||||
|
FcmToken = table.Column<string>(type: "longtext", nullable: false)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
TenantId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci")
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_FCMTokenMappings", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_FCMTokenMappings_Tenants_TenantId",
|
||||||
|
column: x => x.TenantId,
|
||||||
|
principalTable: "Tenants",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
})
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_FCMTokenMappings_TenantId",
|
||||||
|
table: "FCMTokenMappings",
|
||||||
|
column: "TenantId");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "FCMTokenMappings");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
4474
Marco.Pms.DataAccess/Migrations/20250820110719_Added_Expriy_Date_In_FCMMapping_Table.Designer.cs
generated
Normal file
4474
Marco.Pms.DataAccess/Migrations/20250820110719_Added_Expriy_Date_In_FCMMapping_Table.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,30 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Marco.Pms.DataAccess.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class Added_Expriy_Date_In_FCMMapping_Table : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<DateTime>(
|
||||||
|
name: "ExpiredAt",
|
||||||
|
table: "FCMTokenMappings",
|
||||||
|
type: "datetime(6)",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "ExpiredAt",
|
||||||
|
table: "FCMTokenMappings");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because one or more lines are too long
@ -0,0 +1,268 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Marco.Pms.DataAccess.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class Added_IsAttachmentRequried_Parameter_In_ExpensesTypeMaster_Table : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<bool>(
|
||||||
|
name: "IsAttachmentRequried",
|
||||||
|
table: "ExpensesTypeMaster",
|
||||||
|
type: "tinyint(1)",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: false);
|
||||||
|
|
||||||
|
migrationBuilder.UpdateData(
|
||||||
|
table: "DocumentCategoryMasters",
|
||||||
|
keyColumn: "Id",
|
||||||
|
keyValue: new Guid("2d9fb9cf-db53-476b-a452-492e88e2b51f"),
|
||||||
|
column: "CreatedAt",
|
||||||
|
value: new DateTime(2025, 9, 12, 7, 6, 13, 429, DateTimeKind.Utc).AddTicks(3323));
|
||||||
|
|
||||||
|
migrationBuilder.UpdateData(
|
||||||
|
table: "DocumentCategoryMasters",
|
||||||
|
keyColumn: "Id",
|
||||||
|
keyValue: new Guid("cfbff269-072b-477a-b48b-72cdc57dd1d3"),
|
||||||
|
column: "CreatedAt",
|
||||||
|
value: new DateTime(2025, 9, 12, 7, 6, 13, 429, DateTimeKind.Utc).AddTicks(3316));
|
||||||
|
|
||||||
|
migrationBuilder.UpdateData(
|
||||||
|
table: "DocumentTypeMasters",
|
||||||
|
keyColumn: "Id",
|
||||||
|
keyValue: new Guid("07ca7182-9ac0-4407-b988-59901170cb86"),
|
||||||
|
column: "CreatedAt",
|
||||||
|
value: new DateTime(2025, 9, 3, 10, 46, 49, 955, DateTimeKind.Utc));
|
||||||
|
|
||||||
|
migrationBuilder.UpdateData(
|
||||||
|
table: "DocumentTypeMasters",
|
||||||
|
keyColumn: "Id",
|
||||||
|
keyValue: new Guid("16c40b80-c207-4a0c-a4d3-381414afe35a"),
|
||||||
|
column: "CreatedAt",
|
||||||
|
value: new DateTime(2025, 9, 3, 10, 46, 49, 955, DateTimeKind.Utc));
|
||||||
|
|
||||||
|
migrationBuilder.UpdateData(
|
||||||
|
table: "DocumentTypeMasters",
|
||||||
|
keyColumn: "Id",
|
||||||
|
keyValue: new Guid("260abd7e-c96d-4ae4-a29b-9b5bb5d24ebd"),
|
||||||
|
column: "CreatedAt",
|
||||||
|
value: new DateTime(2025, 9, 3, 10, 46, 49, 955, DateTimeKind.Utc));
|
||||||
|
|
||||||
|
migrationBuilder.UpdateData(
|
||||||
|
table: "DocumentTypeMasters",
|
||||||
|
keyColumn: "Id",
|
||||||
|
keyValue: new Guid("2d1d7441-46a8-425e-9395-94d0956f8e91"),
|
||||||
|
column: "CreatedAt",
|
||||||
|
value: new DateTime(2025, 9, 3, 10, 46, 49, 955, DateTimeKind.Utc));
|
||||||
|
|
||||||
|
migrationBuilder.UpdateData(
|
||||||
|
table: "DocumentTypeMasters",
|
||||||
|
keyColumn: "Id",
|
||||||
|
keyValue: new Guid("336225ac-67f3-4e14-ba7a-8fad03cf2832"),
|
||||||
|
column: "CreatedAt",
|
||||||
|
value: new DateTime(2025, 9, 3, 10, 46, 49, 955, DateTimeKind.Utc));
|
||||||
|
|
||||||
|
migrationBuilder.UpdateData(
|
||||||
|
table: "DocumentTypeMasters",
|
||||||
|
keyColumn: "Id",
|
||||||
|
keyValue: new Guid("5668de00-5d84-47f7-b9b5-7fefd1219f05"),
|
||||||
|
column: "CreatedAt",
|
||||||
|
value: new DateTime(2025, 9, 3, 10, 46, 49, 955, DateTimeKind.Utc));
|
||||||
|
|
||||||
|
migrationBuilder.UpdateData(
|
||||||
|
table: "DocumentTypeMasters",
|
||||||
|
keyColumn: "Id",
|
||||||
|
keyValue: new Guid("6344393b-9bb1-45f8-b620-9f6e279d012c"),
|
||||||
|
column: "CreatedAt",
|
||||||
|
value: new DateTime(2025, 9, 3, 10, 46, 49, 955, DateTimeKind.Utc));
|
||||||
|
|
||||||
|
migrationBuilder.UpdateData(
|
||||||
|
table: "DocumentTypeMasters",
|
||||||
|
keyColumn: "Id",
|
||||||
|
keyValue: new Guid("7cc41c91-23cb-442b-badd-f932138d149f"),
|
||||||
|
column: "CreatedAt",
|
||||||
|
value: new DateTime(2025, 9, 3, 10, 46, 49, 955, DateTimeKind.Utc));
|
||||||
|
|
||||||
|
migrationBuilder.UpdateData(
|
||||||
|
table: "DocumentTypeMasters",
|
||||||
|
keyColumn: "Id",
|
||||||
|
keyValue: new Guid("846e89a9-5735-45ec-a21d-c97f85a94ada"),
|
||||||
|
column: "CreatedAt",
|
||||||
|
value: new DateTime(2025, 9, 3, 10, 46, 49, 955, DateTimeKind.Utc));
|
||||||
|
|
||||||
|
migrationBuilder.UpdateData(
|
||||||
|
table: "DocumentTypeMasters",
|
||||||
|
keyColumn: "Id",
|
||||||
|
keyValue: new Guid("a1a190ba-c4a8-432f-b26d-1231ca1d44bc"),
|
||||||
|
column: "CreatedAt",
|
||||||
|
value: new DateTime(2025, 9, 3, 10, 46, 49, 955, DateTimeKind.Utc));
|
||||||
|
|
||||||
|
migrationBuilder.UpdateData(
|
||||||
|
table: "DocumentTypeMasters",
|
||||||
|
keyColumn: "Id",
|
||||||
|
keyValue: new Guid("f76d8215-d399-4f0e-b414-12e427f50be3"),
|
||||||
|
column: "CreatedAt",
|
||||||
|
value: new DateTime(2025, 9, 3, 10, 46, 49, 955, DateTimeKind.Utc));
|
||||||
|
|
||||||
|
migrationBuilder.UpdateData(
|
||||||
|
table: "ExpensesTypeMaster",
|
||||||
|
keyColumn: "Id",
|
||||||
|
keyValue: new Guid("1e2d697a-76b4-4be8-bc66-87144561a1a0"),
|
||||||
|
column: "IsAttachmentRequried",
|
||||||
|
value: true);
|
||||||
|
|
||||||
|
migrationBuilder.UpdateData(
|
||||||
|
table: "ExpensesTypeMaster",
|
||||||
|
keyColumn: "Id",
|
||||||
|
keyValue: new Guid("2de53163-0dbd-404b-8e60-1b02e6b4886a"),
|
||||||
|
column: "IsAttachmentRequried",
|
||||||
|
value: false);
|
||||||
|
|
||||||
|
migrationBuilder.UpdateData(
|
||||||
|
table: "ExpensesTypeMaster",
|
||||||
|
keyColumn: "Id",
|
||||||
|
keyValue: new Guid("4842fa61-64eb-4241-aebd-8282065af9f9"),
|
||||||
|
column: "IsAttachmentRequried",
|
||||||
|
value: true);
|
||||||
|
|
||||||
|
migrationBuilder.UpdateData(
|
||||||
|
table: "ExpensesTypeMaster",
|
||||||
|
keyColumn: "Id",
|
||||||
|
keyValue: new Guid("52484820-1b54-4865-8f0f-baa2b1d339b9"),
|
||||||
|
column: "IsAttachmentRequried",
|
||||||
|
value: true);
|
||||||
|
|
||||||
|
migrationBuilder.UpdateData(
|
||||||
|
table: "ExpensesTypeMaster",
|
||||||
|
keyColumn: "Id",
|
||||||
|
keyValue: new Guid("5e0c6227-d49d-41ff-9f1f-781f0aee2469"),
|
||||||
|
column: "IsAttachmentRequried",
|
||||||
|
value: true);
|
||||||
|
|
||||||
|
migrationBuilder.UpdateData(
|
||||||
|
table: "ExpensesTypeMaster",
|
||||||
|
keyColumn: "Id",
|
||||||
|
keyValue: new Guid("77013784-9324-4d8b-bd36-d6f928e68942"),
|
||||||
|
column: "IsAttachmentRequried",
|
||||||
|
value: true);
|
||||||
|
|
||||||
|
migrationBuilder.UpdateData(
|
||||||
|
table: "ExpensesTypeMaster",
|
||||||
|
keyColumn: "Id",
|
||||||
|
keyValue: new Guid("dd120bc4-ab0a-45ba-8450-5cd45ff221ca"),
|
||||||
|
column: "IsAttachmentRequried",
|
||||||
|
value: false);
|
||||||
|
|
||||||
|
migrationBuilder.UpdateData(
|
||||||
|
table: "ExpensesTypeMaster",
|
||||||
|
keyColumn: "Id",
|
||||||
|
keyValue: new Guid("fc59eb90-98ea-481c-b421-54bfa9e42d8f"),
|
||||||
|
column: "IsAttachmentRequried",
|
||||||
|
value: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "IsAttachmentRequried",
|
||||||
|
table: "ExpensesTypeMaster");
|
||||||
|
|
||||||
|
migrationBuilder.UpdateData(
|
||||||
|
table: "DocumentCategoryMasters",
|
||||||
|
keyColumn: "Id",
|
||||||
|
keyValue: new Guid("2d9fb9cf-db53-476b-a452-492e88e2b51f"),
|
||||||
|
column: "CreatedAt",
|
||||||
|
value: new DateTime(2025, 9, 3, 10, 46, 49, 955, DateTimeKind.Utc).AddTicks(6233));
|
||||||
|
|
||||||
|
migrationBuilder.UpdateData(
|
||||||
|
table: "DocumentCategoryMasters",
|
||||||
|
keyColumn: "Id",
|
||||||
|
keyValue: new Guid("cfbff269-072b-477a-b48b-72cdc57dd1d3"),
|
||||||
|
column: "CreatedAt",
|
||||||
|
value: new DateTime(2025, 9, 3, 10, 46, 49, 955, DateTimeKind.Utc).AddTicks(6226));
|
||||||
|
|
||||||
|
migrationBuilder.UpdateData(
|
||||||
|
table: "DocumentTypeMasters",
|
||||||
|
keyColumn: "Id",
|
||||||
|
keyValue: new Guid("07ca7182-9ac0-4407-b988-59901170cb86"),
|
||||||
|
column: "CreatedAt",
|
||||||
|
value: new DateTime(2025, 9, 3, 10, 46, 49, 955, DateTimeKind.Utc).AddTicks(6307));
|
||||||
|
|
||||||
|
migrationBuilder.UpdateData(
|
||||||
|
table: "DocumentTypeMasters",
|
||||||
|
keyColumn: "Id",
|
||||||
|
keyValue: new Guid("16c40b80-c207-4a0c-a4d3-381414afe35a"),
|
||||||
|
column: "CreatedAt",
|
||||||
|
value: new DateTime(2025, 9, 3, 10, 46, 49, 955, DateTimeKind.Utc).AddTicks(6290));
|
||||||
|
|
||||||
|
migrationBuilder.UpdateData(
|
||||||
|
table: "DocumentTypeMasters",
|
||||||
|
keyColumn: "Id",
|
||||||
|
keyValue: new Guid("260abd7e-c96d-4ae4-a29b-9b5bb5d24ebd"),
|
||||||
|
column: "CreatedAt",
|
||||||
|
value: new DateTime(2025, 9, 3, 10, 46, 49, 955, DateTimeKind.Utc).AddTicks(6298));
|
||||||
|
|
||||||
|
migrationBuilder.UpdateData(
|
||||||
|
table: "DocumentTypeMasters",
|
||||||
|
keyColumn: "Id",
|
||||||
|
keyValue: new Guid("2d1d7441-46a8-425e-9395-94d0956f8e91"),
|
||||||
|
column: "CreatedAt",
|
||||||
|
value: new DateTime(2025, 9, 3, 10, 46, 49, 955, DateTimeKind.Utc).AddTicks(6286));
|
||||||
|
|
||||||
|
migrationBuilder.UpdateData(
|
||||||
|
table: "DocumentTypeMasters",
|
||||||
|
keyColumn: "Id",
|
||||||
|
keyValue: new Guid("336225ac-67f3-4e14-ba7a-8fad03cf2832"),
|
||||||
|
column: "CreatedAt",
|
||||||
|
value: new DateTime(2025, 9, 3, 10, 46, 49, 955, DateTimeKind.Utc).AddTicks(6275));
|
||||||
|
|
||||||
|
migrationBuilder.UpdateData(
|
||||||
|
table: "DocumentTypeMasters",
|
||||||
|
keyColumn: "Id",
|
||||||
|
keyValue: new Guid("5668de00-5d84-47f7-b9b5-7fefd1219f05"),
|
||||||
|
column: "CreatedAt",
|
||||||
|
value: new DateTime(2025, 9, 3, 10, 46, 49, 955, DateTimeKind.Utc).AddTicks(6319));
|
||||||
|
|
||||||
|
migrationBuilder.UpdateData(
|
||||||
|
table: "DocumentTypeMasters",
|
||||||
|
keyColumn: "Id",
|
||||||
|
keyValue: new Guid("6344393b-9bb1-45f8-b620-9f6e279d012c"),
|
||||||
|
column: "CreatedAt",
|
||||||
|
value: new DateTime(2025, 9, 3, 10, 46, 49, 955, DateTimeKind.Utc).AddTicks(6282));
|
||||||
|
|
||||||
|
migrationBuilder.UpdateData(
|
||||||
|
table: "DocumentTypeMasters",
|
||||||
|
keyColumn: "Id",
|
||||||
|
keyValue: new Guid("7cc41c91-23cb-442b-badd-f932138d149f"),
|
||||||
|
column: "CreatedAt",
|
||||||
|
value: new DateTime(2025, 9, 3, 10, 46, 49, 955, DateTimeKind.Utc).AddTicks(6314));
|
||||||
|
|
||||||
|
migrationBuilder.UpdateData(
|
||||||
|
table: "DocumentTypeMasters",
|
||||||
|
keyColumn: "Id",
|
||||||
|
keyValue: new Guid("846e89a9-5735-45ec-a21d-c97f85a94ada"),
|
||||||
|
column: "CreatedAt",
|
||||||
|
value: new DateTime(2025, 9, 3, 10, 46, 49, 955, DateTimeKind.Utc).AddTicks(6311));
|
||||||
|
|
||||||
|
migrationBuilder.UpdateData(
|
||||||
|
table: "DocumentTypeMasters",
|
||||||
|
keyColumn: "Id",
|
||||||
|
keyValue: new Guid("a1a190ba-c4a8-432f-b26d-1231ca1d44bc"),
|
||||||
|
column: "CreatedAt",
|
||||||
|
value: new DateTime(2025, 9, 3, 10, 46, 49, 955, DateTimeKind.Utc).AddTicks(6302));
|
||||||
|
|
||||||
|
migrationBuilder.UpdateData(
|
||||||
|
table: "DocumentTypeMasters",
|
||||||
|
keyColumn: "Id",
|
||||||
|
keyValue: new Guid("f76d8215-d399-4f0e-b414-12e427f50be3"),
|
||||||
|
column: "CreatedAt",
|
||||||
|
value: new DateTime(2025, 9, 3, 10, 46, 49, 955, DateTimeKind.Utc).AddTicks(6295));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -931,7 +931,7 @@ namespace Marco.Pms.DataAccess.Migrations
|
|||||||
new
|
new
|
||||||
{
|
{
|
||||||
Id = new Guid("cfbff269-072b-477a-b48b-72cdc57dd1d3"),
|
Id = new Guid("cfbff269-072b-477a-b48b-72cdc57dd1d3"),
|
||||||
CreatedAt = new DateTime(2025, 9, 3, 10, 46, 49, 955, DateTimeKind.Utc).AddTicks(6226),
|
CreatedAt = new DateTime(2025, 9, 12, 7, 6, 13, 429, DateTimeKind.Utc).AddTicks(3316),
|
||||||
Description = "Project documents are formal records that outline the plans, progress, and details necessary to execute and manage a project effectively.",
|
Description = "Project documents are formal records that outline the plans, progress, and details necessary to execute and manage a project effectively.",
|
||||||
EntityTypeId = new Guid("c8fe7115-aa27-43bc-99f4-7b05fabe436e"),
|
EntityTypeId = new Guid("c8fe7115-aa27-43bc-99f4-7b05fabe436e"),
|
||||||
Name = "Project Documents",
|
Name = "Project Documents",
|
||||||
@ -940,7 +940,7 @@ namespace Marco.Pms.DataAccess.Migrations
|
|||||||
new
|
new
|
||||||
{
|
{
|
||||||
Id = new Guid("2d9fb9cf-db53-476b-a452-492e88e2b51f"),
|
Id = new Guid("2d9fb9cf-db53-476b-a452-492e88e2b51f"),
|
||||||
CreatedAt = new DateTime(2025, 9, 3, 10, 46, 49, 955, DateTimeKind.Utc).AddTicks(6233),
|
CreatedAt = new DateTime(2025, 9, 12, 7, 6, 13, 429, DateTimeKind.Utc).AddTicks(3323),
|
||||||
Description = "Employment details along with legal IDs like passports or driver’s licenses to verify identity and work authorization.",
|
Description = "Employment details along with legal IDs like passports or driver’s licenses to verify identity and work authorization.",
|
||||||
EntityTypeId = new Guid("dbb9555a-7a0c-40f2-a9ed-f0463f1ceed7"),
|
EntityTypeId = new Guid("dbb9555a-7a0c-40f2-a9ed-f0463f1ceed7"),
|
||||||
Name = "Employee Documents",
|
Name = "Employee Documents",
|
||||||
@ -1026,7 +1026,7 @@ namespace Marco.Pms.DataAccess.Migrations
|
|||||||
{
|
{
|
||||||
Id = new Guid("336225ac-67f3-4e14-ba7a-8fad03cf2832"),
|
Id = new Guid("336225ac-67f3-4e14-ba7a-8fad03cf2832"),
|
||||||
AllowedContentType = "application/pdf,image/jpeg",
|
AllowedContentType = "application/pdf,image/jpeg",
|
||||||
CreatedAt = new DateTime(2025, 9, 3, 10, 46, 49, 955, DateTimeKind.Utc).AddTicks(6275),
|
CreatedAt = new DateTime(2025, 9, 3, 10, 46, 49, 955, DateTimeKind.Utc),
|
||||||
DocumentCategoryId = new Guid("2d9fb9cf-db53-476b-a452-492e88e2b51f"),
|
DocumentCategoryId = new Guid("2d9fb9cf-db53-476b-a452-492e88e2b51f"),
|
||||||
IsActive = true,
|
IsActive = true,
|
||||||
IsMandatory = true,
|
IsMandatory = true,
|
||||||
@ -1041,7 +1041,7 @@ namespace Marco.Pms.DataAccess.Migrations
|
|||||||
{
|
{
|
||||||
Id = new Guid("6344393b-9bb1-45f8-b620-9f6e279d012c"),
|
Id = new Guid("6344393b-9bb1-45f8-b620-9f6e279d012c"),
|
||||||
AllowedContentType = "application/pdf,image/jpeg",
|
AllowedContentType = "application/pdf,image/jpeg",
|
||||||
CreatedAt = new DateTime(2025, 9, 3, 10, 46, 49, 955, DateTimeKind.Utc).AddTicks(6282),
|
CreatedAt = new DateTime(2025, 9, 3, 10, 46, 49, 955, DateTimeKind.Utc),
|
||||||
DocumentCategoryId = new Guid("2d9fb9cf-db53-476b-a452-492e88e2b51f"),
|
DocumentCategoryId = new Guid("2d9fb9cf-db53-476b-a452-492e88e2b51f"),
|
||||||
IsActive = true,
|
IsActive = true,
|
||||||
IsMandatory = true,
|
IsMandatory = true,
|
||||||
@ -1056,7 +1056,7 @@ namespace Marco.Pms.DataAccess.Migrations
|
|||||||
{
|
{
|
||||||
Id = new Guid("2d1d7441-46a8-425e-9395-94d0956f8e91"),
|
Id = new Guid("2d1d7441-46a8-425e-9395-94d0956f8e91"),
|
||||||
AllowedContentType = "application/pdf,image/jpeg",
|
AllowedContentType = "application/pdf,image/jpeg",
|
||||||
CreatedAt = new DateTime(2025, 9, 3, 10, 46, 49, 955, DateTimeKind.Utc).AddTicks(6286),
|
CreatedAt = new DateTime(2025, 9, 3, 10, 46, 49, 955, DateTimeKind.Utc),
|
||||||
DocumentCategoryId = new Guid("2d9fb9cf-db53-476b-a452-492e88e2b51f"),
|
DocumentCategoryId = new Guid("2d9fb9cf-db53-476b-a452-492e88e2b51f"),
|
||||||
IsActive = true,
|
IsActive = true,
|
||||||
IsMandatory = true,
|
IsMandatory = true,
|
||||||
@ -1071,7 +1071,7 @@ namespace Marco.Pms.DataAccess.Migrations
|
|||||||
{
|
{
|
||||||
Id = new Guid("16c40b80-c207-4a0c-a4d3-381414afe35a"),
|
Id = new Guid("16c40b80-c207-4a0c-a4d3-381414afe35a"),
|
||||||
AllowedContentType = "application/pdf,image/jpeg",
|
AllowedContentType = "application/pdf,image/jpeg",
|
||||||
CreatedAt = new DateTime(2025, 9, 3, 10, 46, 49, 955, DateTimeKind.Utc).AddTicks(6290),
|
CreatedAt = new DateTime(2025, 9, 3, 10, 46, 49, 955, DateTimeKind.Utc),
|
||||||
DocumentCategoryId = new Guid("2d9fb9cf-db53-476b-a452-492e88e2b51f"),
|
DocumentCategoryId = new Guid("2d9fb9cf-db53-476b-a452-492e88e2b51f"),
|
||||||
IsActive = true,
|
IsActive = true,
|
||||||
IsMandatory = true,
|
IsMandatory = true,
|
||||||
@ -1086,7 +1086,7 @@ namespace Marco.Pms.DataAccess.Migrations
|
|||||||
{
|
{
|
||||||
Id = new Guid("f76d8215-d399-4f0e-b414-12e427f50be3"),
|
Id = new Guid("f76d8215-d399-4f0e-b414-12e427f50be3"),
|
||||||
AllowedContentType = "application/pdf,image/jpeg",
|
AllowedContentType = "application/pdf,image/jpeg",
|
||||||
CreatedAt = new DateTime(2025, 9, 3, 10, 46, 49, 955, DateTimeKind.Utc).AddTicks(6295),
|
CreatedAt = new DateTime(2025, 9, 3, 10, 46, 49, 955, DateTimeKind.Utc),
|
||||||
DocumentCategoryId = new Guid("2d9fb9cf-db53-476b-a452-492e88e2b51f"),
|
DocumentCategoryId = new Guid("2d9fb9cf-db53-476b-a452-492e88e2b51f"),
|
||||||
IsActive = true,
|
IsActive = true,
|
||||||
IsMandatory = true,
|
IsMandatory = true,
|
||||||
@ -1101,7 +1101,7 @@ namespace Marco.Pms.DataAccess.Migrations
|
|||||||
{
|
{
|
||||||
Id = new Guid("260abd7e-c96d-4ae4-a29b-9b5bb5d24ebd"),
|
Id = new Guid("260abd7e-c96d-4ae4-a29b-9b5bb5d24ebd"),
|
||||||
AllowedContentType = "application/pdf,application/msword,application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
AllowedContentType = "application/pdf,application/msword,application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
||||||
CreatedAt = new DateTime(2025, 9, 3, 10, 46, 49, 955, DateTimeKind.Utc).AddTicks(6298),
|
CreatedAt = new DateTime(2025, 9, 3, 10, 46, 49, 955, DateTimeKind.Utc),
|
||||||
DocumentCategoryId = new Guid("cfbff269-072b-477a-b48b-72cdc57dd1d3"),
|
DocumentCategoryId = new Guid("cfbff269-072b-477a-b48b-72cdc57dd1d3"),
|
||||||
IsActive = true,
|
IsActive = true,
|
||||||
IsMandatory = false,
|
IsMandatory = false,
|
||||||
@ -1115,7 +1115,7 @@ namespace Marco.Pms.DataAccess.Migrations
|
|||||||
{
|
{
|
||||||
Id = new Guid("a1a190ba-c4a8-432f-b26d-1231ca1d44bc"),
|
Id = new Guid("a1a190ba-c4a8-432f-b26d-1231ca1d44bc"),
|
||||||
AllowedContentType = "application/pdf,application/msword,application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
AllowedContentType = "application/pdf,application/msword,application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
||||||
CreatedAt = new DateTime(2025, 9, 3, 10, 46, 49, 955, DateTimeKind.Utc).AddTicks(6302),
|
CreatedAt = new DateTime(2025, 9, 3, 10, 46, 49, 955, DateTimeKind.Utc),
|
||||||
DocumentCategoryId = new Guid("cfbff269-072b-477a-b48b-72cdc57dd1d3"),
|
DocumentCategoryId = new Guid("cfbff269-072b-477a-b48b-72cdc57dd1d3"),
|
||||||
IsActive = true,
|
IsActive = true,
|
||||||
IsMandatory = false,
|
IsMandatory = false,
|
||||||
@ -1129,7 +1129,7 @@ namespace Marco.Pms.DataAccess.Migrations
|
|||||||
{
|
{
|
||||||
Id = new Guid("07ca7182-9ac0-4407-b988-59901170cb86"),
|
Id = new Guid("07ca7182-9ac0-4407-b988-59901170cb86"),
|
||||||
AllowedContentType = "application/pdf,application/msword,application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
AllowedContentType = "application/pdf,application/msword,application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
||||||
CreatedAt = new DateTime(2025, 9, 3, 10, 46, 49, 955, DateTimeKind.Utc).AddTicks(6307),
|
CreatedAt = new DateTime(2025, 9, 3, 10, 46, 49, 955, DateTimeKind.Utc),
|
||||||
DocumentCategoryId = new Guid("cfbff269-072b-477a-b48b-72cdc57dd1d3"),
|
DocumentCategoryId = new Guid("cfbff269-072b-477a-b48b-72cdc57dd1d3"),
|
||||||
IsActive = true,
|
IsActive = true,
|
||||||
IsMandatory = false,
|
IsMandatory = false,
|
||||||
@ -1143,7 +1143,7 @@ namespace Marco.Pms.DataAccess.Migrations
|
|||||||
{
|
{
|
||||||
Id = new Guid("846e89a9-5735-45ec-a21d-c97f85a94ada"),
|
Id = new Guid("846e89a9-5735-45ec-a21d-c97f85a94ada"),
|
||||||
AllowedContentType = "application/pdf,application/msword,application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
AllowedContentType = "application/pdf,application/msword,application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
||||||
CreatedAt = new DateTime(2025, 9, 3, 10, 46, 49, 955, DateTimeKind.Utc).AddTicks(6311),
|
CreatedAt = new DateTime(2025, 9, 3, 10, 46, 49, 955, DateTimeKind.Utc),
|
||||||
DocumentCategoryId = new Guid("cfbff269-072b-477a-b48b-72cdc57dd1d3"),
|
DocumentCategoryId = new Guid("cfbff269-072b-477a-b48b-72cdc57dd1d3"),
|
||||||
IsActive = true,
|
IsActive = true,
|
||||||
IsMandatory = false,
|
IsMandatory = false,
|
||||||
@ -1157,7 +1157,7 @@ namespace Marco.Pms.DataAccess.Migrations
|
|||||||
{
|
{
|
||||||
Id = new Guid("7cc41c91-23cb-442b-badd-f932138d149f"),
|
Id = new Guid("7cc41c91-23cb-442b-badd-f932138d149f"),
|
||||||
AllowedContentType = "application/pdf,application/msword,application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
AllowedContentType = "application/pdf,application/msword,application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
||||||
CreatedAt = new DateTime(2025, 9, 3, 10, 46, 49, 955, DateTimeKind.Utc).AddTicks(6314),
|
CreatedAt = new DateTime(2025, 9, 3, 10, 46, 49, 955, DateTimeKind.Utc),
|
||||||
DocumentCategoryId = new Guid("cfbff269-072b-477a-b48b-72cdc57dd1d3"),
|
DocumentCategoryId = new Guid("cfbff269-072b-477a-b48b-72cdc57dd1d3"),
|
||||||
IsActive = true,
|
IsActive = true,
|
||||||
IsMandatory = false,
|
IsMandatory = false,
|
||||||
@ -1171,7 +1171,7 @@ namespace Marco.Pms.DataAccess.Migrations
|
|||||||
{
|
{
|
||||||
Id = new Guid("5668de00-5d84-47f7-b9b5-7fefd1219f05"),
|
Id = new Guid("5668de00-5d84-47f7-b9b5-7fefd1219f05"),
|
||||||
AllowedContentType = "application/pdf,image/vnd.dwg,application/acad",
|
AllowedContentType = "application/pdf,image/vnd.dwg,application/acad",
|
||||||
CreatedAt = new DateTime(2025, 9, 3, 10, 46, 49, 955, DateTimeKind.Utc).AddTicks(6319),
|
CreatedAt = new DateTime(2025, 9, 3, 10, 46, 49, 955, DateTimeKind.Utc),
|
||||||
DocumentCategoryId = new Guid("cfbff269-072b-477a-b48b-72cdc57dd1d3"),
|
DocumentCategoryId = new Guid("cfbff269-072b-477a-b48b-72cdc57dd1d3"),
|
||||||
IsActive = true,
|
IsActive = true,
|
||||||
IsMandatory = false,
|
IsMandatory = false,
|
||||||
@ -2606,6 +2606,9 @@ namespace Marco.Pms.DataAccess.Migrations
|
|||||||
b.Property<bool>("IsActive")
|
b.Property<bool>("IsActive")
|
||||||
.HasColumnType("tinyint(1)");
|
.HasColumnType("tinyint(1)");
|
||||||
|
|
||||||
|
b.Property<bool>("IsAttachmentRequried")
|
||||||
|
.HasColumnType("tinyint(1)");
|
||||||
|
|
||||||
b.Property<string>("Name")
|
b.Property<string>("Name")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("longtext");
|
.HasColumnType("longtext");
|
||||||
@ -2628,6 +2631,7 @@ namespace Marco.Pms.DataAccess.Migrations
|
|||||||
Id = new Guid("5e0c6227-d49d-41ff-9f1f-781f0aee2469"),
|
Id = new Guid("5e0c6227-d49d-41ff-9f1f-781f0aee2469"),
|
||||||
Description = "Materials, equipment and supplies purchased for site operations.",
|
Description = "Materials, equipment and supplies purchased for site operations.",
|
||||||
IsActive = true,
|
IsActive = true,
|
||||||
|
IsAttachmentRequried = true,
|
||||||
Name = "Procurement",
|
Name = "Procurement",
|
||||||
NoOfPersonsRequired = false,
|
NoOfPersonsRequired = false,
|
||||||
TenantId = new Guid("b3466e83-7e11-464c-b93a-daf047838b26")
|
TenantId = new Guid("b3466e83-7e11-464c-b93a-daf047838b26")
|
||||||
@ -2637,6 +2641,7 @@ namespace Marco.Pms.DataAccess.Migrations
|
|||||||
Id = new Guid("2de53163-0dbd-404b-8e60-1b02e6b4886a"),
|
Id = new Guid("2de53163-0dbd-404b-8e60-1b02e6b4886a"),
|
||||||
Description = "Vehicle fuel, logistics services and delivery of goods or personnel.",
|
Description = "Vehicle fuel, logistics services and delivery of goods or personnel.",
|
||||||
IsActive = true,
|
IsActive = true,
|
||||||
|
IsAttachmentRequried = false,
|
||||||
Name = "Transport",
|
Name = "Transport",
|
||||||
NoOfPersonsRequired = false,
|
NoOfPersonsRequired = false,
|
||||||
TenantId = new Guid("b3466e83-7e11-464c-b93a-daf047838b26")
|
TenantId = new Guid("b3466e83-7e11-464c-b93a-daf047838b26")
|
||||||
@ -2646,6 +2651,7 @@ namespace Marco.Pms.DataAccess.Migrations
|
|||||||
Id = new Guid("dd120bc4-ab0a-45ba-8450-5cd45ff221ca"),
|
Id = new Guid("dd120bc4-ab0a-45ba-8450-5cd45ff221ca"),
|
||||||
Description = "Delivery of personnel.",
|
Description = "Delivery of personnel.",
|
||||||
IsActive = true,
|
IsActive = true,
|
||||||
|
IsAttachmentRequried = false,
|
||||||
Name = "Travelling",
|
Name = "Travelling",
|
||||||
NoOfPersonsRequired = true,
|
NoOfPersonsRequired = true,
|
||||||
TenantId = new Guid("b3466e83-7e11-464c-b93a-daf047838b26")
|
TenantId = new Guid("b3466e83-7e11-464c-b93a-daf047838b26")
|
||||||
@ -2655,6 +2661,7 @@ namespace Marco.Pms.DataAccess.Migrations
|
|||||||
Id = new Guid("52484820-1b54-4865-8f0f-baa2b1d339b9"),
|
Id = new Guid("52484820-1b54-4865-8f0f-baa2b1d339b9"),
|
||||||
Description = "Site setup costs including equipment deployment and temporary infrastructure.",
|
Description = "Site setup costs including equipment deployment and temporary infrastructure.",
|
||||||
IsActive = true,
|
IsActive = true,
|
||||||
|
IsAttachmentRequried = true,
|
||||||
Name = "Mobilization",
|
Name = "Mobilization",
|
||||||
NoOfPersonsRequired = false,
|
NoOfPersonsRequired = false,
|
||||||
TenantId = new Guid("b3466e83-7e11-464c-b93a-daf047838b26")
|
TenantId = new Guid("b3466e83-7e11-464c-b93a-daf047838b26")
|
||||||
@ -2664,6 +2671,7 @@ namespace Marco.Pms.DataAccess.Migrations
|
|||||||
Id = new Guid("fc59eb90-98ea-481c-b421-54bfa9e42d8f"),
|
Id = new Guid("fc59eb90-98ea-481c-b421-54bfa9e42d8f"),
|
||||||
Description = " Worker amenities like snacks, meals, safety gear, accommodation, medical support etc.",
|
Description = " Worker amenities like snacks, meals, safety gear, accommodation, medical support etc.",
|
||||||
IsActive = true,
|
IsActive = true,
|
||||||
|
IsAttachmentRequried = true,
|
||||||
Name = "Employee Welfare",
|
Name = "Employee Welfare",
|
||||||
NoOfPersonsRequired = true,
|
NoOfPersonsRequired = true,
|
||||||
TenantId = new Guid("b3466e83-7e11-464c-b93a-daf047838b26")
|
TenantId = new Guid("b3466e83-7e11-464c-b93a-daf047838b26")
|
||||||
@ -2673,6 +2681,7 @@ namespace Marco.Pms.DataAccess.Migrations
|
|||||||
Id = new Guid("77013784-9324-4d8b-bd36-d6f928e68942"),
|
Id = new Guid("77013784-9324-4d8b-bd36-d6f928e68942"),
|
||||||
Description = "Machinery servicing, electricity, water, and temporary office needs.",
|
Description = "Machinery servicing, electricity, water, and temporary office needs.",
|
||||||
IsActive = true,
|
IsActive = true,
|
||||||
|
IsAttachmentRequried = true,
|
||||||
Name = "Maintenance & Utilities",
|
Name = "Maintenance & Utilities",
|
||||||
NoOfPersonsRequired = false,
|
NoOfPersonsRequired = false,
|
||||||
TenantId = new Guid("b3466e83-7e11-464c-b93a-daf047838b26")
|
TenantId = new Guid("b3466e83-7e11-464c-b93a-daf047838b26")
|
||||||
@ -2682,6 +2691,7 @@ namespace Marco.Pms.DataAccess.Migrations
|
|||||||
Id = new Guid("1e2d697a-76b4-4be8-bc66-87144561a1a0"),
|
Id = new Guid("1e2d697a-76b4-4be8-bc66-87144561a1a0"),
|
||||||
Description = "Scheduled payments for external services or goods.",
|
Description = "Scheduled payments for external services or goods.",
|
||||||
IsActive = true,
|
IsActive = true,
|
||||||
|
IsAttachmentRequried = true,
|
||||||
Name = "Vendor/Supplier Payments",
|
Name = "Vendor/Supplier Payments",
|
||||||
NoOfPersonsRequired = false,
|
NoOfPersonsRequired = false,
|
||||||
TenantId = new Guid("b3466e83-7e11-464c-b93a-daf047838b26")
|
TenantId = new Guid("b3466e83-7e11-464c-b93a-daf047838b26")
|
||||||
@ -2691,6 +2701,7 @@ namespace Marco.Pms.DataAccess.Migrations
|
|||||||
Id = new Guid("4842fa61-64eb-4241-aebd-8282065af9f9"),
|
Id = new Guid("4842fa61-64eb-4241-aebd-8282065af9f9"),
|
||||||
Description = "Government fees, insurance, inspections and safety-related expenditures.",
|
Description = "Government fees, insurance, inspections and safety-related expenditures.",
|
||||||
IsActive = true,
|
IsActive = true,
|
||||||
|
IsAttachmentRequried = true,
|
||||||
Name = "Compliance & Safety",
|
Name = "Compliance & Safety",
|
||||||
NoOfPersonsRequired = false,
|
NoOfPersonsRequired = false,
|
||||||
TenantId = new Guid("b3466e83-7e11-464c-b93a-daf047838b26")
|
TenantId = new Guid("b3466e83-7e11-464c-b93a-daf047838b26")
|
||||||
@ -3878,6 +3889,32 @@ namespace Marco.Pms.DataAccess.Migrations
|
|||||||
b.ToTable("TenantSubscriptions");
|
b.ToTable("TenantSubscriptions");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Marco.Pms.Model.Utilities.FCMTokenMapping", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("char(36)");
|
||||||
|
|
||||||
|
b.Property<Guid>("EmployeeId")
|
||||||
|
.HasColumnType("char(36)");
|
||||||
|
|
||||||
|
b.Property<DateTime>("ExpiredAt")
|
||||||
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
|
b.Property<string>("FcmToken")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<Guid>("TenantId")
|
||||||
|
.HasColumnType("char(36)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("TenantId");
|
||||||
|
|
||||||
|
b.ToTable("FCMTokenMappings");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Marco.Pms.Model.Utilities.Inquiries", b =>
|
modelBuilder.Entity("Marco.Pms.Model.Utilities.Inquiries", b =>
|
||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
@ -5452,6 +5489,17 @@ namespace Marco.Pms.DataAccess.Migrations
|
|||||||
b.Navigation("UpdatedBy");
|
b.Navigation("UpdatedBy");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Marco.Pms.Model.Utilities.FCMTokenMapping", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Marco.Pms.Model.TenantModels.Tenant", "Tenant")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("TenantId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Tenant");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
|
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using Marco.Pms.Model.MongoDBModels.Utility;
|
using Marco.Pms.Model.MongoDBModels;
|
||||||
|
using Marco.Pms.Model.MongoDBModels.Utility;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using MongoDB.Bson;
|
using MongoDB.Bson;
|
||||||
@ -146,5 +147,86 @@ namespace Marco.Pms.Helpers.Utility
|
|||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region =================================================================== NotificatioBody Helper Functions ===================================================================
|
||||||
|
|
||||||
|
public async Task AddNotificationAsync(NotificationMongoDB notification)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var collection = _mongoDatabase.GetCollection<NotificationMongoDB>("NotificatioBody");
|
||||||
|
|
||||||
|
var indexKeys = Builders<NotificationMongoDB>.IndexKeys
|
||||||
|
.Ascending(doc => doc.TenantId)
|
||||||
|
.Ascending(doc => doc.Name);
|
||||||
|
|
||||||
|
// Define index options with unique constraint
|
||||||
|
var indexOptions = new CreateIndexOptions { Unique = true };
|
||||||
|
|
||||||
|
// Create the index model
|
||||||
|
var indexModel = new CreateIndexModel<NotificationMongoDB>(indexKeys, indexOptions);
|
||||||
|
|
||||||
|
// Create the index on the collection (this operation is idempotent)
|
||||||
|
collection.Indexes.CreateOne(indexModel);
|
||||||
|
|
||||||
|
await collection.InsertOneAsync(notification);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Exception occured while adding the Notification body {NotificationName} for tenant {TenantId}", notification.Name, notification.TenantId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<NotificationMongoDB> GetNotificationBodyAsync(string name, Guid tenantId)
|
||||||
|
{
|
||||||
|
var rootTenantId = Guid.Parse("b3466e83-7e11-464c-b93a-daf047838b26");
|
||||||
|
NotificationMongoDB? result = null;
|
||||||
|
NotificationMongoDB? defaultNotification = new NotificationMongoDB
|
||||||
|
{
|
||||||
|
Name = "default",
|
||||||
|
Title = "Error: Something Went Wrong",
|
||||||
|
Body = " An unexpected error occurred. Please try again. If the problem persists, contact support",
|
||||||
|
Parameters = "",
|
||||||
|
TenantId = rootTenantId
|
||||||
|
};
|
||||||
|
|
||||||
|
var collection = _mongoDatabase.GetCollection<NotificationMongoDB>("NotificatioBody");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var filter = Builders<NotificationMongoDB>.Filter.And(
|
||||||
|
Builders<NotificationMongoDB>.Filter.Eq(n => n.Name, name),
|
||||||
|
Builders<NotificationMongoDB>.Filter.Eq(n => n.TenantId, tenantId)
|
||||||
|
|
||||||
|
);
|
||||||
|
|
||||||
|
result = await collection
|
||||||
|
.Find(filter)
|
||||||
|
.FirstOrDefaultAsync();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Exception occured while fetching the Notification body {NotificationName} for tenant {TenantId}", name, tenantId);
|
||||||
|
}
|
||||||
|
if (result == null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var filter = Builders<NotificationMongoDB>.Filter.And(
|
||||||
|
Builders<NotificationMongoDB>.Filter.Eq(n => n.Name, name),
|
||||||
|
Builders<NotificationMongoDB>.Filter.Eq(n => n.TenantId, rootTenantId)
|
||||||
|
|
||||||
|
);
|
||||||
|
result = await collection.Find(filter).FirstOrDefaultAsync();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Exception occured while fetching the Notification body {NotificationName} for tenant {TenantId}", name, rootTenantId);
|
||||||
|
return defaultNotification;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ namespace Marco.Pms.Model.Dtos.Attendance
|
|||||||
public ATTENDANCE_MARK_TYPE Action { get; set; }
|
public ATTENDANCE_MARK_TYPE Action { get; set; }
|
||||||
|
|
||||||
public FileUploadModel? Image { get; set; }
|
public FileUploadModel? Image { get; set; }
|
||||||
|
public string DeviceToken { get; set; } = string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum ATTENDANCE_MARK_TYPE
|
public enum ATTENDANCE_MARK_TYPE
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
{
|
{
|
||||||
public class LoginDto
|
public class LoginDto
|
||||||
{
|
{
|
||||||
public string? Username { get; set; }
|
public required string Username { get; set; }
|
||||||
public string? Password { get; set; }
|
public required string Password { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
namespace Marco.Pms.Model.Dtos.Authentication
|
namespace Marco.Pms.Model.Dtos.Authentication
|
||||||
{
|
{
|
||||||
public class LogoutDto
|
public class LogoutDto
|
||||||
{ public string? RefreshToken { get; set; }
|
{
|
||||||
|
public required string RefreshToken { get; set; }
|
||||||
|
public string? FcmToken { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
{
|
{
|
||||||
public class RefreshTokenDto
|
public class RefreshTokenDto
|
||||||
{
|
{
|
||||||
public string? Token { get; set; }
|
public required string Token { get; set; }
|
||||||
public string? RefreshToken { get; set; }
|
public required string RefreshToken { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,8 +2,9 @@
|
|||||||
{
|
{
|
||||||
public class VerifyMPINDto
|
public class VerifyMPINDto
|
||||||
{
|
{
|
||||||
public Guid EmployeeId { get; set; }
|
public required Guid EmployeeId { get; set; }
|
||||||
public string? MPIN { get; set; }
|
public required string MPIN { get; set; }
|
||||||
public string? MPINToken { get; set; }
|
public required string MPINToken { get; set; }
|
||||||
|
public required string FcmToken { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,18 +3,18 @@
|
|||||||
public class CreateUserDto
|
public class CreateUserDto
|
||||||
{
|
{
|
||||||
public Guid? Id { get; set; }
|
public Guid? Id { get; set; }
|
||||||
public string? FirstName { get; set; }
|
public required string FirstName { get; set; }
|
||||||
public string? LastName { get; set; }
|
public string? LastName { get; set; }
|
||||||
public string? MiddleName { get; set; }
|
public string? MiddleName { get; set; }
|
||||||
public string? Email { get; set; }
|
public string? Email { get; set; }
|
||||||
|
|
||||||
public string? Gender { get; set; }
|
public required string Gender { get; set; }
|
||||||
public string? BirthDate { get; set; }
|
public required string BirthDate { get; set; }
|
||||||
public string? JoiningDate { get; set; }
|
public required string JoiningDate { get; set; }
|
||||||
|
|
||||||
public string? PermanentAddress { get; set; }
|
public required string PermanentAddress { get; set; }
|
||||||
public string? CurrentAddress { get; set; }
|
public required string CurrentAddress { get; set; }
|
||||||
public string? PhoneNumber { get; set; }
|
public required string PhoneNumber { get; set; }
|
||||||
|
|
||||||
public string? EmergencyPhoneNumber { get; set; }
|
public string? EmergencyPhoneNumber { get; set; }
|
||||||
public string? EmergencyContactPerson { get; set; }
|
public string? EmergencyContactPerson { get; set; }
|
||||||
@ -33,10 +33,11 @@
|
|||||||
public class MobileUserManageDto
|
public class MobileUserManageDto
|
||||||
{
|
{
|
||||||
public Guid? Id { get; set; }
|
public Guid? Id { get; set; }
|
||||||
public string FirstName { get; set; } = string.Empty;
|
public required string FirstName { get; set; }
|
||||||
public string? LastName { get; set; }
|
public required string LastName { get; set; }
|
||||||
public string PhoneNumber { get; set; } = string.Empty;
|
public required string PhoneNumber { get; set; }
|
||||||
public string? Gender { get; set; }
|
public required DateTime JoiningDate { get; set; }
|
||||||
|
public required string Gender { get; set; }
|
||||||
public Guid JobRoleId { get; set; }
|
public Guid JobRoleId { get; set; }
|
||||||
public string? ProfileImage { get; set; }
|
public string? ProfileImage { get; set; }
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
public Guid? Id { get; set; }
|
public Guid? Id { get; set; }
|
||||||
public required string Name { get; set; }
|
public required string Name { get; set; }
|
||||||
public required bool NoOfPersonsRequired { get; set; }
|
public required bool NoOfPersonsRequired { get; set; }
|
||||||
|
public required bool IsAttachmentRequried { get; set; }
|
||||||
public string? Description { get; set; }
|
public string? Description { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -74,7 +74,7 @@ namespace Marco.Pms.Model.Mapper
|
|||||||
PhoneNumber = model.PhoneNumber,
|
PhoneNumber = model.PhoneNumber,
|
||||||
Photo = image,
|
Photo = image,
|
||||||
JobRoleId = model.JobRoleId,
|
JobRoleId = model.JobRoleId,
|
||||||
JoiningDate = null,
|
JoiningDate = model.JoiningDate,
|
||||||
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -9,5 +9,6 @@ namespace Marco.Pms.Model.Master
|
|||||||
public bool NoOfPersonsRequired { get; set; }
|
public bool NoOfPersonsRequired { get; set; }
|
||||||
public string Description { get; set; } = string.Empty;
|
public string Description { get; set; } = string.Empty;
|
||||||
public bool IsActive { get; set; } = true;
|
public bool IsActive { get; set; } = true;
|
||||||
|
public bool IsAttachmentRequried { get; set; } = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
19
Marco.Pms.Model/MongoDBModels/NotificationMongoDB.cs
Normal file
19
Marco.Pms.Model/MongoDBModels/NotificationMongoDB.cs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
using MongoDB.Bson;
|
||||||
|
using MongoDB.Bson.Serialization.Attributes;
|
||||||
|
|
||||||
|
namespace Marco.Pms.Model.MongoDBModels
|
||||||
|
{
|
||||||
|
public class NotificationMongoDB
|
||||||
|
{
|
||||||
|
[BsonId]
|
||||||
|
[BsonRepresentation(BsonType.String)]
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
public string Title { get; set; } = string.Empty;
|
||||||
|
public string Body { get; set; } = string.Empty;
|
||||||
|
public string Parameters { get; set; } = string.Empty; // Comma seprated variable needed for dynamic notifications
|
||||||
|
|
||||||
|
[BsonRepresentation(BsonType.String)]
|
||||||
|
public Guid TenantId { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
using System.ComponentModel;
|
using Marco.Pms.Model.Utilities;
|
||||||
|
using System.ComponentModel;
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using Marco.Pms.Model.Utilities;
|
|
||||||
|
|
||||||
namespace Marco.Pms.Model.Projects
|
namespace Marco.Pms.Model.Projects
|
||||||
{
|
{
|
||||||
|
7
Marco.Pms.Model/Utilities/FCMTokenDto.cs
Normal file
7
Marco.Pms.Model/Utilities/FCMTokenDto.cs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
namespace Marco.Pms.Model.Utilities
|
||||||
|
{
|
||||||
|
public class FCMTokenDto
|
||||||
|
{
|
||||||
|
public required string FcmToken { get; set; }
|
||||||
|
}
|
||||||
|
}
|
10
Marco.Pms.Model/Utilities/FCMTokenMapping.cs
Normal file
10
Marco.Pms.Model/Utilities/FCMTokenMapping.cs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
namespace Marco.Pms.Model.Utilities
|
||||||
|
{
|
||||||
|
public class FCMTokenMapping : TenantRelation
|
||||||
|
{
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
public Guid EmployeeId { get; set; }
|
||||||
|
public string FcmToken { get; set; } = string.Empty;
|
||||||
|
public DateTime ExpiredAt { get; set; } = DateTime.UtcNow.AddDays(6);
|
||||||
|
}
|
||||||
|
}
|
@ -4,7 +4,7 @@
|
|||||||
{
|
{
|
||||||
public Guid? DocumentId { get; set; }
|
public Guid? DocumentId { get; set; }
|
||||||
public required string FileName { get; set; } // Name of the file (e.g., "image1.png")
|
public required string FileName { get; set; } // Name of the file (e.g., "image1.png")
|
||||||
public required string Base64Data { get; set; } // Base64-encoded string of the file
|
public string? Base64Data { get; set; } // Base64-encoded string of the file
|
||||||
public required string ContentType { get; set; } // MIME type (e.g., "image/png", "application/pdf")
|
public required string ContentType { get; set; } // MIME type (e.g., "image/png", "application/pdf")
|
||||||
public long FileSize { get; set; } // File size in bytes
|
public long FileSize { get; set; } // File size in bytes
|
||||||
public string? Description { get; set; } // Optional: Description or purpose of the file
|
public string? Description { get; set; } // Optional: Description or purpose of the file
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
public Guid Id { get; set; }
|
public Guid Id { get; set; }
|
||||||
public string Name { get; set; } = string.Empty;
|
public string Name { get; set; } = string.Empty;
|
||||||
public bool NoOfPersonsRequired { get; set; }
|
public bool NoOfPersonsRequired { get; set; }
|
||||||
|
public bool IsAttachmentRequried { get; set; }
|
||||||
public string Description { get; set; } = string.Empty;
|
public string Description { get; set; } = string.Empty;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,10 +35,11 @@ namespace MarcoBMS.Services.Controllers
|
|||||||
private readonly PermissionServices _permission;
|
private readonly PermissionServices _permission;
|
||||||
private readonly ILoggingService _logger;
|
private readonly ILoggingService _logger;
|
||||||
private readonly IHubContext<MarcoHub> _signalR;
|
private readonly IHubContext<MarcoHub> _signalR;
|
||||||
|
private readonly IFirebaseService _firebase;
|
||||||
|
|
||||||
public AttendanceController(
|
public AttendanceController(
|
||||||
ApplicationDbContext context, EmployeeHelper employeeHelper, IProjectServices projectServices, UserHelper userHelper, S3UploadService s3Service, ILoggingService logger, PermissionServices permission, IHubContext<MarcoHub> signalR)
|
ApplicationDbContext context, EmployeeHelper employeeHelper, IProjectServices projectServices, UserHelper userHelper,
|
||||||
|
S3UploadService s3Service, ILoggingService logger, PermissionServices permission, IHubContext<MarcoHub> signalR, IFirebaseService firebase)
|
||||||
{
|
{
|
||||||
_context = context;
|
_context = context;
|
||||||
_employeeHelper = employeeHelper;
|
_employeeHelper = employeeHelper;
|
||||||
@ -48,6 +49,7 @@ namespace MarcoBMS.Services.Controllers
|
|||||||
_logger = logger;
|
_logger = logger;
|
||||||
_permission = permission;
|
_permission = permission;
|
||||||
_signalR = signalR;
|
_signalR = signalR;
|
||||||
|
_firebase = firebase;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Guid GetTenantId()
|
private Guid GetTenantId()
|
||||||
@ -81,8 +83,8 @@ namespace MarcoBMS.Services.Controllers
|
|||||||
return Ok(ApiResponse<object>.SuccessResponse(attendanceLogVMs, System.String.Format("{0} Attendance records fetched successfully", lstAttendance.Count), 200));
|
return Ok(ApiResponse<object>.SuccessResponse(attendanceLogVMs, System.String.Format("{0} Attendance records fetched successfully", lstAttendance.Count), 200));
|
||||||
|
|
||||||
}
|
}
|
||||||
[HttpGet("log/employee/{employeeId}")]
|
|
||||||
|
|
||||||
|
[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] string? dateFrom = null, [FromQuery] string? dateTo = null)
|
||||||
{
|
{
|
||||||
Guid TenantId = GetTenantId();
|
Guid TenantId = GetTenantId();
|
||||||
@ -258,7 +260,6 @@ namespace MarcoBMS.Services.Controllers
|
|||||||
/// <param name="date">YYYY-MM-dd</param>
|
/// <param name="date">YYYY-MM-dd</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
[HttpGet("project/team")]
|
[HttpGet("project/team")]
|
||||||
|
|
||||||
public async Task<IActionResult> EmployeeAttendanceByProject([FromQuery] Guid projectId, [FromQuery] bool IncludeInActive, [FromQuery] string? date = null)
|
public async Task<IActionResult> EmployeeAttendanceByProject([FromQuery] Guid projectId, [FromQuery] bool IncludeInActive, [FromQuery] string? date = null)
|
||||||
{
|
{
|
||||||
Guid TenantId = GetTenantId();
|
Guid TenantId = GetTenantId();
|
||||||
@ -363,7 +364,6 @@ namespace MarcoBMS.Services.Controllers
|
|||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("regularize")]
|
[HttpGet("regularize")]
|
||||||
|
|
||||||
public async Task<IActionResult> GetRequestRegularizeAttendance([FromQuery] Guid projectId, [FromQuery] bool IncludeInActive)
|
public async Task<IActionResult> GetRequestRegularizeAttendance([FromQuery] Guid projectId, [FromQuery] bool IncludeInActive)
|
||||||
{
|
{
|
||||||
Guid TenantId = GetTenantId();
|
Guid TenantId = GetTenantId();
|
||||||
@ -577,6 +577,19 @@ namespace MarcoBMS.Services.Controllers
|
|||||||
var notification = new { LoggedInUserId = currentEmployee.Id, Keyword = "Attendance", Activity = sendActivity, ProjectId = attendance.ProjectID, Response = vm };
|
var notification = new { LoggedInUserId = currentEmployee.Id, Keyword = "Attendance", Activity = sendActivity, ProjectId = attendance.ProjectID, Response = vm };
|
||||||
await _signalR.Clients.All.SendAsync("NotificationEventHandler", notification);
|
await _signalR.Clients.All.SendAsync("NotificationEventHandler", notification);
|
||||||
_logger.LogInfo("Attendance for employee {FirstName} {LastName} has been marked", employee.FirstName ?? string.Empty, employee.LastName ?? string.Empty);
|
_logger.LogInfo("Attendance for employee {FirstName} {LastName} has been marked", employee.FirstName ?? string.Empty, employee.LastName ?? string.Empty);
|
||||||
|
|
||||||
|
_ = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
// --- Push Notification Section ---
|
||||||
|
// This section attempts to send a test push notification to the user's device.
|
||||||
|
// It's designed to fail gracefully and handle invalid Firebase Cloud Messaging (FCM) tokens.
|
||||||
|
|
||||||
|
var name = $"{vm.FirstName} {vm.LastName}";
|
||||||
|
|
||||||
|
await _firebase.SendAttendanceMessageAsync(attendance.ProjectID, name, recordAttendanceDot.Action, attendance.EmployeeID, TenantId);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
return Ok(ApiResponse<object>.SuccessResponse(vm, "Attendance marked successfully.", 200));
|
return Ok(ApiResponse<object>.SuccessResponse(vm, "Attendance marked successfully.", 200));
|
||||||
}
|
}
|
||||||
_logger.LogInfo("Attendance for employee {FirstName} {LastName} has been marked", employee.FirstName ?? string.Empty, employee.LastName ?? string.Empty);
|
_logger.LogInfo("Attendance for employee {FirstName} {LastName} has been marked", employee.FirstName ?? string.Empty, employee.LastName ?? string.Empty);
|
||||||
@ -774,8 +787,19 @@ namespace MarcoBMS.Services.Controllers
|
|||||||
|
|
||||||
await _signalR.Clients.All.SendAsync("NotificationEventHandler", notification);
|
await _signalR.Clients.All.SendAsync("NotificationEventHandler", notification);
|
||||||
|
|
||||||
_logger.LogInfo("Attendance recorded for employee: {FullName}", $"{employee.FirstName} {employee.LastName}");
|
_ = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
// --- Push Notification Section ---
|
||||||
|
// This section attempts to send a test push notification to the user's device.
|
||||||
|
// It's designed to fail gracefully and handle invalid Firebase Cloud Messaging (FCM) tokens.
|
||||||
|
|
||||||
|
var name = $"{vm.FirstName} {vm.LastName}";
|
||||||
|
|
||||||
|
await _firebase.SendAttendanceMessageAsync(attendance.ProjectID, name, recordAttendanceDot.Action, attendance.EmployeeID, tenantId);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
_logger.LogInfo("Attendance recorded for employee: {FullName}", $"{employee.FirstName} {employee.LastName}");
|
||||||
return Ok(ApiResponse<object>.SuccessResponse(vm, "Attendance marked successfully.", 200));
|
return Ok(ApiResponse<object>.SuccessResponse(vm, "Attendance marked successfully.", 200));
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
@ -5,6 +5,7 @@ using Marco.Pms.Model.Dtos.Util;
|
|||||||
using Marco.Pms.Model.Employees;
|
using Marco.Pms.Model.Employees;
|
||||||
using Marco.Pms.Model.Entitlements;
|
using Marco.Pms.Model.Entitlements;
|
||||||
using Marco.Pms.Model.Utilities;
|
using Marco.Pms.Model.Utilities;
|
||||||
|
using Marco.Pms.Services.Service.ServiceInterfaces;
|
||||||
using MarcoBMS.Services.Helpers;
|
using MarcoBMS.Services.Helpers;
|
||||||
using MarcoBMS.Services.Service;
|
using MarcoBMS.Services.Service;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
@ -31,9 +32,10 @@ namespace MarcoBMS.Services.Controllers
|
|||||||
private readonly IConfiguration _configuration;
|
private readonly IConfiguration _configuration;
|
||||||
private readonly EmployeeHelper _employeeHelper;
|
private readonly EmployeeHelper _employeeHelper;
|
||||||
private readonly ILoggingService _logger;
|
private readonly ILoggingService _logger;
|
||||||
//string tenentId = "1";
|
private readonly IFirebaseService _firebase;
|
||||||
|
private readonly Guid tenantId;
|
||||||
public AuthController(UserManager<ApplicationUser> userManager, ApplicationDbContext context, JwtSettings jwtSettings, RefreshTokenService refreshTokenService,
|
public AuthController(UserManager<ApplicationUser> userManager, ApplicationDbContext context, JwtSettings jwtSettings, RefreshTokenService refreshTokenService,
|
||||||
IEmailSender emailSender, IConfiguration configuration, EmployeeHelper employeeHelper, UserHelper userHelper, ILoggingService logger)
|
IEmailSender emailSender, IConfiguration configuration, EmployeeHelper employeeHelper, UserHelper userHelper, ILoggingService logger, IFirebaseService firebase)
|
||||||
{
|
{
|
||||||
_userManager = userManager;
|
_userManager = userManager;
|
||||||
_jwtSettings = jwtSettings;
|
_jwtSettings = jwtSettings;
|
||||||
@ -44,6 +46,8 @@ namespace MarcoBMS.Services.Controllers
|
|||||||
_context = context;
|
_context = context;
|
||||||
_userHelper = userHelper;
|
_userHelper = userHelper;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
_firebase = firebase;
|
||||||
|
tenantId = userHelper.GetTenantId();
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("login")]
|
[HttpPost("login")]
|
||||||
@ -51,7 +55,7 @@ namespace MarcoBMS.Services.Controllers
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Find user by email or phone number
|
|
||||||
var user = await _context.ApplicationUsers
|
var user = await _context.ApplicationUsers
|
||||||
.FirstOrDefaultAsync(u => u.Email == loginDto.Username || u.PhoneNumber == loginDto.Username);
|
.FirstOrDefaultAsync(u => u.Email == loginDto.Username || u.PhoneNumber == loginDto.Username);
|
||||||
|
|
||||||
@ -115,78 +119,145 @@ namespace MarcoBMS.Services.Controllers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles mobile user login, validates credentials, sends a test push notification,
|
||||||
|
/// and generates JWT, Refresh, and MPIN tokens upon successful authentication.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="loginDto">Data Transfer Object containing the user's login credentials and device token.</param>
|
||||||
|
/// <returns>An IActionResult containing the authentication tokens or an error response.</returns>
|
||||||
|
|
||||||
[HttpPost("login-mobile")]
|
[HttpPost("login-mobile")]
|
||||||
public async Task<IActionResult> LoginMobile([FromBody] LoginDto loginDto)
|
public async Task<IActionResult> LoginMobile([FromBody] LoginDto loginDto)
|
||||||
{
|
{
|
||||||
// Validate input DTO
|
// Log the start of the login attempt for traceability.
|
||||||
if (loginDto == null || string.IsNullOrWhiteSpace(loginDto.Username) || string.IsNullOrWhiteSpace(loginDto.Password))
|
_logger.LogInfo("Login attempt initiated for user: {Username}", loginDto.Username ?? "N/A");
|
||||||
|
|
||||||
|
try
|
||||||
{
|
{
|
||||||
return BadRequest(ApiResponse<object>.ErrorResponse("Username or password is missing.", "Invalid request", 400));
|
// --- Input Validation ---
|
||||||
|
// Ensure that the request body and essential fields are not null or empty.
|
||||||
|
if (loginDto == null || string.IsNullOrWhiteSpace(loginDto.Username) || string.IsNullOrWhiteSpace(loginDto.Password))
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Login failed due to missing username or password.");
|
||||||
|
return BadRequest(ApiResponse<object>.ErrorResponse("Username or password is missing.", "Invalid request", 400));
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- User Retrieval ---
|
||||||
|
// Find the user in the database by their email or phone number.
|
||||||
|
_logger.LogInfo("Searching for user: {Username}", loginDto.Username);
|
||||||
|
var user = await _context.ApplicationUsers
|
||||||
|
.FirstOrDefaultAsync(u => u.Email == loginDto.Username || u.PhoneNumber == loginDto.Username);
|
||||||
|
|
||||||
|
// If no user is found, return an unauthorized response.
|
||||||
|
if (user == null || string.IsNullOrWhiteSpace(user.UserName))
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Login failed: User not found for username {Username}", loginDto.Username);
|
||||||
|
return Unauthorized(ApiResponse<object>.ErrorResponse("Invalid username or password.", "Invalid username or password.", 401));
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- User Status Checks ---
|
||||||
|
// Check if the user's account is marked as inactive.
|
||||||
|
if (!user.IsActive)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Login failed: User '{Username}' account is inactive.", user.UserName);
|
||||||
|
return BadRequest(ApiResponse<object>.ErrorResponse("User is inactive", "User is inactive", 400));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the user has confirmed their email address.
|
||||||
|
if (!user.EmailConfirmed)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Login failed: User '{Username}' email is not verified.", user.UserName);
|
||||||
|
return BadRequest(ApiResponse<object>.ErrorResponse("Your email is not verified. Please verify your email.", "Email not verified", 400));
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Password Validation ---
|
||||||
|
// Use ASP.NET Identity's UserManager to securely check the password.
|
||||||
|
_logger.LogInfo("Validating password for user: {Username}", user.UserName);
|
||||||
|
var isPasswordValid = await _userManager.CheckPasswordAsync(user, loginDto.Password);
|
||||||
|
if (!isPasswordValid)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Login failed: Invalid password for user {Username}", user.UserName);
|
||||||
|
return Unauthorized(ApiResponse<object>.ErrorResponse("Invalid username or password.", "Invalid credentials", 401));
|
||||||
|
}
|
||||||
|
_logger.LogInfo("Password validation successful for user: {Username}", user.UserName);
|
||||||
|
|
||||||
|
// Check if the username property on the user object is populated.
|
||||||
|
if (string.IsNullOrWhiteSpace(user.UserName))
|
||||||
|
{
|
||||||
|
// This is an unlikely edge case, but good to handle.
|
||||||
|
_logger.LogWarning("Login failed: User object for ID {UserId} is missing a UserName.", user.Id);
|
||||||
|
return NotFound(ApiResponse<object>.ErrorResponse("UserName not found", "Username is missing", 404));
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Employee and Tenant Context Retrieval ---
|
||||||
|
// Fetch associated employee details to get tenant context for token generation.
|
||||||
|
_logger.LogInfo("Fetching employee details for user ID: {UserId}", user.Id);
|
||||||
|
var emp = await _employeeHelper.GetEmployeeByApplicationUserID(user.Id);
|
||||||
|
if (emp == null)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Login failed: Could not find associated employee record for user ID {UserId}", user.Id);
|
||||||
|
return NotFound(ApiResponse<object>.ErrorResponse("Employee not found", "Employee details missing", 404));
|
||||||
|
}
|
||||||
|
_logger.LogInfo("Successfully found employee details for tenant ID: {TenantId}", emp.TenantId);
|
||||||
|
|
||||||
|
// --- Token Generation ---
|
||||||
|
// Generate the primary JWT access token.
|
||||||
|
_logger.LogInfo("Generating JWT for user: {Username}", user.UserName);
|
||||||
|
var token = _refreshTokenService.GenerateJwtToken(user.UserName, emp.TenantId, _jwtSettings);
|
||||||
|
|
||||||
|
// Generate a new refresh token and store it in the database.
|
||||||
|
_logger.LogInfo("Generating and storing Refresh Token for user: {Username}", user.UserName);
|
||||||
|
var refreshToken = await _refreshTokenService.CreateRefreshToken(user.Id, emp.TenantId.ToString(), _jwtSettings);
|
||||||
|
|
||||||
|
// Fetch the user's MPIN token if it exists.
|
||||||
|
_logger.LogInfo("Fetching MPIN token for user: {Username}", user.UserName);
|
||||||
|
var mpinToken = await _context.MPINDetails.FirstOrDefaultAsync(p => p.UserId == Guid.Parse(user.Id) && p.TenantId == emp.TenantId);
|
||||||
|
|
||||||
|
// --- Response Assembly ---
|
||||||
|
// Combine all tokens into a single response object.
|
||||||
|
var responseData = new
|
||||||
|
{
|
||||||
|
token,
|
||||||
|
refreshToken,
|
||||||
|
mpinToken = mpinToken?.MPINToken // Safely access the MPIN token, will be null if not found.
|
||||||
|
};
|
||||||
|
|
||||||
|
// Return a successful response with the generated tokens.
|
||||||
|
_logger.LogInfo("User {Username} logged in successfully.", user.UserName);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Exception occured while saving FCM Token for employee {EmployeeId}", emp.Id);
|
||||||
|
return StatusCode(500, ApiResponse<object>.ErrorResponse("Internal Error", ex.Message, 500));
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
// --- Push Notification Section ---
|
||||||
|
// This section attempts to send a test push notification to the user's device.
|
||||||
|
// It's designed to fail gracefully and handle invalid Firebase Cloud Messaging (FCM) tokens.
|
||||||
|
var name = $"{emp.FirstName} {emp.LastName}";
|
||||||
|
await _firebase.SendLoginMessageAsync(name, tenantId);
|
||||||
|
|
||||||
|
});
|
||||||
|
return Ok(ApiResponse<object>.SuccessResponse(responseData, "User logged in successfully.", 200));
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
// Find user by email or phone number
|
|
||||||
var user = await _context.ApplicationUsers
|
|
||||||
.FirstOrDefaultAsync(u => u.Email == loginDto.Username || u.PhoneNumber == loginDto.Username);
|
|
||||||
|
|
||||||
// If user not found, return unauthorized
|
|
||||||
if (user == null)
|
|
||||||
{
|
{
|
||||||
return Unauthorized(ApiResponse<object>.ErrorResponse("Invalid username or password.", "Invalid username or password.", 401));
|
// --- Global Exception Handling ---
|
||||||
|
// Catch any unexpected exceptions during the login process.
|
||||||
|
_logger.LogError(ex, "An unexpected error occurred during the LoginMobile process for user: {Username}", loginDto?.Username ?? "N/A");
|
||||||
|
|
||||||
|
// Return a generic 500 Internal Server Error to avoid leaking implementation details.
|
||||||
|
return StatusCode(500, ApiResponse<object>.ErrorResponse("An internal server error occurred.", "Server Error", 500));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if user is inactive
|
|
||||||
if (!user.IsActive)
|
|
||||||
{
|
|
||||||
return BadRequest(ApiResponse<object>.ErrorResponse("User is inactive", "User is inactive", 400));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if user email is not confirmed
|
|
||||||
if (!user.EmailConfirmed)
|
|
||||||
{
|
|
||||||
return BadRequest(ApiResponse<object>.ErrorResponse("Your email is not verified. Please verify your email.", "Email not verified", 400));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate password using ASP.NET Identity
|
|
||||||
var isPasswordValid = await _userManager.CheckPasswordAsync(user, loginDto.Password);
|
|
||||||
if (!isPasswordValid)
|
|
||||||
{
|
|
||||||
return Unauthorized(ApiResponse<object>.ErrorResponse("Invalid username or password.", "Invalid credentials", 401));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if username is missing
|
|
||||||
if (string.IsNullOrWhiteSpace(user.UserName))
|
|
||||||
{
|
|
||||||
return NotFound(ApiResponse<object>.ErrorResponse("UserName not found", "Username is missing", 404));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get employee information for tenant context
|
|
||||||
var emp = await _employeeHelper.GetEmployeeByApplicationUserID(user.Id);
|
|
||||||
if (emp == null)
|
|
||||||
{
|
|
||||||
return NotFound(ApiResponse<object>.ErrorResponse("Employee not found", "Employee details missing", 404));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate JWT token
|
|
||||||
var token = _refreshTokenService.GenerateJwtToken(user.UserName, emp.TenantId, _jwtSettings);
|
|
||||||
|
|
||||||
// Generate Refresh Token and store in DB
|
|
||||||
var refreshToken = await _refreshTokenService.CreateRefreshToken(user.Id, emp.TenantId.ToString(), _jwtSettings);
|
|
||||||
|
|
||||||
// Fetch MPIN Token
|
|
||||||
var mpinToken = await _context.MPINDetails.FirstOrDefaultAsync(p => p.UserId == Guid.Parse(user.Id) && p.TenantId == emp.TenantId);
|
|
||||||
|
|
||||||
// Combine all tokens in response
|
|
||||||
var responseData = new
|
|
||||||
{
|
|
||||||
token,
|
|
||||||
refreshToken,
|
|
||||||
mpinToken = mpinToken?.MPINToken
|
|
||||||
};
|
|
||||||
|
|
||||||
// Return success response
|
|
||||||
return Ok(ApiResponse<object>.SuccessResponse(responseData, "User logged in successfully.", 200));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[HttpPost("login-mpin")]
|
[HttpPost("login-mpin")]
|
||||||
public async Task<IActionResult> VerifyMPIN([FromBody] VerifyMPINDto verifyMPIN)
|
public async Task<IActionResult> VerifyMPIN([FromBody] VerifyMPINDto verifyMPIN)
|
||||||
{
|
{
|
||||||
@ -256,6 +327,44 @@ namespace MarcoBMS.Services.Controllers
|
|||||||
return Unauthorized(ApiResponse<object>.ErrorResponse("MPIN mismatch", "MPIN did not match", 401));
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
// --- Push Notification Section ---
|
||||||
|
// This section attempts to send a test push notification to the user's device.
|
||||||
|
// It's designed to fail gracefully and handle invalid Firebase Cloud Messaging (FCM) tokens.
|
||||||
|
await _firebase.SendLoginOnAnotherDeviceMessageAsync(requestEmployee.Id, verifyMPIN.FcmToken, tenantId);
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Generate new tokens
|
// Generate new tokens
|
||||||
var jwtToken = _refreshTokenService.GenerateJwtToken(requestEmployee.Email, tenantId, _jwtSettings);
|
var jwtToken = _refreshTokenService.GenerateJwtToken(requestEmployee.Email, tenantId, _jwtSettings);
|
||||||
var refreshToken = await _refreshTokenService.CreateRefreshToken(requestEmployee.ApplicationUserId, tenantId.ToString(), _jwtSettings);
|
var refreshToken = await _refreshTokenService.CreateRefreshToken(requestEmployee.ApplicationUserId, tenantId.ToString(), _jwtSettings);
|
||||||
@ -278,6 +387,7 @@ namespace MarcoBMS.Services.Controllers
|
|||||||
[HttpPost("logout")]
|
[HttpPost("logout")]
|
||||||
public async Task<IActionResult> Logout([FromBody] LogoutDto logoutDto)
|
public async Task<IActionResult> Logout([FromBody] LogoutDto logoutDto)
|
||||||
{
|
{
|
||||||
|
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
|
||||||
if (string.IsNullOrWhiteSpace(logoutDto.RefreshToken))
|
if (string.IsNullOrWhiteSpace(logoutDto.RefreshToken))
|
||||||
{
|
{
|
||||||
_logger.LogWarning("Logout failed: Refresh token is missing");
|
_logger.LogWarning("Logout failed: Refresh token is missing");
|
||||||
@ -301,7 +411,15 @@ namespace MarcoBMS.Services.Controllers
|
|||||||
await _refreshTokenService.BlacklistJwtTokenAsync(jwtToken);
|
await _refreshTokenService.BlacklistJwtTokenAsync(jwtToken);
|
||||||
_logger.LogInfo("JWT access token blacklisted successfully");
|
_logger.LogInfo("JWT access token blacklisted successfully");
|
||||||
}
|
}
|
||||||
|
if (!string.IsNullOrWhiteSpace(logoutDto.FcmToken))
|
||||||
|
{
|
||||||
|
var fcmTokenMapping = await _context.FCMTokenMappings.FirstOrDefaultAsync(ft => ft.EmployeeId == loggedInEmployee.Id && ft.FcmToken == logoutDto.FcmToken && ft.TenantId == tenantId);
|
||||||
|
if (fcmTokenMapping != null)
|
||||||
|
{
|
||||||
|
_context.FCMTokenMappings.Remove(fcmTokenMapping);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
_logger.LogInfo("User logged out successfully");
|
_logger.LogInfo("User logged out successfully");
|
||||||
return Ok(ApiResponse<object>.SuccessResponse(new { }, "Logged out successfully", 200));
|
return Ok(ApiResponse<object>.SuccessResponse(new { }, "Logged out successfully", 200));
|
||||||
}
|
}
|
||||||
@ -806,6 +924,40 @@ namespace MarcoBMS.Services.Controllers
|
|||||||
return Ok(ApiResponse<object>.SuccessResponse(mpinToken, "MPIN updated successfully", 200));
|
return Ok(ApiResponse<object>.SuccessResponse(mpinToken, "MPIN updated successfully", 200));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Authorize]
|
||||||
|
[HttpPost("set/device-token")]
|
||||||
|
public async Task<IActionResult> StoreDeviceToken([FromBody] FCMTokenDto model)
|
||||||
|
{
|
||||||
|
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
|
||||||
|
|
||||||
|
var existingFCMTokenMapping = await _context.FCMTokenMappings.Where(ft => ft.FcmToken == model.FcmToken).ToListAsync();
|
||||||
|
|
||||||
|
if (existingFCMTokenMapping.Any())
|
||||||
|
{
|
||||||
|
_context.FCMTokenMappings.RemoveRange(existingFCMTokenMapping);
|
||||||
|
}
|
||||||
|
|
||||||
|
var fcmTokenMapping = new FCMTokenMapping
|
||||||
|
{
|
||||||
|
EmployeeId = loggedInEmployee.Id,
|
||||||
|
FcmToken = model.FcmToken,
|
||||||
|
ExpiredAt = DateTime.UtcNow.AddDays(6),
|
||||||
|
TenantId = tenantId
|
||||||
|
};
|
||||||
|
_context.FCMTokenMappings.Add(fcmTokenMapping);
|
||||||
|
_logger.LogInfo("New FCM Token registering for employee {EmployeeId}", loggedInEmployee.Id);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Exception occured while saving FCM Token for employee {EmployeeId}", loggedInEmployee.Id);
|
||||||
|
return StatusCode(500, ApiResponse<object>.ErrorResponse("Internal Error", ex.Message, 500));
|
||||||
|
}
|
||||||
|
return Ok(ApiResponse<object>.SuccessResponse(new { }, "FCM Token registered Successfuly", 200));
|
||||||
|
}
|
||||||
private static string ComputeSha256Hash(string rawData)
|
private static string ComputeSha256Hash(string rawData)
|
||||||
{
|
{
|
||||||
using (SHA256 sha256 = SHA256.Create())
|
using (SHA256 sha256 = SHA256.Create())
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using AutoMapper;
|
using AutoMapper;
|
||||||
|
using FirebaseAdmin.Messaging;
|
||||||
using Marco.Pms.DataAccess.Data;
|
using Marco.Pms.DataAccess.Data;
|
||||||
using Marco.Pms.Helpers.Utility;
|
using Marco.Pms.Helpers.Utility;
|
||||||
using Marco.Pms.Model.DocumentManager;
|
using Marco.Pms.Model.DocumentManager;
|
||||||
@ -10,6 +11,7 @@ using Marco.Pms.Model.Utilities;
|
|||||||
using Marco.Pms.Model.ViewModels.Activities;
|
using Marco.Pms.Model.ViewModels.Activities;
|
||||||
using Marco.Pms.Model.ViewModels.DocumentManager;
|
using Marco.Pms.Model.ViewModels.DocumentManager;
|
||||||
using Marco.Pms.Services.Service;
|
using Marco.Pms.Services.Service;
|
||||||
|
using Marco.Pms.Services.Service.ServiceInterfaces;
|
||||||
using MarcoBMS.Services.Helpers;
|
using MarcoBMS.Services.Helpers;
|
||||||
using MarcoBMS.Services.Service;
|
using MarcoBMS.Services.Service;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
@ -163,9 +165,8 @@ namespace Marco.Pms.Services.Controllers
|
|||||||
if (documentFilter.IsUploadedAt)
|
if (documentFilter.IsUploadedAt)
|
||||||
{
|
{
|
||||||
documentQuery = documentQuery.Where(da =>
|
documentQuery = documentQuery.Where(da =>
|
||||||
da.UpdatedAt.HasValue &&
|
da.UploadedAt.Date >= documentFilter.StartDate.Value.Date &&
|
||||||
da.UpdatedAt.Value.Date >= documentFilter.StartDate.Value.Date &&
|
da.UploadedAt.Date <= documentFilter.EndDate.Value.Date);
|
||||||
da.UpdatedAt.Value.Date <= documentFilter.EndDate.Value.Date);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -703,13 +704,6 @@ namespace Marco.Pms.Services.Controllers
|
|||||||
return NotFound(ApiResponse<object>.ErrorResponse($"{(entityType == EmployeeEntity ? "Employee" : "Project")} Not Found", "Entity not found in database", 404));
|
return NotFound(ApiResponse<object>.ErrorResponse($"{(entityType == EmployeeEntity ? "Employee" : "Project")} Not Found", "Entity not found in database", 404));
|
||||||
}
|
}
|
||||||
|
|
||||||
var isDocumentExist = await _context.DocumentAttachments.AnyAsync(da => da.DocumentId == model.DocumentId && da.TenantId == tenantId);
|
|
||||||
if (isDocumentExist)
|
|
||||||
{
|
|
||||||
_logger.LogWarning("{DocumentId} is already existed in database", model.DocumentId ?? string.Empty);
|
|
||||||
return StatusCode(409, ApiResponse<object>.ErrorResponse("Document already existed", "Document already existed in database", 409));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Map DTO to DB entity
|
// Map DTO to DB entity
|
||||||
var attachment = _mapper.Map<DocumentAttachment>(model);
|
var attachment = _mapper.Map<DocumentAttachment>(model);
|
||||||
attachment.UploadedAt = DateTime.UtcNow;
|
attachment.UploadedAt = DateTime.UtcNow;
|
||||||
@ -841,6 +835,39 @@ namespace Marco.Pms.Services.Controllers
|
|||||||
response.ParentAttachmentId = versionMapping.ParentAttachmentId;
|
response.ParentAttachmentId = versionMapping.ParentAttachmentId;
|
||||||
response.Version = versionMapping.Version;
|
response.Version = versionMapping.Version;
|
||||||
|
|
||||||
|
var _firebase = scope.ServiceProvider.GetRequiredService<IFirebaseService>();
|
||||||
|
|
||||||
|
_ = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
// --- Push Notification Section ---
|
||||||
|
// This section attempts to send a test push notification to the user's device.
|
||||||
|
// It's designed to fail gracefully and handle invalid Firebase Cloud Messaging (FCM) tokens.
|
||||||
|
|
||||||
|
var name = $"{loggedInEmployee.FirstName} {loggedInEmployee.LastName}";
|
||||||
|
|
||||||
|
if (EmployeeEntity == entityType && model.EntityId != loggedInEmployee.Id)
|
||||||
|
{
|
||||||
|
var notification = new Notification
|
||||||
|
{
|
||||||
|
Title = $"Document added to Employee.",
|
||||||
|
Body = $"Document added to your profile of type \"{documentType.Name}\" by {name}"
|
||||||
|
};
|
||||||
|
|
||||||
|
await _firebase.SendEmployeeDocumentMessageAsync(attachment.Id, model.EntityId, notification, tenantId);
|
||||||
|
}
|
||||||
|
if (ProjectEntity == entityType)
|
||||||
|
{
|
||||||
|
var notification = new Notification
|
||||||
|
{
|
||||||
|
Title = $"Document added to Project.",
|
||||||
|
Body = $"Document added to your Project of type \"{documentType.Name}\" by {name}"
|
||||||
|
};
|
||||||
|
|
||||||
|
await _firebase.SendProjectDocumentMessageAsync(attachment.Id, model.EntityId, notification, tenantId);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
return Ok(ApiResponse<object>.SuccessResponse(response, "Document added successfully", 200));
|
return Ok(ApiResponse<object>.SuccessResponse(response, "Document added successfully", 200));
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@ -932,6 +959,41 @@ namespace Marco.Pms.Services.Controllers
|
|||||||
}
|
}
|
||||||
|
|
||||||
_logger.LogInfo("Document verified successfully. DocumentId: {DocumentId}, VerifiedBy: {EmployeeId}", documentAttachment.Id, loggedInEmployee.Id);
|
_logger.LogInfo("Document verified successfully. DocumentId: {DocumentId}, VerifiedBy: {EmployeeId}", documentAttachment.Id, loggedInEmployee.Id);
|
||||||
|
|
||||||
|
var _firebase = scope.ServiceProvider.GetRequiredService<IFirebaseService>();
|
||||||
|
_ = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
// --- Push Notification Section ---
|
||||||
|
// This section attempts to send a test push notification to the user's device.
|
||||||
|
// It's designed to fail gracefully and handle invalid Firebase Cloud Messaging (FCM) tokens.
|
||||||
|
|
||||||
|
var name = $"{loggedInEmployee.FirstName} {loggedInEmployee.LastName}";
|
||||||
|
|
||||||
|
var entityType = documentAttachment.DocumentType?.DocumentCategory?.EntityTypeId;
|
||||||
|
var documentType = documentAttachment.DocumentType;
|
||||||
|
|
||||||
|
if (EmployeeEntity == entityType && documentAttachment.EntityId != loggedInEmployee.Id)
|
||||||
|
{
|
||||||
|
var notification = new Notification
|
||||||
|
{
|
||||||
|
Title = $"Your Document is Verified.",
|
||||||
|
Body = $"Your Document of type \"{documentType?.Name}\" is Verified by {name}"
|
||||||
|
};
|
||||||
|
|
||||||
|
await _firebase.SendEmployeeDocumentMessageAsync(documentAttachment.Id, documentAttachment.EntityId, notification, tenantId);
|
||||||
|
}
|
||||||
|
if (ProjectEntity == entityType)
|
||||||
|
{
|
||||||
|
var notification = new Notification
|
||||||
|
{
|
||||||
|
Title = $"Document for your project is verified",
|
||||||
|
Body = $"Document for your Project of type \"{documentType?.Name}\" is Verified by {name}"
|
||||||
|
};
|
||||||
|
|
||||||
|
await _firebase.SendProjectDocumentMessageAsync(documentAttachment.Id, documentAttachment.EntityId, notification, tenantId);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
return Ok(ApiResponse<object>.SuccessResponse(new { }, "Document is verified successfully", 200));
|
return Ok(ApiResponse<object>.SuccessResponse(new { }, "Document is verified successfully", 200));
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@ -1038,13 +1100,6 @@ namespace Marco.Pms.Services.Controllers
|
|||||||
return NotFound(ApiResponse<object>.ErrorResponse($"{(entityType == EmployeeEntity ? "Employee" : "Project")} Not Found", "Entity not found in database", 404));
|
return NotFound(ApiResponse<object>.ErrorResponse($"{(entityType == EmployeeEntity ? "Employee" : "Project")} Not Found", "Entity not found in database", 404));
|
||||||
}
|
}
|
||||||
|
|
||||||
var isDocumentExist = await _context.DocumentAttachments.AnyAsync(da => da.Id != model.Id && da.DocumentId == model.DocumentId && da.TenantId == tenantId);
|
|
||||||
if (isDocumentExist)
|
|
||||||
{
|
|
||||||
_logger.LogWarning("{DocumentId} is already existed in database", model.DocumentId ?? string.Empty);
|
|
||||||
return StatusCode(409, ApiResponse<object>.ErrorResponse("Document already existed", "Document already existed in database", 409));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prepare for versioning
|
// Prepare for versioning
|
||||||
var oldVersionMapping = await _context.AttachmentVersionMappings
|
var oldVersionMapping = await _context.AttachmentVersionMappings
|
||||||
.FirstOrDefaultAsync(av => av.ChildAttachmentId == oldAttachment.Id && av.TenantId == tenantId);
|
.FirstOrDefaultAsync(av => av.ChildAttachmentId == oldAttachment.Id && av.TenantId == tenantId);
|
||||||
@ -1176,7 +1231,7 @@ namespace Marco.Pms.Services.Controllers
|
|||||||
oldAttachment.DocumentId = model.DocumentId;
|
oldAttachment.DocumentId = model.DocumentId;
|
||||||
oldAttachment.Description = model.Description;
|
oldAttachment.Description = model.Description;
|
||||||
oldAttachment.DocumentDataId = document.Id;
|
oldAttachment.DocumentDataId = document.Id;
|
||||||
if (oldAttachment.IsVerified == true)
|
if (oldAttachment.IsVerified != null)
|
||||||
{
|
{
|
||||||
oldAttachment.IsVerified = null;
|
oldAttachment.IsVerified = null;
|
||||||
_logger.LogInfo("Reset verification flag for AttachmentId: {AttachmentId}", oldAttachment.Id);
|
_logger.LogInfo("Reset verification flag for AttachmentId: {AttachmentId}", oldAttachment.Id);
|
||||||
@ -1196,7 +1251,7 @@ namespace Marco.Pms.Services.Controllers
|
|||||||
oldAttachment.Name = model.Name;
|
oldAttachment.Name = model.Name;
|
||||||
oldAttachment.DocumentId = model.DocumentId;
|
oldAttachment.DocumentId = model.DocumentId;
|
||||||
oldAttachment.Description = model.Description;
|
oldAttachment.Description = model.Description;
|
||||||
if (oldAttachment.IsVerified == true)
|
if (oldAttachment.IsVerified != null)
|
||||||
{
|
{
|
||||||
oldAttachment.IsVerified = null;
|
oldAttachment.IsVerified = null;
|
||||||
_logger.LogInfo("Reset verification flag for AttachmentId: {AttachmentId}", oldAttachment.Id);
|
_logger.LogInfo("Reset verification flag for AttachmentId: {AttachmentId}", oldAttachment.Id);
|
||||||
@ -1288,6 +1343,39 @@ namespace Marco.Pms.Services.Controllers
|
|||||||
response.Version = newVersionMapping.Version;
|
response.Version = newVersionMapping.Version;
|
||||||
_logger.LogInfo("API completed successfully for AttachmentId: {AttachmentId}", newAttachment.Id);
|
_logger.LogInfo("API completed successfully for AttachmentId: {AttachmentId}", newAttachment.Id);
|
||||||
|
|
||||||
|
var _firebase = scope.ServiceProvider.GetRequiredService<IFirebaseService>();
|
||||||
|
|
||||||
|
_ = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
// --- Push Notification Section ---
|
||||||
|
// This section attempts to send a test push notification to the user's device.
|
||||||
|
// It's designed to fail gracefully and handle invalid Firebase Cloud Messaging (FCM) tokens.
|
||||||
|
|
||||||
|
var name = $"{loggedInEmployee.FirstName} {loggedInEmployee.LastName}";
|
||||||
|
|
||||||
|
if (EmployeeEntity == entityType && newAttachment.EntityId != loggedInEmployee.Id)
|
||||||
|
{
|
||||||
|
var notification = new Notification
|
||||||
|
{
|
||||||
|
Title = $"Your Document is updated.",
|
||||||
|
Body = $"Your Document of type \"{documentType?.Name}\" is updated by {name}"
|
||||||
|
};
|
||||||
|
|
||||||
|
await _firebase.SendEmployeeDocumentMessageAsync(newAttachment.Id, newAttachment.EntityId, notification, tenantId);
|
||||||
|
}
|
||||||
|
if (ProjectEntity == entityType)
|
||||||
|
{
|
||||||
|
var notification = new Notification
|
||||||
|
{
|
||||||
|
Title = "Your Project Document is updated.",
|
||||||
|
Body = $"Your Project Document of type \"{documentType?.Name}\" is updated by {name}"
|
||||||
|
};
|
||||||
|
|
||||||
|
await _firebase.SendProjectDocumentMessageAsync(newAttachment.Id, newAttachment.EntityId, notification, tenantId);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
return Ok(ApiResponse<object>.SuccessResponse(response, "Document Updated successfully", 200));
|
return Ok(ApiResponse<object>.SuccessResponse(response, "Document Updated successfully", 200));
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@ -1370,6 +1458,42 @@ namespace Marco.Pms.Services.Controllers
|
|||||||
// Log the successful completion of the operation
|
// Log the successful completion of the operation
|
||||||
_logger.LogInfo("DocumentAttachment ID: {DocumentId} has been {Message} by employee ID: {EmployeeId}", id, message, loggedInEmployee.Id);
|
_logger.LogInfo("DocumentAttachment ID: {DocumentId} has been {Message} by employee ID: {EmployeeId}", id, message, loggedInEmployee.Id);
|
||||||
|
|
||||||
|
var _firebase = scope.ServiceProvider.GetRequiredService<IFirebaseService>();
|
||||||
|
|
||||||
|
_ = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
// --- Push Notification Section ---
|
||||||
|
// This section attempts to send a test push notification to the user's device.
|
||||||
|
// It's designed to fail gracefully and handle invalid Firebase Cloud Messaging (FCM) tokens.
|
||||||
|
|
||||||
|
var entityType = documentAttachment.DocumentType?.DocumentCategory?.EntityTypeId;
|
||||||
|
var documentType = documentAttachment.DocumentType;
|
||||||
|
|
||||||
|
var name = $"{loggedInEmployee.FirstName} {loggedInEmployee.LastName}";
|
||||||
|
|
||||||
|
if (EmployeeEntity == entityType && documentAttachment.EntityId != loggedInEmployee.Id)
|
||||||
|
{
|
||||||
|
var notification = new Notification
|
||||||
|
{
|
||||||
|
Title = $"Your Document is {message}.",
|
||||||
|
Body = $"Your Document of type \"{documentType?.Name}\" is {message} by {name}"
|
||||||
|
};
|
||||||
|
|
||||||
|
await _firebase.SendEmployeeDocumentMessageAsync(documentAttachment.Id, documentAttachment.EntityId, notification, tenantId);
|
||||||
|
}
|
||||||
|
if (ProjectEntity == entityType)
|
||||||
|
{
|
||||||
|
var notification = new Notification
|
||||||
|
{
|
||||||
|
Title = "Your Project Document is {message}.",
|
||||||
|
Body = $"Your Project Document of type \"{documentType?.Name}\" is {message} by {name}"
|
||||||
|
};
|
||||||
|
|
||||||
|
await _firebase.SendProjectDocumentMessageAsync(documentAttachment.Id, documentAttachment.EntityId, notification, tenantId);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
// Return success response
|
// Return success response
|
||||||
return Ok(ApiResponse<object>.SuccessResponse(new { }, $"Document attachment is {message}", 200));
|
return Ok(ApiResponse<object>.SuccessResponse(new { }, $"Document attachment is {message}", 200));
|
||||||
}
|
}
|
||||||
|
@ -33,6 +33,7 @@ namespace MarcoBMS.Services.Controllers
|
|||||||
{
|
{
|
||||||
|
|
||||||
private readonly ApplicationDbContext _context;
|
private readonly ApplicationDbContext _context;
|
||||||
|
private readonly IServiceScopeFactory _serviceScope;
|
||||||
private readonly UserManager<ApplicationUser> _userManager;
|
private readonly UserManager<ApplicationUser> _userManager;
|
||||||
private readonly IEmailSender _emailSender;
|
private readonly IEmailSender _emailSender;
|
||||||
private readonly EmployeeHelper _employeeHelper;
|
private readonly EmployeeHelper _employeeHelper;
|
||||||
@ -47,10 +48,21 @@ namespace MarcoBMS.Services.Controllers
|
|||||||
private readonly Guid tenantId;
|
private readonly Guid tenantId;
|
||||||
|
|
||||||
|
|
||||||
public EmployeeController(UserManager<ApplicationUser> userManager, IEmailSender emailSender,
|
public EmployeeController(IServiceScopeFactory serviceScope,
|
||||||
ApplicationDbContext context, EmployeeHelper employeeHelper, UserHelper userHelper, IConfiguration configuration, ILoggingService logger,
|
UserManager<ApplicationUser> userManager,
|
||||||
IHubContext<MarcoHub> signalR, PermissionServices permission, IProjectServices projectServices, IMapper mapper, GeneralHelper generalHelper)
|
IEmailSender emailSender,
|
||||||
|
ApplicationDbContext context,
|
||||||
|
EmployeeHelper employeeHelper,
|
||||||
|
UserHelper userHelper,
|
||||||
|
IConfiguration configuration,
|
||||||
|
ILoggingService logger,
|
||||||
|
IHubContext<MarcoHub> signalR,
|
||||||
|
PermissionServices permission,
|
||||||
|
IProjectServices projectServices,
|
||||||
|
IMapper mapper,
|
||||||
|
GeneralHelper generalHelper)
|
||||||
{
|
{
|
||||||
|
_serviceScope = serviceScope;
|
||||||
_context = context;
|
_context = context;
|
||||||
_userManager = userManager;
|
_userManager = userManager;
|
||||||
_emailSender = emailSender;
|
_emailSender = emailSender;
|
||||||
@ -503,6 +515,7 @@ namespace MarcoBMS.Services.Controllers
|
|||||||
existingEmployee.LastName = model.LastName;
|
existingEmployee.LastName = model.LastName;
|
||||||
existingEmployee.Gender = model.Gender;
|
existingEmployee.Gender = model.Gender;
|
||||||
existingEmployee.PhoneNumber = model.PhoneNumber;
|
existingEmployee.PhoneNumber = model.PhoneNumber;
|
||||||
|
existingEmployee.JoiningDate = model.JoiningDate;
|
||||||
existingEmployee.JobRoleId = model.JobRoleId;
|
existingEmployee.JobRoleId = model.JobRoleId;
|
||||||
existingEmployee.Photo = imageBytes;
|
existingEmployee.Photo = imageBytes;
|
||||||
|
|
||||||
@ -518,6 +531,8 @@ namespace MarcoBMS.Services.Controllers
|
|||||||
[HttpDelete("{id}")]
|
[HttpDelete("{id}")]
|
||||||
public async Task<IActionResult> SuspendEmployee(Guid id, [FromQuery] bool active = false)
|
public async Task<IActionResult> SuspendEmployee(Guid id, [FromQuery] bool active = false)
|
||||||
{
|
{
|
||||||
|
using var scope = _serviceScope.CreateScope();
|
||||||
|
|
||||||
Guid tenantId = _userHelper.GetTenantId();
|
Guid tenantId = _userHelper.GetTenantId();
|
||||||
var LoggedEmployee = await _userHelper.GetCurrentEmployeeAsync();
|
var LoggedEmployee = await _userHelper.GetCurrentEmployeeAsync();
|
||||||
Employee? employee = await _context.Employees.FirstOrDefaultAsync(e => e.Id == id && e.TenantId == tenantId);
|
Employee? employee = await _context.Employees.FirstOrDefaultAsync(e => e.Id == id && e.TenantId == tenantId);
|
||||||
@ -601,6 +616,19 @@ namespace MarcoBMS.Services.Controllers
|
|||||||
_logger.LogInfo("Application role mapping associated with employee ID {EmployeeId} has been removed.", employee.Id);
|
_logger.LogInfo("Application role mapping associated with employee ID {EmployeeId} has been removed.", employee.Id);
|
||||||
}
|
}
|
||||||
_logger.LogInfo("Employee with ID {EmployeId} Deleted successfully", employee.Id);
|
_logger.LogInfo("Employee with ID {EmployeId} Deleted successfully", employee.Id);
|
||||||
|
|
||||||
|
var _firebase = scope.ServiceProvider.GetRequiredService<IFirebaseService>();
|
||||||
|
|
||||||
|
_ = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
// --- Push Notification Section ---
|
||||||
|
// This section attempts to send a test push notification to the user's device.
|
||||||
|
// It's designed to fail gracefully and handle invalid Firebase Cloud Messaging (FCM) tokens.
|
||||||
|
|
||||||
|
await _firebase.SendEmployeeSuspendMessageAsync(employee.Id, tenantId);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -9,6 +9,7 @@ using Marco.Pms.Model.ViewModels.Activities;
|
|||||||
using Marco.Pms.Services.Helpers;
|
using Marco.Pms.Services.Helpers;
|
||||||
using Marco.Pms.Services.Hubs;
|
using Marco.Pms.Services.Hubs;
|
||||||
using Marco.Pms.Services.Service;
|
using Marco.Pms.Services.Service;
|
||||||
|
using Marco.Pms.Services.Service.ServiceInterfaces;
|
||||||
using MarcoBMS.Services.Helpers;
|
using MarcoBMS.Services.Helpers;
|
||||||
using MarcoBMS.Services.Service;
|
using MarcoBMS.Services.Service;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
@ -33,9 +34,10 @@ namespace MarcoBMS.Services.Controllers
|
|||||||
private readonly IHubContext<MarcoHub> _signalR;
|
private readonly IHubContext<MarcoHub> _signalR;
|
||||||
private readonly CacheUpdateHelper _cache;
|
private readonly CacheUpdateHelper _cache;
|
||||||
private readonly PermissionServices _permissionServices;
|
private readonly PermissionServices _permissionServices;
|
||||||
|
private readonly IFirebaseService _firebase;
|
||||||
|
|
||||||
public TaskController(ApplicationDbContext context, UserHelper userHelper, S3UploadService s3Service, ILoggingService logger, PermissionServices permissionServices,
|
public TaskController(ApplicationDbContext context, UserHelper userHelper, S3UploadService s3Service, ILoggingService logger, PermissionServices permissionServices,
|
||||||
IHubContext<MarcoHub> signalR, CacheUpdateHelper cache)
|
IHubContext<MarcoHub> signalR, CacheUpdateHelper cache, IFirebaseService firebase)
|
||||||
{
|
{
|
||||||
_context = context;
|
_context = context;
|
||||||
_userHelper = userHelper;
|
_userHelper = userHelper;
|
||||||
@ -44,6 +46,7 @@ namespace MarcoBMS.Services.Controllers
|
|||||||
_signalR = signalR;
|
_signalR = signalR;
|
||||||
_cache = cache;
|
_cache = cache;
|
||||||
_permissionServices = permissionServices;
|
_permissionServices = permissionServices;
|
||||||
|
_firebase = firebase;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Guid GetTenantId()
|
private Guid GetTenantId()
|
||||||
@ -66,28 +69,28 @@ namespace MarcoBMS.Services.Controllers
|
|||||||
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid data", errors, 400));
|
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid data", errors, 400));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retrieve tenant and employee context
|
// Retrieve tenant and loggedInEmployee context
|
||||||
var tenantId = GetTenantId();
|
var tenantId = GetTenantId();
|
||||||
var employee = await _userHelper.GetCurrentEmployeeAsync();
|
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
|
||||||
|
|
||||||
// Check for permission to approve tasks
|
// Check for permission to approve tasks
|
||||||
var hasPermission = await _permissionServices.HasPermission(PermissionsMaster.AssignAndReportProgress, employee.Id);
|
var hasPermission = await _permissionServices.HasPermission(PermissionsMaster.AssignAndReportProgress, loggedInEmployee.Id);
|
||||||
if (!hasPermission)
|
if (!hasPermission)
|
||||||
{
|
{
|
||||||
_logger.LogWarning("Employee {EmployeeId} attempted to assign Task without permission", employee.Id);
|
_logger.LogWarning("Employee {EmployeeId} attempted to assign Task without permission", loggedInEmployee.Id);
|
||||||
return StatusCode(403, ApiResponse<object>.ErrorResponse("You don't have access", "User not authorized to approve tasks", 403));
|
return StatusCode(403, ApiResponse<object>.ErrorResponse("You don't have access", "User not authorized to approve tasks", 403));
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.LogInfo("Employee {EmployeeId} is assigning a new task", employee.Id);
|
_logger.LogInfo("Employee {EmployeeId} is assigning a new task", loggedInEmployee.Id);
|
||||||
|
|
||||||
// Convert DTO to entity and save TaskAllocation
|
// Convert DTO to entity and save TaskAllocation
|
||||||
var taskAllocation = assignTask.ToTaskAllocationFromAssignTaskDto(employee.Id, tenantId);
|
var taskAllocation = assignTask.ToTaskAllocationFromAssignTaskDto(loggedInEmployee.Id, tenantId);
|
||||||
_context.TaskAllocations.Add(taskAllocation);
|
_context.TaskAllocations.Add(taskAllocation);
|
||||||
await _context.SaveChangesAsync();
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
await _cache.UpdatePlannedAndCompleteWorksInWorkItem(taskAllocation.WorkItemId, todaysAssigned: taskAllocation.PlannedTask);
|
await _cache.UpdatePlannedAndCompleteWorksInWorkItem(taskAllocation.WorkItemId, todaysAssigned: taskAllocation.PlannedTask);
|
||||||
|
|
||||||
_logger.LogInfo("Task {TaskId} assigned by Employee {EmployeeId}", taskAllocation.Id, employee.Id);
|
_logger.LogInfo("Task {TaskId} assigned by Employee {EmployeeId}", taskAllocation.Id, loggedInEmployee.Id);
|
||||||
|
|
||||||
var response = taskAllocation.ToAssignTaskVMFromTaskAllocation();
|
var response = taskAllocation.ToAssignTaskVMFromTaskAllocation();
|
||||||
|
|
||||||
@ -117,6 +120,18 @@ namespace MarcoBMS.Services.Controllers
|
|||||||
var team = employees.Select(e => e.ToBasicEmployeeVMFromEmployee()).ToList();
|
var team = employees.Select(e => e.ToBasicEmployeeVMFromEmployee()).ToList();
|
||||||
response.teamMembers = team;
|
response.teamMembers = team;
|
||||||
|
|
||||||
|
_ = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
// --- Push Notification Section ---
|
||||||
|
// This section attempts to send a test push notification to the user's device.
|
||||||
|
// It's designed to fail gracefully and handle invalid Firebase Cloud Messaging (FCM) tokens.
|
||||||
|
|
||||||
|
var name = $"{loggedInEmployee.FirstName} {loggedInEmployee.LastName}";
|
||||||
|
|
||||||
|
await _firebase.SendAssignTaskMessageAsync(taskAllocation.WorkItemId, name, employeeIds, tenantId);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
return Ok(ApiResponse<object>.SuccessResponse(response, "Task assigned successfully", 200));
|
return Ok(ApiResponse<object>.SuccessResponse(response, "Task assigned successfully", 200));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -146,7 +161,14 @@ namespace MarcoBMS.Services.Controllers
|
|||||||
|
|
||||||
var taskAllocation = await _context.TaskAllocations
|
var taskAllocation = await _context.TaskAllocations
|
||||||
.Include(t => t.WorkItem)
|
.Include(t => t.WorkItem)
|
||||||
.FirstOrDefaultAsync(t => t.Id == reportTask.Id);
|
.ThenInclude(wi => wi!.WorkArea)
|
||||||
|
.ThenInclude(wa => wa!.Floor)
|
||||||
|
.ThenInclude(f => f!.Building)
|
||||||
|
.FirstOrDefaultAsync(t => t.Id == reportTask.Id &&
|
||||||
|
t.WorkItem != null &&
|
||||||
|
t.WorkItem.WorkArea != null &&
|
||||||
|
t.WorkItem.WorkArea.Floor != null &&
|
||||||
|
t.WorkItem.WorkArea.Floor.Building != null);
|
||||||
|
|
||||||
if (taskAllocation == null)
|
if (taskAllocation == null)
|
||||||
{
|
{
|
||||||
@ -278,6 +300,18 @@ namespace MarcoBMS.Services.Controllers
|
|||||||
|
|
||||||
_logger.LogInfo("Task {TaskId} reported successfully by Employee {EmployeeId}", taskAllocation.Id, loggedInEmployee.Id);
|
_logger.LogInfo("Task {TaskId} reported successfully by Employee {EmployeeId}", taskAllocation.Id, loggedInEmployee.Id);
|
||||||
|
|
||||||
|
_ = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
// --- Push Notification Section ---
|
||||||
|
// This section attempts to send a test push notification to the user's device.
|
||||||
|
// It's designed to fail gracefully and handle invalid Firebase Cloud Messaging (FCM) tokens.
|
||||||
|
|
||||||
|
var name = $"{loggedInEmployee.FirstName} {loggedInEmployee.LastName}";
|
||||||
|
|
||||||
|
await _firebase.SendReportTaskMessageAsync(taskAllocation.Id, name, tenantId);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
return Ok(ApiResponse<object>.SuccessResponse(response, "Task reported successfully", 200));
|
return Ok(ApiResponse<object>.SuccessResponse(response, "Task reported successfully", 200));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -378,6 +412,18 @@ namespace MarcoBMS.Services.Controllers
|
|||||||
var notification = new { LoggedInUserId = loggedInEmployee.Id, Keyword = "Task_Comment", NumberOfImages = numberofImages, ProjectId = projectId };
|
var notification = new { LoggedInUserId = loggedInEmployee.Id, Keyword = "Task_Comment", NumberOfImages = numberofImages, ProjectId = projectId };
|
||||||
await _signalR.Clients.All.SendAsync("NotificationEventHandler", notification);
|
await _signalR.Clients.All.SendAsync("NotificationEventHandler", notification);
|
||||||
|
|
||||||
|
_ = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
// --- Push Notification Section ---
|
||||||
|
// This section attempts to send a test push notification to the user's device.
|
||||||
|
// It's designed to fail gracefully and handle invalid Firebase Cloud Messaging (FCM) tokens.
|
||||||
|
|
||||||
|
var name = $"{loggedInEmployee.FirstName} {loggedInEmployee.LastName}";
|
||||||
|
|
||||||
|
await _firebase.SendTaskCommentMessageAsync(taskAllocation.Id, name, tenantId);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
return Ok(ApiResponse<object>.SuccessResponse(response, "Comment saved successfully", 200));
|
return Ok(ApiResponse<object>.SuccessResponse(response, "Comment saved successfully", 200));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -724,16 +770,6 @@ namespace MarcoBMS.Services.Controllers
|
|||||||
"Approved tasks cannot be greater than completed tasks", 400));
|
"Approved tasks cannot be greater than completed tasks", 400));
|
||||||
}
|
}
|
||||||
|
|
||||||
//// Update completed work in the associated work item, if it exists
|
|
||||||
//if (taskAllocation.WorkItem != null && taskAllocation.CompletedTask != approveTask.ApprovedTask)
|
|
||||||
//{
|
|
||||||
// if (taskAllocation.CompletedTask > 0)
|
|
||||||
// {
|
|
||||||
// taskAllocation.WorkItem.CompletedWork -= taskAllocation.CompletedTask;
|
|
||||||
// }
|
|
||||||
// taskAllocation.WorkItem.CompletedWork += approveTask.ApprovedTask;
|
|
||||||
//}
|
|
||||||
|
|
||||||
// Update task allocation details
|
// Update task allocation details
|
||||||
taskAllocation.ApprovedById = loggedInEmployee.Id;
|
taskAllocation.ApprovedById = loggedInEmployee.Id;
|
||||||
taskAllocation.ApprovedDate = DateTime.UtcNow;
|
taskAllocation.ApprovedDate = DateTime.UtcNow;
|
||||||
@ -819,6 +855,18 @@ namespace MarcoBMS.Services.Controllers
|
|||||||
|
|
||||||
_logger.LogInfo("Task {TaskId} successfully approved by Employee {EmployeeId}", approveTask.Id, loggedInEmployee.Id);
|
_logger.LogInfo("Task {TaskId} successfully approved by Employee {EmployeeId}", approveTask.Id, loggedInEmployee.Id);
|
||||||
|
|
||||||
|
_ = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
// --- Push Notification Section ---
|
||||||
|
// This section attempts to send a test push notification to the user's device.
|
||||||
|
// It's designed to fail gracefully and handle invalid Firebase Cloud Messaging (FCM) tokens.
|
||||||
|
|
||||||
|
var name = $"{loggedInEmployee.FirstName} {loggedInEmployee.LastName}";
|
||||||
|
|
||||||
|
await _firebase.SendApproveTaskMessageAsync(taskAllocation.Id, name, tenantId);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
return Ok(ApiResponse<object>.SuccessResponse("Task has been approved", "Task has been approved", 200));
|
return Ok(ApiResponse<object>.SuccessResponse("Task has been approved", "Task has been approved", 200));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
13
Marco.Pms.Services/FireBase/service-account - old.json
Normal file
13
Marco.Pms.Services/FireBase/service-account - old.json
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"type": "service_account",
|
||||||
|
"project_id": "marcopms-mobileapp",
|
||||||
|
"private_key_id": "5ee56ae12fbbfd95f46c613db3aa966fe26fe1b6",
|
||||||
|
"private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDJrORK61zoPVTw\nr8TkLbgV9qejyTD6OP67fMsxgJDSr8Fq6AJxKNfIMD+RhH44/etUeoHMDoYXQY5k\nu9sRaHnh1Hk62FJSm4SnhwwMdjVZT4xakuq4cfWfXBu3lQJHXfZEXJIjTwLr3Jb4\nryWVndEAI0/mT2drJc3riGLYCDEf4A79RXAzqGqg4A539JJ5+3zAtqepTbGpZ/cO\nJOmLP8k27Pm7lvuiyl4f16Xw0V00s1RCnFJIyBrrCqtCT5vTWdZ7a5ka10HgCFlW\nPUwjfB7b1rqSXmxsCOuRma2fQYc0cIhnFvXh71pC4kP0/V9K6cWxS97i1URU0hhi\nVOPoZ+j3AgMBAAECggEAOfUXvngZQRyvFmRM/w4sgxNZZfZhvuc2PYdFlbpO5F1i\nBmkamo6URJGpExayd4pxYNu8BXp/CpvqYgSilkQiEsZO+JxGPDs5SjPDQKmP91Sn\nDzh9f/gwEFYWGRIXj47vQQIhdUg1nLbOJDWhZXfvIk0DnzpejCpXHUMatN7Vz0TA\nAfj9mMcptXBNtLKl59sDkkscEj3Uf/s0d//jrhhEOsyid+slgIpRpQzgHQBavi0d\nhT/aVnBE9NiCgkZARVjcljIXTg24rYrARHYjsN066WdOF2uVnUNrxOgLQfzE7ZOe\nWzF0PDWyJe4FBjabIOHqFw4Nt3j2EakXUJ6Q/PVukQKBgQDvj2mv+0myeP5Wc5Cu\nW9BVrE41Q8nBq0D5CTIyklAP7SVOakmzjzbRV2rjL3Gnzo27tK3aUQjetTMYv5fk\nUUMJkzGvpgJW44qtULHP5gvkaPjV18CwvQv8KyyEOnF7uWkXquJ+9nfrzDaocx/X\nA1wx3Csvd/tTePSCY0uBCIMCFQKBgQDXg+tAiTUesGB3YpS77oc1XHdB9j+/anzx\n2e/PcGMzY2BZdNZ23avS0dnWfkZ1Eocxma8UP+okIvSyMpD4kSlJeya56HZU748E\nvJM7HlqCuYRvVbXVAiHrdC87eKhCzA9MNwBpy0fTbuudaDU2Z9WhoRgHqnte8vvw\nLCNTcKXd2wKBgQCT+cBM5in1xmtEt4ntSeV8pjyBBmh/6urtadLKDjrKO7BJqbnw\n4kv4L8lkoA/SmfJOuiKRsnCKMN9pMCAA9nk0VungF+lmBpPIzwmm4/EAnB7o6Kas\nBXp7v6d13ivvQu45omLaDiCxVKmGj+ZhCEBQxDEg1zo1q4dNa0xeXgWeqQKBgC0Z\n41qPHDm+6YEydTPbGBqXrjF0qiSR0XH/jMsZlvkDG/+8jsEzZKjq166moHIRnY9I\nvTX8pjBHzHOaV3JdVomVJyaSumjN9V0lZZ5inMhssIVoJ3RbTOPsXZIRjwzjjXQC\nsqhxLSfXN6GqVDB9jFyVzOSVzdmx+f1qDz5//YYvAoGAKPZazVtNAXf1/qgBEivE\ncSFaGhdcw35Pwk9CNwKtRT2DFOVQEvd18xY5Naqnm6thYv+UUEAQzGS/6mBUXsOi\n7nDsEN2P40o/eR6x10ZExwsqzIeOTd16fRiQyoMIEOQ7bkGO4KeZ8zAeqKelQxhU\njigmeDSw8y+HfTx74P7yAtI=\n-----END PRIVATE KEY-----\n",
|
||||||
|
"client_email": "firebase-adminsdk-fbsvc@marcopms-mobileapp.iam.gserviceaccount.com",
|
||||||
|
"client_id": "107885185225751718454",
|
||||||
|
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
|
||||||
|
"token_uri": "https://oauth2.googleapis.com/token",
|
||||||
|
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
|
||||||
|
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-fbsvc%40marcopms-mobileapp.iam.gserviceaccount.com",
|
||||||
|
"universe_domain": "googleapis.com"
|
||||||
|
}
|
13
Marco.Pms.Services/FireBase/service-account.json
Normal file
13
Marco.Pms.Services/FireBase/service-account.json
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"type": "service_account",
|
||||||
|
"project_id": "mtest-a0635",
|
||||||
|
"private_key_id": "39a69f7d2a64234784e0d0ce6c113052296d6dc1",
|
||||||
|
"private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCimbxktO7PeQ4h\n81Ye2ZBcZjltDhqqD0o9XyLmNdHszzM056bwpJkvgoyyTJAIvR2fcBF3YQFyuC+1\nddLHtchP48FjflZ+zZzLp7oaA/Zh28OZLbCsu+Nm8vO3WJVoIaJYgi+jEz21G128\ncOIbgkKIpLMz1wQhPPOwDTuSdQ+WajWJb04/aNrmTRH1hMreyhHiIFmalcavUgc1\nY5FvgrGs7EaKjYBevoFN3dwmEXjfyHjfBSxnt1yytl9tbtINqdrYLYAMm1l3+KqO\nCGxicQE5kjI1osI2wRjsk105RHnpxPg2GZnI4vTIOkEY5czhRSOs94g2d628H6fq\nVzf9UqwtAgMBAAECggEARluLf3AjHbdd/CbVDwhJRRIeqye9NfTjxOaTrVWAfp2x\npKTQQbSXbE1rIAOtF3rthH3zsNpSzBcS3cwb5rqr8JW2qpySRNAnlp//ER7Bz9pO\nKsvwdO3gGj3qY117WNGk8/NxNXkv7FvpFY8q54hXzdSmjjnt2YwMThOLwXXRxt2B\nFxN3FpBWqw12epqS162nW2nIRJ34Jloil4J5x61Sc79MCFyCxyhMlrBkY+Ni/xb1\nigBXBjczxNiJqqDie0mc16WB1HMEcBP9Yjtb46Hhfs3NDDWNqDkoM8QmEMSg8EHy\nyjcSlf0Wj8I9Kf+0PZo+2FB2DbuhfA8IVR9U/c00KQKBgQDd/OULx6QpmUev1Gl/\nrwwN67ZUMJ72cRuwvLFsMTIzZ+oItO0AR1uMkRZ1crOMc490XNUvSCGP6piZQAn1\nro8qNAh+0Q/UvKHM1khOj/4DxEGZRnNOhe6QLZM9QNygENuEYfdYDD9wcQI9Xs+B\nMIOBsuuqUVHlsbvYkeYNS8M8swKBgQC7g3i1dYRC/bkNMthVS4GTlFRuLscyIjTi\nhruhdaSE+fBZ5RO3XDzz6oDHYcdo/z5ySqI7EIsckNRbwFsMCOjSP3xJapadPYwU\nIhZBU7lgNlPnHJ/BIUwA5JZqRqGTNWrFINUHZFp2RK/x2bYdfoqY8bq08eWs9gmR\nc7U7i+6jnwKBgGaO3isxExD89fewBQWuk70it1vyEp785rQimT3JBM5nJeLb49sL\nHKq2pU+hrH4pLY+vC/cKNidNVS8IPRG6kf4HiB0+7Td15rLCFSnmsI6A72Wm/MK8\ncdk+lRXpj4SMBT8GG8Yb8ns6WrSLxwaCqV8UkHhhlZqvIIAP998Qr6StAoGAQTwr\n8nU/3k6G4qCdwo7SNZWVCgAcLMTZwTU+cZ2L7vdFNwELKu9cBT/ALZ1G0rB5+Skd\n546J1xZLyt/QzQ8McJjFlIUQgQO4iAiT1YZbJ62+4tiCe54p4uWjrrWD4MLkslAJ\nzNiM4DhlPa6QPRKZBTyTx/+f99xg18l5c43rJ+ECgYBkXMfjdn8SOaG6ggJrf1xx\nas49vwAscx4AJaOdVu3D8lCwoNCuAJhBHcFqsJ0wEHWpsqKAdXxqX/Nt2x8t7zL0\nPoRCvfsq5P7GdRrNhrHxLwjDqh+OS+Ow6t0esPQ5RPBgtjvthAlb7bV2nIfkpmdl\nFbjML8vkXk9iPJsbAfO2jw==\n-----END PRIVATE KEY-----\n",
|
||||||
|
"client_email": "firebase-adminsdk-fbsvc@mtest-a0635.iam.gserviceaccount.com",
|
||||||
|
"client_id": "111097905744982732087",
|
||||||
|
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
|
||||||
|
"token_uri": "https://oauth2.googleapis.com/token",
|
||||||
|
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
|
||||||
|
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-fbsvc%40mtest-a0635.iam.gserviceaccount.com",
|
||||||
|
"universe_domain": "googleapis.com"
|
||||||
|
}
|
@ -13,6 +13,7 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="AutoMapper" Version="13.0.1" />
|
<PackageReference Include="AutoMapper" Version="13.0.1" />
|
||||||
<PackageReference Include="AWSSDK.S3" Version="3.7.416.13" />
|
<PackageReference Include="AWSSDK.S3" Version="3.7.416.13" />
|
||||||
|
<PackageReference Include="FirebaseAdmin" Version="3.3.0" />
|
||||||
<PackageReference Include="MailKit" Version="4.9.0" />
|
<PackageReference Include="MailKit" Version="4.9.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="7.0.20" />
|
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="7.0.20" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.12" />
|
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.12" />
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
using Marco.Pms.CacheHelper;
|
using Marco.Pms.CacheHelper;
|
||||||
|
using FirebaseAdmin;
|
||||||
|
using Google.Apis.Auth.OAuth2;
|
||||||
using Marco.Pms.DataAccess.Data;
|
using Marco.Pms.DataAccess.Data;
|
||||||
using Marco.Pms.Helpers;
|
using Marco.Pms.Helpers;
|
||||||
using Marco.Pms.Helpers.CacheHelper;
|
using Marco.Pms.Helpers.CacheHelper;
|
||||||
@ -179,6 +181,7 @@ builder.Services.AddScoped<IProjectServices, ProjectServices>();
|
|||||||
builder.Services.AddScoped<IExpensesService, ExpensesService>();
|
builder.Services.AddScoped<IExpensesService, ExpensesService>();
|
||||||
builder.Services.AddScoped<IMasterService, MasterService>();
|
builder.Services.AddScoped<IMasterService, MasterService>();
|
||||||
builder.Services.AddScoped<IDirectoryService, DirectoryService>();
|
builder.Services.AddScoped<IDirectoryService, DirectoryService>();
|
||||||
|
builder.Services.AddScoped<IFirebaseService, FirebaseService>();
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Helpers
|
#region Helpers
|
||||||
@ -202,6 +205,13 @@ builder.Services.AddScoped<SidebarMenuHelper>();
|
|||||||
|
|
||||||
// Singleton services (one instance for the app's lifetime)
|
// Singleton services (one instance for the app's lifetime)
|
||||||
builder.Services.AddSingleton<ILoggingService, LoggingService>();
|
builder.Services.AddSingleton<ILoggingService, LoggingService>();
|
||||||
|
|
||||||
|
string path = Path.Combine(builder.Environment.ContentRootPath, "FireBase", "service-account.json");
|
||||||
|
|
||||||
|
FirebaseApp.Create(new AppOptions()
|
||||||
|
{
|
||||||
|
Credential = GoogleCredential.FromFile(path),
|
||||||
|
});
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Web Server (Kestrel)
|
#region Web Server (Kestrel)
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using AutoMapper;
|
using AutoMapper;
|
||||||
|
using FirebaseAdmin.Messaging;
|
||||||
using Marco.Pms.DataAccess.Data;
|
using Marco.Pms.DataAccess.Data;
|
||||||
using Marco.Pms.Helpers.Utility;
|
using Marco.Pms.Helpers.Utility;
|
||||||
using Marco.Pms.Model.Directory;
|
using Marco.Pms.Model.Directory;
|
||||||
@ -119,15 +120,18 @@ namespace Marco.Pms.Services.Service
|
|||||||
|
|
||||||
// --- Advanced Filtering from 'filter' parameter ---
|
// --- Advanced Filtering from 'filter' parameter ---
|
||||||
ContactFilterDto? contactFilter = TryDeserializeContactFilter(filter);
|
ContactFilterDto? contactFilter = TryDeserializeContactFilter(filter);
|
||||||
if (contactFilter?.BucketIds?.Any() ?? false)
|
if (contactFilter != null)
|
||||||
{
|
{
|
||||||
// Note: Permission filtering is already applied. Here we further restrict by the user's filter choice.
|
if (contactFilter.BucketIds?.Any() ?? false)
|
||||||
contactQuery = contactQuery.Where(c => dbContext.ContactBucketMappings.Any(cbm =>
|
{
|
||||||
cbm.ContactId == c.Id && contactFilter.BucketIds.Contains(cbm.BucketId)));
|
// Note: Permission filtering is already applied. Here we further restrict by the user's filter choice.
|
||||||
}
|
contactQuery = contactQuery.Where(c => dbContext.ContactBucketMappings.Any(cbm =>
|
||||||
if (contactFilter?.CategoryIds?.Any() ?? false)
|
cbm.ContactId == c.Id && contactFilter.BucketIds.Contains(cbm.BucketId)));
|
||||||
{
|
}
|
||||||
contactQuery = contactQuery.Where(c => c.ContactCategoryId.HasValue && contactFilter.CategoryIds.Contains(c.ContactCategoryId.Value));
|
if (contactFilter.CategoryIds?.Any() ?? false)
|
||||||
|
{
|
||||||
|
contactQuery = contactQuery.Where(c => c.ContactCategoryId.HasValue && contactFilter.CategoryIds.Contains(c.ContactCategoryId.Value));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Standard Filtering ---
|
// --- Standard Filtering ---
|
||||||
@ -935,6 +939,7 @@ namespace Marco.Pms.Services.Service
|
|||||||
/// <returns>An ApiResponse containing the newly created contact's view model or an error.</returns>
|
/// <returns>An ApiResponse containing the newly created contact's view model or an error.</returns>
|
||||||
public async Task<ApiResponse<object>> CreateContactAsync(CreateContactDto createContact, Guid tenantId, Employee loggedInEmployee)
|
public async Task<ApiResponse<object>> CreateContactAsync(CreateContactDto createContact, Guid tenantId, Employee loggedInEmployee)
|
||||||
{
|
{
|
||||||
|
using var scope = _serviceScopeFactory.CreateScope();
|
||||||
Guid loggedInEmployeeId = loggedInEmployee.Id;
|
Guid loggedInEmployeeId = loggedInEmployee.Id;
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(createContact.Name) ||
|
if (string.IsNullOrWhiteSpace(createContact.Name) ||
|
||||||
@ -951,6 +956,10 @@ namespace Marco.Pms.Services.Service
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var contact = _mapper.Map<Contact>(createContact);
|
var contact = _mapper.Map<Contact>(createContact);
|
||||||
|
if (string.IsNullOrWhiteSpace(createContact.Name))
|
||||||
|
{
|
||||||
|
contact.Description = string.Empty;
|
||||||
|
}
|
||||||
contact.CreatedAt = DateTime.UtcNow;
|
contact.CreatedAt = DateTime.UtcNow;
|
||||||
contact.CreatedById = loggedInEmployeeId;
|
contact.CreatedById = loggedInEmployeeId;
|
||||||
contact.TenantId = tenantId;
|
contact.TenantId = tenantId;
|
||||||
@ -1007,6 +1016,24 @@ namespace Marco.Pms.Services.Service
|
|||||||
contactVM.BucketIds = contactBucketMappings.Select(cb => cb.BucketId).ToList();
|
contactVM.BucketIds = contactBucketMappings.Select(cb => cb.BucketId).ToList();
|
||||||
contactVM.ProjectIds = projectMappings.Select(cp => cp.ProjectId).ToList();
|
contactVM.ProjectIds = projectMappings.Select(cp => cp.ProjectId).ToList();
|
||||||
|
|
||||||
|
var _firebase = scope.ServiceProvider.GetRequiredService<IFirebaseService>();
|
||||||
|
_ = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
// --- Push Notification Section ---
|
||||||
|
// This section attempts to send a test push notification to the user's device.
|
||||||
|
// It's designed to fail gracefully and handle invalid Firebase Cloud Messaging (FCM) tokens.
|
||||||
|
var name = $"{loggedInEmployee.FirstName} {loggedInEmployee.LastName}";
|
||||||
|
|
||||||
|
var notification = new Notification
|
||||||
|
{
|
||||||
|
Title = "New Contact Created",
|
||||||
|
Body = $"New Contact \"{contact.Name}\" is created by {name} in your bucket"
|
||||||
|
};
|
||||||
|
|
||||||
|
await _firebase.SendContactAsync(contact.Id, contactVM.BucketIds, notification, tenantId);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
return ApiResponse<object>.SuccessResponse(contactVM, "Contact created successfully.", 201);
|
return ApiResponse<object>.SuccessResponse(contactVM, "Contact created successfully.", 201);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@ -1022,6 +1049,7 @@ namespace Marco.Pms.Services.Service
|
|||||||
|
|
||||||
public async Task<ApiResponse<object>> UpdateContactAsync(Guid id, UpdateContactDto updateContact, Guid tenantId, Employee loggedInEmployee)
|
public async Task<ApiResponse<object>> UpdateContactAsync(Guid id, UpdateContactDto updateContact, Guid tenantId, Employee loggedInEmployee)
|
||||||
{
|
{
|
||||||
|
using var scope = _serviceScopeFactory.CreateScope();
|
||||||
if (updateContact == null)
|
if (updateContact == null)
|
||||||
{
|
{
|
||||||
_logger.LogWarning("Employee {EmployeeId} sent empty payload for updating contact.", loggedInEmployee.Id);
|
_logger.LogWarning("Employee {EmployeeId} sent empty payload for updating contact.", loggedInEmployee.Id);
|
||||||
@ -1093,6 +1121,14 @@ namespace Marco.Pms.Services.Service
|
|||||||
|
|
||||||
// Update the main contact object from DTO
|
// Update the main contact object from DTO
|
||||||
var updatedContact = _mapper.Map<Contact>(updateContact);
|
var updatedContact = _mapper.Map<Contact>(updateContact);
|
||||||
|
if (string.IsNullOrWhiteSpace(updateContact.Designation))
|
||||||
|
{
|
||||||
|
updatedContact.Designation = string.Empty;
|
||||||
|
}
|
||||||
|
if (string.IsNullOrWhiteSpace(updateContact.Description))
|
||||||
|
{
|
||||||
|
updatedContact.Description = string.Empty;
|
||||||
|
}
|
||||||
updatedContact.TenantId = tenantId;
|
updatedContact.TenantId = tenantId;
|
||||||
updatedContact.CreatedAt = contact.CreatedAt;
|
updatedContact.CreatedAt = contact.CreatedAt;
|
||||||
updatedContact.CreatedById = contact.CreatedById;
|
updatedContact.CreatedById = contact.CreatedById;
|
||||||
@ -1454,6 +1490,28 @@ namespace Marco.Pms.Services.Service
|
|||||||
var contactVM = responseTask.Result;
|
var contactVM = responseTask.Result;
|
||||||
|
|
||||||
_logger.LogInfo("Contact {ContactId} successfully updated by employee {EmployeeId}.", contact.Id, loggedInEmployee.Id);
|
_logger.LogInfo("Contact {ContactId} successfully updated by employee {EmployeeId}.", contact.Id, loggedInEmployee.Id);
|
||||||
|
var _firebase = scope.ServiceProvider.GetRequiredService<IFirebaseService>();
|
||||||
|
|
||||||
|
_ = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
// --- Push Notification Section ---
|
||||||
|
// This section attempts to send a test push notification to the user's device.
|
||||||
|
// It's designed to fail gracefully and handle invalid Firebase Cloud Messaging (FCM) tokens.
|
||||||
|
|
||||||
|
if (contactVM.BucketIds?.Any() ?? false)
|
||||||
|
{
|
||||||
|
var name = $"{loggedInEmployee.FirstName} {loggedInEmployee.LastName}";
|
||||||
|
|
||||||
|
var notification = new Notification
|
||||||
|
{
|
||||||
|
Title = $"Contact Updated - \"{contact.Name}\"",
|
||||||
|
Body = $"Contact \"{contact.Name}\" is updated by {name} in your bucket"
|
||||||
|
};
|
||||||
|
|
||||||
|
await _firebase.SendContactAsync(updateContact.Id, contactVM.BucketIds, notification, tenantId);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
return ApiResponse<object>.SuccessResponse(contactVM, "Contact Updated Successfully", 200);
|
return ApiResponse<object>.SuccessResponse(contactVM, "Contact Updated Successfully", 200);
|
||||||
}
|
}
|
||||||
@ -1471,6 +1529,15 @@ namespace Marco.Pms.Services.Service
|
|||||||
return ApiResponse<object>.ErrorResponse("Contact ID is empty", "Contact ID is empty", 400);
|
return ApiResponse<object>.ErrorResponse("Contact ID is empty", "Contact ID is empty", 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var bucketIds = await _context.ContactBucketMappings.Where(cb => cb.ContactId == id).Select(cb => cb.BucketId).ToListAsync();
|
||||||
|
var hasContactAccess = await _context.EmployeeBucketMappings.AnyAsync(eb => bucketIds.Contains(eb.BucketId) && eb.EmployeeId == loggedInEmployee.Id);
|
||||||
|
if (hasContactAccess)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Employee {EmployeeId} does not have permission to delete contact {ContactId}",
|
||||||
|
loggedInEmployee.Id, id);
|
||||||
|
return ApiResponse<object>.ErrorResponse("Unauthorized", "You do not have permission", 403);
|
||||||
|
}
|
||||||
|
|
||||||
// Try to find the contact for the given tenant
|
// Try to find the contact for the given tenant
|
||||||
Contact? contact = await _context.Contacts.FirstOrDefaultAsync(c => c.Id == id && c.TenantId == tenantId);
|
Contact? contact = await _context.Contacts.FirstOrDefaultAsync(c => c.Id == id && c.TenantId == tenantId);
|
||||||
|
|
||||||
@ -1505,10 +1572,45 @@ namespace Marco.Pms.Services.Service
|
|||||||
|
|
||||||
_logger.LogInfo("Contact ID {ContactId} has been {(DeletedOrActivated)} by Employee ID {EmployeeId}.", id, active ? "activated" : "deleted", loggedInEmployee.Id);
|
_logger.LogInfo("Contact ID {ContactId} has been {(DeletedOrActivated)} by Employee ID {EmployeeId}.", id, active ? "activated" : "deleted", loggedInEmployee.Id);
|
||||||
|
|
||||||
|
using var scope = _serviceScopeFactory.CreateScope();
|
||||||
|
var _firebase = scope.ServiceProvider.GetRequiredService<IFirebaseService>();
|
||||||
|
|
||||||
|
_ = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
// --- Push Notification Section ---
|
||||||
|
// This section attempts to send a test push notification to the user's device.
|
||||||
|
// It's designed to fail gracefully and handle invalid Firebase Cloud Messaging (FCM) tokens.
|
||||||
|
|
||||||
|
if (bucketIds.Any())
|
||||||
|
{
|
||||||
|
var name = $"{loggedInEmployee.FirstName} {loggedInEmployee.LastName}";
|
||||||
|
|
||||||
|
Notification notification;
|
||||||
|
if (active)
|
||||||
|
{
|
||||||
|
notification = new Notification
|
||||||
|
{
|
||||||
|
Title = $"Contact restored - \"{contact.Name}\"",
|
||||||
|
Body = $"Contact \"{contact.Name}\" is restored by {name} in your bucket"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
notification = new Notification
|
||||||
|
{
|
||||||
|
Title = $"Contact Deleted - \"{contact.Name}\"",
|
||||||
|
Body = $"Contact \"{contact.Name}\" is deleted by {name} in your bucket"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
await _firebase.SendContactAsync(contact.Id, bucketIds, notification, tenantId);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
return ApiResponse<object>.SuccessResponse(new { }, active ? "Contact is activated successfully" : "Contact is deleted successfully", 200);
|
return ApiResponse<object>.SuccessResponse(new { }, active ? "Contact is activated successfully" : "Contact is deleted successfully", 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
@ -1929,6 +2031,14 @@ namespace Marco.Pms.Services.Service
|
|||||||
|
|
||||||
try
|
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)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Employee {EmployeeId} does not have permission to delete contact {ContactId}",
|
||||||
|
loggedInEmployee.Id, noteDto.ContactId);
|
||||||
|
return ApiResponse<object>.ErrorResponse("Unauthorized", "You do not have permission", 403);
|
||||||
|
}
|
||||||
// Check if the contact exists and is active for this tenant
|
// Check if the contact exists and is active for this tenant
|
||||||
Contact? contact = await _context.Contacts
|
Contact? contact = await _context.Contacts
|
||||||
.AsNoTracking() // optimization for read-only query
|
.AsNoTracking() // optimization for read-only query
|
||||||
@ -1959,6 +2069,27 @@ namespace Marco.Pms.Services.Service
|
|||||||
"Employee {EmployeeId} successfully added a note (NoteId: {NoteId}) to Contact {ContactId} for Tenant {TenantId}.",
|
"Employee {EmployeeId} successfully added a note (NoteId: {NoteId}) to Contact {ContactId} for Tenant {TenantId}.",
|
||||||
loggedInEmployee.Id, note.Id, contact.Id, tenantId);
|
loggedInEmployee.Id, note.Id, contact.Id, tenantId);
|
||||||
|
|
||||||
|
using var scope = _serviceScopeFactory.CreateScope();
|
||||||
|
var _firebase = scope.ServiceProvider.GetRequiredService<IFirebaseService>();
|
||||||
|
|
||||||
|
_ = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
// --- Push Notification Section ---
|
||||||
|
// This section attempts to send a test push notification to the user's device.
|
||||||
|
// It's designed to fail gracefully and handle invalid Firebase Cloud Messaging (FCM) tokens.
|
||||||
|
|
||||||
|
var name = $"{loggedInEmployee.FirstName} {loggedInEmployee.LastName}";
|
||||||
|
|
||||||
|
var notification = new Notification
|
||||||
|
{
|
||||||
|
Title = $"New note added at Contact - \"{contact.Name}\"",
|
||||||
|
Body = $"New note added at Contact \"{contact.Name}\" by {name} in your bucket"
|
||||||
|
};
|
||||||
|
|
||||||
|
await _firebase.SendContactNoteAsync(contact.Id, bucketIds, notification, tenantId);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
return ApiResponse<object>.SuccessResponse(noteVM, "Note added successfully.", 200);
|
return ApiResponse<object>.SuccessResponse(noteVM, "Note added successfully.", 200);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@ -1990,6 +2121,16 @@ namespace Marco.Pms.Services.Service
|
|||||||
|
|
||||||
return ApiResponse<object>.ErrorResponse("Invalid or empty payload", "Invalid or empty payload", 400);
|
return ApiResponse<object>.ErrorResponse("Invalid or empty payload", "Invalid or empty payload", 400);
|
||||||
}
|
}
|
||||||
|
var (hasAdminPermission, hasManagerPermission, hasUserPermission) = await CheckPermissionsAsync(loggedInEmployee.Id);
|
||||||
|
|
||||||
|
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 (!hasAdminPermission && hasContactAccess)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Employee {EmployeeId} does not have permission to delete contact {ContactId}",
|
||||||
|
loggedInEmployee.Id, noteDto.ContactId);
|
||||||
|
return ApiResponse<object>.ErrorResponse("Unauthorized", "You do not have permission", 403);
|
||||||
|
}
|
||||||
|
|
||||||
// Check if the contact belongs to this tenant
|
// Check if the contact belongs to this tenant
|
||||||
Contact? contact = await _context.Contacts
|
Contact? contact = await _context.Contacts
|
||||||
@ -2061,6 +2202,27 @@ namespace Marco.Pms.Services.Service
|
|||||||
_logger.LogInfo("Employee {EmployeeId} successfully updated Note {NoteId} for Contact {ContactId} at {UpdatedAt}",
|
_logger.LogInfo("Employee {EmployeeId} successfully updated Note {NoteId} for Contact {ContactId} at {UpdatedAt}",
|
||||||
loggedInEmployee.Id, noteVM.Id, contact.Id, noteVM.UpdatedAt);
|
loggedInEmployee.Id, noteVM.Id, contact.Id, noteVM.UpdatedAt);
|
||||||
|
|
||||||
|
using var scope = _serviceScopeFactory.CreateScope();
|
||||||
|
var _firebase = scope.ServiceProvider.GetRequiredService<IFirebaseService>();
|
||||||
|
|
||||||
|
_ = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
// --- Push Notification Section ---
|
||||||
|
// This section attempts to send a test push notification to the user's device.
|
||||||
|
// It's designed to fail gracefully and handle invalid Firebase Cloud Messaging (FCM) tokens.
|
||||||
|
|
||||||
|
var name = $"{loggedInEmployee.FirstName} {loggedInEmployee.LastName}";
|
||||||
|
|
||||||
|
var notification = new Notification
|
||||||
|
{
|
||||||
|
Title = $"Note updated at Contact - \"{contact.Name}\"",
|
||||||
|
Body = $"Note updated at Contact \"{contact.Name}\" by {name} in your bucket"
|
||||||
|
};
|
||||||
|
|
||||||
|
await _firebase.SendContactNoteAsync(contact.Id, bucketIds, notification, tenantId);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
return ApiResponse<object>.SuccessResponse(noteVM, "Note updated successfully", 200);
|
return ApiResponse<object>.SuccessResponse(noteVM, "Note updated successfully", 200);
|
||||||
}
|
}
|
||||||
catch (DbUpdateException ex)
|
catch (DbUpdateException ex)
|
||||||
@ -2103,6 +2265,28 @@ namespace Marco.Pms.Services.Service
|
|||||||
return ApiResponse<object>.ErrorResponse("Note not found", "Note not found", 404);
|
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)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Employee {EmployeeId} does not have permission to delete contact {ContactId}",
|
||||||
|
loggedInEmployee.Id, note.ContactId);
|
||||||
|
return ApiResponse<object>.ErrorResponse("Unauthorized", "You do not have permission", 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the contact belongs to this tenant
|
||||||
|
Contact? contact = await _context.Contacts
|
||||||
|
.AsNoTracking()
|
||||||
|
.FirstOrDefaultAsync(c => c.Id == note.ContactId && c.TenantId == tenantId);
|
||||||
|
|
||||||
|
if (contact == null)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Employee {EmployeeId} attempted to update note {NoteId} for Contact {ContactId}, but the contact was not found in Tenant {TenantId}.",
|
||||||
|
loggedInEmployee.Id, note.Id, note.ContactId, tenantId);
|
||||||
|
|
||||||
|
return ApiResponse<object>.ErrorResponse("Contact not found", "Contact not found", 404);
|
||||||
|
}
|
||||||
|
|
||||||
// Capture old state for audit logging
|
// Capture old state for audit logging
|
||||||
var oldObject = _updateLogsHelper.EntityToBsonDocument(note);
|
var oldObject = _updateLogsHelper.EntityToBsonDocument(note);
|
||||||
|
|
||||||
@ -2146,6 +2330,38 @@ namespace Marco.Pms.Services.Service
|
|||||||
loggedInEmployee.Id, id, currentTime);
|
loggedInEmployee.Id, id, currentTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
using var scope = _serviceScopeFactory.CreateScope();
|
||||||
|
var _firebase = scope.ServiceProvider.GetRequiredService<IFirebaseService>();
|
||||||
|
|
||||||
|
_ = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
// --- Push Notification Section ---
|
||||||
|
// This section attempts to send a test push notification to the user's device.
|
||||||
|
// It's designed to fail gracefully and handle invalid Firebase Cloud Messaging (FCM) tokens.
|
||||||
|
|
||||||
|
var name = $"{loggedInEmployee.FirstName} {loggedInEmployee.LastName}";
|
||||||
|
|
||||||
|
Notification notification;
|
||||||
|
if (active)
|
||||||
|
{
|
||||||
|
notification = new Notification
|
||||||
|
{
|
||||||
|
Title = $"Note restored at Contact - \"{contact.Name}\"",
|
||||||
|
Body = $"Note restored at Contact \"{contact.Name}\" by {name} in your bucket"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
notification = new Notification
|
||||||
|
{
|
||||||
|
Title = $"Note deleted at Contact - \"{contact.Name}\"",
|
||||||
|
Body = $"Note deleted at Contact \"{contact.Name}\" by {name} in your bucket"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
await _firebase.SendContactNoteAsync(contact.Id, bucketIds, notification, tenantId);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
return ApiResponse<object>.SuccessResponse(new { },
|
return ApiResponse<object>.SuccessResponse(new { },
|
||||||
active ? "Note restored successfully" : "Note deleted successfully",
|
active ? "Note restored successfully" : "Note deleted successfully",
|
||||||
200);
|
200);
|
||||||
@ -2264,6 +2480,7 @@ namespace Marco.Pms.Services.Service
|
|||||||
|
|
||||||
_logger.LogInfo("Fetched {BucketCount} buckets for Employee {EmployeeId} successfully", bucketVMs.Count, loggedInEmployee.Id);
|
_logger.LogInfo("Fetched {BucketCount} buckets for Employee {EmployeeId} successfully", bucketVMs.Count, loggedInEmployee.Id);
|
||||||
|
|
||||||
|
|
||||||
return ApiResponse<object>.SuccessResponse(bucketVMs, $"{bucketVMs.Count} buckets fetched successfully", 200);
|
return ApiResponse<object>.SuccessResponse(bucketVMs, $"{bucketVMs.Count} buckets fetched successfully", 200);
|
||||||
}
|
}
|
||||||
public async Task<ApiResponse<object>> CreateBucketAsync(CreateBucketDto bucketDto, Guid tenantId, Employee loggedInEmployee)
|
public async Task<ApiResponse<object>> CreateBucketAsync(CreateBucketDto bucketDto, Guid tenantId, Employee loggedInEmployee)
|
||||||
@ -2337,6 +2554,27 @@ namespace Marco.Pms.Services.Service
|
|||||||
|
|
||||||
_logger.LogInfo("Employee {EmployeeId} successfully created bucket {BucketId}", loggedInEmployee.Id, newBucket.Id);
|
_logger.LogInfo("Employee {EmployeeId} successfully created bucket {BucketId}", loggedInEmployee.Id, newBucket.Id);
|
||||||
|
|
||||||
|
using var scope = _serviceScopeFactory.CreateScope();
|
||||||
|
var _firebase = scope.ServiceProvider.GetRequiredService<IFirebaseService>();
|
||||||
|
|
||||||
|
_ = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
// --- Push Notification Section ---
|
||||||
|
// This section attempts to send a test push notification to the user's device.
|
||||||
|
// It's designed to fail gracefully and handle invalid Firebase Cloud Messaging (FCM) tokens.
|
||||||
|
|
||||||
|
var name = $"{loggedInEmployee.FirstName} {loggedInEmployee.LastName}";
|
||||||
|
|
||||||
|
var notification = new Notification
|
||||||
|
{
|
||||||
|
Title = "New Bucket created",
|
||||||
|
Body = $"New Bucket created \"{newBucket.Name}\" by {name}"
|
||||||
|
};
|
||||||
|
|
||||||
|
await _firebase.SendBucketAsync(newBucket.Id, notification, tenantId);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
return ApiResponse<object>.SuccessResponse(bucketVM, "Bucket created successfully", 200);
|
return ApiResponse<object>.SuccessResponse(bucketVM, "Bucket created successfully", 200);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@ -2427,6 +2665,7 @@ namespace Marco.Pms.Services.Service
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Save changes to bucket and logs
|
// Save changes to bucket and logs
|
||||||
|
_context.Buckets.Update(bucket);
|
||||||
await _context.SaveChangesAsync();
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
// Now load contacts related to the bucket using a separate context for parallelism
|
// Now load contacts related to the bucket using a separate context for parallelism
|
||||||
@ -2449,6 +2688,27 @@ namespace Marco.Pms.Services.Service
|
|||||||
|
|
||||||
_logger.LogInfo("Employee ID {LoggedInEmployeeId} successfully updated bucket ID {BucketId}.", loggedInEmployee.Id, bucket.Id);
|
_logger.LogInfo("Employee ID {LoggedInEmployeeId} successfully updated bucket ID {BucketId}.", loggedInEmployee.Id, bucket.Id);
|
||||||
|
|
||||||
|
using var scope = _serviceScopeFactory.CreateScope();
|
||||||
|
var _firebase = scope.ServiceProvider.GetRequiredService<IFirebaseService>();
|
||||||
|
|
||||||
|
_ = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
// --- Push Notification Section ---
|
||||||
|
// This section attempts to send a test push notification to the user's device.
|
||||||
|
// It's designed to fail gracefully and handle invalid Firebase Cloud Messaging (FCM) tokens.
|
||||||
|
|
||||||
|
var name = $"{loggedInEmployee.FirstName} {loggedInEmployee.LastName}";
|
||||||
|
|
||||||
|
var notification = new Notification
|
||||||
|
{
|
||||||
|
Title = $"Bucket updated - \"{bucket.Name}\"",
|
||||||
|
Body = $"Bucket updated \"{bucket.Name}\" by {name}"
|
||||||
|
};
|
||||||
|
|
||||||
|
await _firebase.SendBucketAsync(bucket.Id, notification, tenantId);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
return ApiResponse<object>.SuccessResponse(bucketVM, "Bucket updated successfully", 200);
|
return ApiResponse<object>.SuccessResponse(bucketVM, "Bucket updated successfully", 200);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@ -2519,6 +2779,9 @@ namespace Marco.Pms.Services.Service
|
|||||||
int assignedEmployeesCount = 0;
|
int assignedEmployeesCount = 0;
|
||||||
int removedEmployeesCount = 0;
|
int removedEmployeesCount = 0;
|
||||||
|
|
||||||
|
List<Guid> assignedEmployeeIds = new List<Guid>();
|
||||||
|
List<Guid> removedEmployeeIds = new List<Guid>();
|
||||||
|
|
||||||
// Process each assignment request
|
// Process each assignment request
|
||||||
foreach (var assignBucket in assignBuckets)
|
foreach (var assignBucket in assignBuckets)
|
||||||
{
|
{
|
||||||
@ -2541,6 +2804,7 @@ namespace Marco.Pms.Services.Service
|
|||||||
};
|
};
|
||||||
_context.EmployeeBucketMappings.Add(newMapping);
|
_context.EmployeeBucketMappings.Add(newMapping);
|
||||||
assignedEmployeesCount++;
|
assignedEmployeesCount++;
|
||||||
|
assignedEmployeeIds.Add(assignBucket.EmployeeId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -2549,6 +2813,7 @@ namespace Marco.Pms.Services.Service
|
|||||||
var existingMapping = employeeBuckets.FirstOrDefault(eb => eb.EmployeeId == assignBucket.EmployeeId);
|
var existingMapping = employeeBuckets.FirstOrDefault(eb => eb.EmployeeId == assignBucket.EmployeeId);
|
||||||
if (existingMapping != null && bucket.CreatedByID != assignBucket.EmployeeId)
|
if (existingMapping != null && bucket.CreatedByID != assignBucket.EmployeeId)
|
||||||
{
|
{
|
||||||
|
removedEmployeeIds.Add(existingMapping.EmployeeId);
|
||||||
_context.EmployeeBucketMappings.Remove(existingMapping);
|
_context.EmployeeBucketMappings.Remove(existingMapping);
|
||||||
removedEmployeesCount++;
|
removedEmployeesCount++;
|
||||||
}
|
}
|
||||||
@ -2607,7 +2872,41 @@ namespace Marco.Pms.Services.Service
|
|||||||
{
|
{
|
||||||
_logger.LogInfo("Employee {EmployeeId} removed {Count} employees from bucket {BucketId}.", loggedInEmployee.Id, removedEmployeesCount, bucketId);
|
_logger.LogInfo("Employee {EmployeeId} removed {Count} employees from bucket {BucketId}.", loggedInEmployee.Id, removedEmployeesCount, bucketId);
|
||||||
}
|
}
|
||||||
|
using var scope = _serviceScopeFactory.CreateScope();
|
||||||
|
var _firebase = scope.ServiceProvider.GetRequiredService<IFirebaseService>();
|
||||||
|
|
||||||
|
_ = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
// --- Push Notification Section ---
|
||||||
|
// This section attempts to send a test push notification to the user's device.
|
||||||
|
// It's designed to fail gracefully and handle invalid Firebase Cloud Messaging (FCM) tokens.
|
||||||
|
|
||||||
|
var name = $"{loggedInEmployee.FirstName} {loggedInEmployee.LastName}";
|
||||||
|
|
||||||
|
if (assignedEmployeeIds.Any())
|
||||||
|
{
|
||||||
|
|
||||||
|
var notification = new Notification
|
||||||
|
{
|
||||||
|
Title = "You have assigned to Bucket",
|
||||||
|
Body = $"You have assigned to bucket \"{bucket.Name}\" by {name}"
|
||||||
|
};
|
||||||
|
|
||||||
|
await _firebase.SendAssignBucketAsync(assignedEmployeeIds, notification, tenantId);
|
||||||
|
}
|
||||||
|
if (removedEmployeeIds.Any())
|
||||||
|
{
|
||||||
|
|
||||||
|
var notification = new Notification
|
||||||
|
{
|
||||||
|
Title = "You have removed from Bucket",
|
||||||
|
Body = $"You have removed from bucket \"{bucket.Name}\" by {name}"
|
||||||
|
};
|
||||||
|
|
||||||
|
await _firebase.SendAssignBucketAsync(removedEmployeeIds, notification, tenantId);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
return ApiResponse<object>.SuccessResponse(bucketVm, "Bucket details updated successfully", 200);
|
return ApiResponse<object>.SuccessResponse(bucketVm, "Bucket details updated successfully", 200);
|
||||||
}
|
}
|
||||||
public async Task<ApiResponse<object>> DeleteBucketAsync(Guid id, Guid tenantId, Employee loggedInEmployee)
|
public async Task<ApiResponse<object>> DeleteBucketAsync(Guid id, Guid tenantId, Employee loggedInEmployee)
|
||||||
@ -2720,6 +3019,27 @@ namespace Marco.Pms.Services.Service
|
|||||||
UpdatedAt = DateTime.UtcNow
|
UpdatedAt = DateTime.UtcNow
|
||||||
}, bucketCollection);
|
}, bucketCollection);
|
||||||
|
|
||||||
|
using var scope = _serviceScopeFactory.CreateScope();
|
||||||
|
var _firebase = scope.ServiceProvider.GetRequiredService<IFirebaseService>();
|
||||||
|
|
||||||
|
_ = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
// --- Push Notification Section ---
|
||||||
|
// This section attempts to send a test push notification to the user's device.
|
||||||
|
// It's designed to fail gracefully and handle invalid Firebase Cloud Messaging (FCM) tokens.
|
||||||
|
|
||||||
|
var name = $"{loggedInEmployee.FirstName} {loggedInEmployee.LastName}";
|
||||||
|
|
||||||
|
var notification = new Notification
|
||||||
|
{
|
||||||
|
Title = $"Bucket deleted - \"{bucket.Name}\"",
|
||||||
|
Body = $"Bucket deleted \"{bucket.Name}\" by {name}"
|
||||||
|
};
|
||||||
|
|
||||||
|
await _firebase.SendBucketAsync(bucket.Id, notification, tenantId);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
return ApiResponse<object>.SuccessResponse(new { }, "Bucket deleted successfully", 200);
|
return ApiResponse<object>.SuccessResponse(new { }, "Bucket deleted successfully", 200);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@ -2739,14 +3059,25 @@ namespace Marco.Pms.Services.Service
|
|||||||
|
|
||||||
private async Task<(bool hasAdmin, bool hasManager, bool hasUser)> CheckPermissionsAsync(Guid employeeId)
|
private async Task<(bool hasAdmin, bool hasManager, bool hasUser)> CheckPermissionsAsync(Guid employeeId)
|
||||||
{
|
{
|
||||||
// Scoping the service provider ensures services are disposed of correctly.
|
|
||||||
using var scope = _serviceScopeFactory.CreateScope();
|
|
||||||
var permissionService = scope.ServiceProvider.GetRequiredService<PermissionServices>();
|
|
||||||
|
|
||||||
// Run all permission checks in parallel.
|
// Run all permission checks in parallel.
|
||||||
var hasAdminTask = permissionService.HasPermission(PermissionsMaster.DirectoryAdmin, employeeId);
|
var hasAdminTask = Task.Run(async () =>
|
||||||
var hasManagerTask = permissionService.HasPermission(PermissionsMaster.DirectoryManager, employeeId);
|
{
|
||||||
var hasUserTask = permissionService.HasPermission(PermissionsMaster.DirectoryUser, employeeId);
|
using var scope = _serviceScopeFactory.CreateScope();
|
||||||
|
var permissionService = scope.ServiceProvider.GetRequiredService<PermissionServices>();
|
||||||
|
return await permissionService.HasPermission(PermissionsMaster.DirectoryAdmin, employeeId);
|
||||||
|
});
|
||||||
|
var hasManagerTask = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
using var scope = _serviceScopeFactory.CreateScope();
|
||||||
|
var permissionService = scope.ServiceProvider.GetRequiredService<PermissionServices>();
|
||||||
|
return await permissionService.HasPermission(PermissionsMaster.DirectoryManager, employeeId);
|
||||||
|
});
|
||||||
|
var hasUserTask = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
using var scope = _serviceScopeFactory.CreateScope();
|
||||||
|
var permissionService = scope.ServiceProvider.GetRequiredService<PermissionServices>();
|
||||||
|
return await permissionService.HasPermission(PermissionsMaster.DirectoryUser, employeeId);
|
||||||
|
});
|
||||||
|
|
||||||
await Task.WhenAll(hasAdminTask, hasManagerTask, hasUserTask);
|
await Task.WhenAll(hasAdminTask, hasManagerTask, hasUserTask);
|
||||||
|
|
||||||
|
@ -525,6 +525,7 @@ namespace Marco.Pms.Services.Service
|
|||||||
if (expenseType == null) validationErrors.Add("Expense Type not found.");
|
if (expenseType == null) validationErrors.Add("Expense Type not found.");
|
||||||
if (paymentMode == null) validationErrors.Add("Payment Mode not found.");
|
if (paymentMode == null) validationErrors.Add("Payment Mode not found.");
|
||||||
if (statusMapping == null) validationErrors.Add("Default status 'Draft' not found.");
|
if (statusMapping == null) validationErrors.Add("Default status 'Draft' not found.");
|
||||||
|
if ((expenseType?.IsAttachmentRequried ?? true) && !(dto.BillAttachments?.Any() ?? false)) validationErrors.Add("Bill Attachment is requried, but not found");
|
||||||
|
|
||||||
if (validationErrors.Any())
|
if (validationErrors.Any())
|
||||||
{
|
{
|
||||||
@ -596,6 +597,9 @@ namespace Marco.Pms.Services.Service
|
|||||||
/// <returns>An ApiResponse containing the updated expense details or an error.</returns>
|
/// <returns>An ApiResponse containing the updated expense details or an error.</returns>
|
||||||
public async Task<ApiResponse<object>> ChangeStatusAsync(ExpenseRecordDto model, Employee loggedInEmployee, Guid tenantId)
|
public async Task<ApiResponse<object>> ChangeStatusAsync(ExpenseRecordDto model, Employee loggedInEmployee, Guid tenantId)
|
||||||
{
|
{
|
||||||
|
using var scope = _serviceScopeFactory.CreateScope();
|
||||||
|
var _firebase = scope.ServiceProvider.GetRequiredService<IFirebaseService>();
|
||||||
|
|
||||||
// 1. Fetch Existing Expense with Related Entities (Single Query)
|
// 1. Fetch Existing Expense with Related Entities (Single Query)
|
||||||
var expense = await _context.Expenses
|
var expense = await _context.Expenses
|
||||||
.Include(e => e.ExpensesType)
|
.Include(e => e.ExpensesType)
|
||||||
@ -670,7 +674,6 @@ namespace Marco.Pms.Services.Service
|
|||||||
}
|
}
|
||||||
else if (requiredPermissions.Any())
|
else if (requiredPermissions.Any())
|
||||||
{
|
{
|
||||||
using var scope = _serviceScopeFactory.CreateScope();
|
|
||||||
var permissionService = scope.ServiceProvider.GetRequiredService<PermissionServices>();
|
var permissionService = scope.ServiceProvider.GetRequiredService<PermissionServices>();
|
||||||
foreach (var permission in requiredPermissions)
|
foreach (var permission in requiredPermissions)
|
||||||
{
|
{
|
||||||
@ -753,6 +756,18 @@ namespace Marco.Pms.Services.Service
|
|||||||
return ApiResponse<object>.ErrorResponse("Expense was modified by another user. Please refresh and try again.", "Concurrency Error", 409);
|
return ApiResponse<object>.ErrorResponse("Expense was modified by another user. Please refresh and try again.", "Concurrency Error", 409);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_ = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
// --- Push Notification Section ---
|
||||||
|
// This section attempts to send a test push notification to the user's device.
|
||||||
|
// It's designed to fail gracefully and handle invalid Firebase Cloud Messaging (FCM) tokens.
|
||||||
|
|
||||||
|
var name = $"{loggedInEmployee.FirstName} {loggedInEmployee.LastName}";
|
||||||
|
|
||||||
|
await _firebase.SendExpenseMessageAsync(expense, name, tenantId);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
// 10. Post-processing (audit log, cache, fetch next states)
|
// 10. Post-processing (audit log, cache, fetch next states)
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
2145
Marco.Pms.Services/Service/FirebaseService.cs
Normal file
2145
Marco.Pms.Services/Service/FirebaseService.cs
Normal file
File diff suppressed because it is too large
Load Diff
@ -222,6 +222,7 @@ namespace Marco.Pms.Services.Service
|
|||||||
Description = "Materials, equipment and supplies purchased for site operations.",
|
Description = "Materials, equipment and supplies purchased for site operations.",
|
||||||
NoOfPersonsRequired = false,
|
NoOfPersonsRequired = false,
|
||||||
IsActive = true,
|
IsActive = true,
|
||||||
|
IsAttachmentRequried = true,
|
||||||
TenantId = tenantId
|
TenantId = tenantId
|
||||||
},
|
},
|
||||||
new ExpensesTypeMaster
|
new ExpensesTypeMaster
|
||||||
@ -231,6 +232,7 @@ namespace Marco.Pms.Services.Service
|
|||||||
Description = "Vehicle fuel, logistics services and delivery of goods or personnel.",
|
Description = "Vehicle fuel, logistics services and delivery of goods or personnel.",
|
||||||
NoOfPersonsRequired = false,
|
NoOfPersonsRequired = false,
|
||||||
IsActive = true,
|
IsActive = true,
|
||||||
|
IsAttachmentRequried = false,
|
||||||
TenantId = tenantId
|
TenantId = tenantId
|
||||||
},
|
},
|
||||||
new ExpensesTypeMaster
|
new ExpensesTypeMaster
|
||||||
@ -240,6 +242,7 @@ namespace Marco.Pms.Services.Service
|
|||||||
Description = "Delivery of personnel.",
|
Description = "Delivery of personnel.",
|
||||||
NoOfPersonsRequired = true,
|
NoOfPersonsRequired = true,
|
||||||
IsActive = true,
|
IsActive = true,
|
||||||
|
IsAttachmentRequried = false,
|
||||||
TenantId = tenantId
|
TenantId = tenantId
|
||||||
},
|
},
|
||||||
new ExpensesTypeMaster
|
new ExpensesTypeMaster
|
||||||
@ -249,6 +252,7 @@ namespace Marco.Pms.Services.Service
|
|||||||
Description = "Site setup costs including equipment deployment and temporary infrastructure.",
|
Description = "Site setup costs including equipment deployment and temporary infrastructure.",
|
||||||
NoOfPersonsRequired = false,
|
NoOfPersonsRequired = false,
|
||||||
IsActive = true,
|
IsActive = true,
|
||||||
|
IsAttachmentRequried = true,
|
||||||
TenantId = tenantId
|
TenantId = tenantId
|
||||||
},
|
},
|
||||||
new ExpensesTypeMaster
|
new ExpensesTypeMaster
|
||||||
@ -258,6 +262,7 @@ namespace Marco.Pms.Services.Service
|
|||||||
Description = " Worker amenities like snacks, meals, safety gear, accommodation, medical support etc.",
|
Description = " Worker amenities like snacks, meals, safety gear, accommodation, medical support etc.",
|
||||||
NoOfPersonsRequired = true,
|
NoOfPersonsRequired = true,
|
||||||
IsActive = true,
|
IsActive = true,
|
||||||
|
IsAttachmentRequried = true,
|
||||||
TenantId = tenantId
|
TenantId = tenantId
|
||||||
},
|
},
|
||||||
new ExpensesTypeMaster
|
new ExpensesTypeMaster
|
||||||
@ -267,6 +272,7 @@ namespace Marco.Pms.Services.Service
|
|||||||
Description = "Machinery servicing, electricity, water, and temporary office needs.",
|
Description = "Machinery servicing, electricity, water, and temporary office needs.",
|
||||||
NoOfPersonsRequired = false,
|
NoOfPersonsRequired = false,
|
||||||
IsActive = true,
|
IsActive = true,
|
||||||
|
IsAttachmentRequried = true,
|
||||||
TenantId = tenantId
|
TenantId = tenantId
|
||||||
},
|
},
|
||||||
new ExpensesTypeMaster
|
new ExpensesTypeMaster
|
||||||
@ -276,6 +282,7 @@ namespace Marco.Pms.Services.Service
|
|||||||
Description = "Scheduled payments for external services or goods.",
|
Description = "Scheduled payments for external services or goods.",
|
||||||
NoOfPersonsRequired = false,
|
NoOfPersonsRequired = false,
|
||||||
IsActive = true,
|
IsActive = true,
|
||||||
|
IsAttachmentRequried = true,
|
||||||
TenantId = tenantId
|
TenantId = tenantId
|
||||||
},
|
},
|
||||||
new ExpensesTypeMaster
|
new ExpensesTypeMaster
|
||||||
@ -285,6 +292,7 @@ namespace Marco.Pms.Services.Service
|
|||||||
Description = "Government fees, insurance, inspections and safety-related expenditures.",
|
Description = "Government fees, insurance, inspections and safety-related expenditures.",
|
||||||
NoOfPersonsRequired = false,
|
NoOfPersonsRequired = false,
|
||||||
IsActive = true,
|
IsActive = true,
|
||||||
|
IsAttachmentRequried = true,
|
||||||
TenantId = tenantId
|
TenantId = tenantId
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -27,16 +27,16 @@ namespace Marco.Pms.Services.Service
|
|||||||
public class ProjectServices : IProjectServices
|
public class ProjectServices : IProjectServices
|
||||||
{
|
{
|
||||||
private readonly IDbContextFactory<ApplicationDbContext> _dbContextFactory;
|
private readonly IDbContextFactory<ApplicationDbContext> _dbContextFactory;
|
||||||
|
private readonly IServiceScopeFactory _serviceScopeFactory;
|
||||||
private readonly ApplicationDbContext _context; // Keeping this for direct scoped context use where appropriate
|
private readonly ApplicationDbContext _context; // Keeping this for direct scoped context use where appropriate
|
||||||
private readonly ILoggingService _logger;
|
private readonly ILoggingService _logger;
|
||||||
private readonly CacheUpdateHelper _cache;
|
private readonly CacheUpdateHelper _cache;
|
||||||
private readonly IMapper _mapper;
|
private readonly IMapper _mapper;
|
||||||
private readonly IServiceScopeFactory _serviceScopeFactory;
|
|
||||||
public ProjectServices(
|
public ProjectServices(
|
||||||
IDbContextFactory<ApplicationDbContext> dbContextFactory,
|
IDbContextFactory<ApplicationDbContext> dbContextFactory,
|
||||||
|
IServiceScopeFactory serviceScopeFactory,
|
||||||
ApplicationDbContext context,
|
ApplicationDbContext context,
|
||||||
ILoggingService logger,
|
ILoggingService logger,
|
||||||
IServiceScopeFactory serviceScopeFactory,
|
|
||||||
CacheUpdateHelper cache,
|
CacheUpdateHelper cache,
|
||||||
IMapper mapper)
|
IMapper mapper)
|
||||||
{
|
{
|
||||||
@ -349,6 +349,9 @@ namespace Marco.Pms.Services.Service
|
|||||||
|
|
||||||
public async Task<ApiResponse<object>> CreateProjectAsync(CreateProjectDto projectDto, Guid tenantId, Employee loggedInEmployee)
|
public async Task<ApiResponse<object>> CreateProjectAsync(CreateProjectDto projectDto, Guid tenantId, Employee loggedInEmployee)
|
||||||
{
|
{
|
||||||
|
using var scope = _serviceScopeFactory.CreateScope();
|
||||||
|
var _firebase = scope.ServiceProvider.GetRequiredService<IFirebaseService>();
|
||||||
|
|
||||||
// 1. Prepare data without I/O
|
// 1. Prepare data without I/O
|
||||||
var loggedInUserId = loggedInEmployee.Id;
|
var loggedInUserId = loggedInEmployee.Id;
|
||||||
var project = _mapper.Map<Project>(projectDto);
|
var project = _mapper.Map<Project>(projectDto);
|
||||||
@ -385,6 +388,18 @@ namespace Marco.Pms.Services.Service
|
|||||||
_logger.LogError(ex, "Project {ProjectId} was created, but a post-creation side-effect (caching/notification) failed. ", project.Id);
|
_logger.LogError(ex, "Project {ProjectId} was created, but a post-creation side-effect (caching/notification) failed. ", project.Id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_ = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
// --- Push Notification Section ---
|
||||||
|
// This section attempts to send a test push notification to the user's device.
|
||||||
|
// It's designed to fail gracefully and handle invalid Firebase Cloud Messaging (FCM) tokens.
|
||||||
|
|
||||||
|
var name = $"{loggedInEmployee.FirstName} {loggedInEmployee.LastName}";
|
||||||
|
|
||||||
|
await _firebase.SendModifyProjectMessageAsync(project, name, false, tenantId);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
// 4. Return a success response to the user as soon as the critical data is saved.
|
// 4. Return a success response to the user as soon as the critical data is saved.
|
||||||
return ApiResponse<object>.SuccessResponse(_mapper.Map<ProjectDto>(project), "Project created successfully.", 200);
|
return ApiResponse<object>.SuccessResponse(_mapper.Map<ProjectDto>(project), "Project created successfully.", 200);
|
||||||
}
|
}
|
||||||
@ -398,9 +413,11 @@ namespace Marco.Pms.Services.Service
|
|||||||
/// <returns>An ApiResponse confirming the update or an appropriate error.</returns>
|
/// <returns>An ApiResponse confirming the update or an appropriate error.</returns>
|
||||||
public async Task<ApiResponse<object>> UpdateProjectAsync(Guid id, UpdateProjectDto updateProjectDto, Guid tenantId, Employee loggedInEmployee)
|
public async Task<ApiResponse<object>> UpdateProjectAsync(Guid id, UpdateProjectDto updateProjectDto, Guid tenantId, Employee loggedInEmployee)
|
||||||
{
|
{
|
||||||
|
using var scope = _serviceScopeFactory.CreateScope();
|
||||||
|
var _firebase = scope.ServiceProvider.GetRequiredService<IFirebaseService>();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using var scope = _serviceScopeFactory.CreateScope();
|
|
||||||
var _permission = scope.ServiceProvider.GetRequiredService<PermissionServices>();
|
var _permission = scope.ServiceProvider.GetRequiredService<PermissionServices>();
|
||||||
// --- Step 1: Fetch the Existing Entity from the Database ---
|
// --- Step 1: Fetch the Existing Entity from the Database ---
|
||||||
// This is crucial to avoid the data loss bug. We only want to modify an existing record.
|
// This is crucial to avoid the data loss bug. We only want to modify an existing record.
|
||||||
@ -451,6 +468,18 @@ namespace Marco.Pms.Services.Service
|
|||||||
// 4a. Update Cache
|
// 4a. Update Cache
|
||||||
await UpdateCacheInBackground(existingProject);
|
await UpdateCacheInBackground(existingProject);
|
||||||
|
|
||||||
|
_ = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
// --- Push Notification Section ---
|
||||||
|
// This section attempts to send a test push notification to the user's device.
|
||||||
|
// It's designed to fail gracefully and handle invalid Firebase Cloud Messaging (FCM) tokens.
|
||||||
|
|
||||||
|
var name = $"{loggedInEmployee.FirstName} {loggedInEmployee.LastName}";
|
||||||
|
|
||||||
|
await _firebase.SendModifyProjectMessageAsync(existingProject, name, true, tenantId);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
// --- Step 5: Return Success Response Immediately ---
|
// --- Step 5: Return Success Response Immediately ---
|
||||||
// The client gets a fast response without waiting for caching or SignalR.
|
// The client gets a fast response without waiting for caching or SignalR.
|
||||||
return ApiResponse<object>.SuccessResponse(projectDto, "Project updated successfully.", 200);
|
return ApiResponse<object>.SuccessResponse(projectDto, "Project updated successfully.", 200);
|
||||||
@ -626,6 +655,9 @@ namespace Marco.Pms.Services.Service
|
|||||||
/// <returns>An ApiResponse containing the list of processed allocations.</returns>
|
/// <returns>An ApiResponse containing the list of processed allocations.</returns>
|
||||||
public async Task<ApiResponse<List<ProjectAllocationVM>>> ManageAllocationAsync(List<ProjectAllocationDot> allocationsDto, Guid tenantId, Employee loggedInEmployee)
|
public async Task<ApiResponse<List<ProjectAllocationVM>>> ManageAllocationAsync(List<ProjectAllocationDot> allocationsDto, Guid tenantId, Employee loggedInEmployee)
|
||||||
{
|
{
|
||||||
|
using var scope = _serviceScopeFactory.CreateScope();
|
||||||
|
var _firebase = scope.ServiceProvider.GetRequiredService<IFirebaseService>();
|
||||||
|
|
||||||
// --- Step 1: Input Validation ---
|
// --- Step 1: Input Validation ---
|
||||||
if (allocationsDto == null || !allocationsDto.Any())
|
if (allocationsDto == null || !allocationsDto.Any())
|
||||||
{
|
{
|
||||||
@ -634,7 +666,6 @@ namespace Marco.Pms.Services.Service
|
|||||||
|
|
||||||
_logger.LogInfo("Starting to manage {AllocationCount} allocations for user {UserId}.", allocationsDto.Count, loggedInEmployee.Id);
|
_logger.LogInfo("Starting to manage {AllocationCount} allocations for user {UserId}.", allocationsDto.Count, loggedInEmployee.Id);
|
||||||
|
|
||||||
using var scope = _serviceScopeFactory.CreateScope();
|
|
||||||
var _permission = scope.ServiceProvider.GetRequiredService<PermissionServices>();
|
var _permission = scope.ServiceProvider.GetRequiredService<PermissionServices>();
|
||||||
|
|
||||||
// --- (Placeholder) Security Check ---
|
// --- (Placeholder) Security Check ---
|
||||||
@ -724,6 +755,17 @@ namespace Marco.Pms.Services.Service
|
|||||||
|
|
||||||
// --- Step 5: Map results and return success ---
|
// --- Step 5: Map results and return success ---
|
||||||
var resultVm = _mapper.Map<List<ProjectAllocationVM>>(processedAllocations);
|
var resultVm = _mapper.Map<List<ProjectAllocationVM>>(processedAllocations);
|
||||||
|
_ = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
// --- Push Notification Section ---
|
||||||
|
// This section attempts to send a test push notification to the user's device.
|
||||||
|
// It's designed to fail gracefully and handle invalid Firebase Cloud Messaging (FCM) tokens.
|
||||||
|
|
||||||
|
var name = $"{loggedInEmployee.FirstName} {loggedInEmployee.LastName}";
|
||||||
|
|
||||||
|
await _firebase.SendProjectAllocationMessageAsync(processedAllocations, name, tenantId);
|
||||||
|
|
||||||
|
});
|
||||||
return ApiResponse<List<ProjectAllocationVM>>.SuccessResponse(resultVm, "Allocations managed successfully.", 200);
|
return ApiResponse<List<ProjectAllocationVM>>.SuccessResponse(resultVm, "Allocations managed successfully.", 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -814,6 +856,9 @@ namespace Marco.Pms.Services.Service
|
|||||||
/// <returns>An ApiResponse containing the list of processed allocations.</returns>
|
/// <returns>An ApiResponse containing the list of processed allocations.</returns>
|
||||||
public async Task<ApiResponse<List<ProjectAllocationVM>>> AssigneProjectsToEmployeeAsync(List<ProjectsAllocationDto> allocationsDto, Guid employeeId, Guid tenantId, Employee loggedInEmployee)
|
public async Task<ApiResponse<List<ProjectAllocationVM>>> AssigneProjectsToEmployeeAsync(List<ProjectsAllocationDto> allocationsDto, Guid employeeId, Guid tenantId, Employee loggedInEmployee)
|
||||||
{
|
{
|
||||||
|
using var scope = _serviceScopeFactory.CreateScope();
|
||||||
|
var _firebase = scope.ServiceProvider.GetRequiredService<IFirebaseService>();
|
||||||
|
|
||||||
// --- Step 1: Input Validation ---
|
// --- Step 1: Input Validation ---
|
||||||
if (allocationsDto == null || !allocationsDto.Any() || employeeId == Guid.Empty)
|
if (allocationsDto == null || !allocationsDto.Any() || employeeId == Guid.Empty)
|
||||||
{
|
{
|
||||||
@ -822,7 +867,6 @@ namespace Marco.Pms.Services.Service
|
|||||||
|
|
||||||
_logger.LogInfo("Starting to manage {AllocationCount} project assignments for Employee {EmployeeId}.", allocationsDto.Count, employeeId);
|
_logger.LogInfo("Starting to manage {AllocationCount} project assignments for Employee {EmployeeId}.", allocationsDto.Count, employeeId);
|
||||||
|
|
||||||
using var scope = _serviceScopeFactory.CreateScope();
|
|
||||||
var _permission = scope.ServiceProvider.GetRequiredService<PermissionServices>();
|
var _permission = scope.ServiceProvider.GetRequiredService<PermissionServices>();
|
||||||
|
|
||||||
// --- (Placeholder) Security Check ---
|
// --- (Placeholder) Security Check ---
|
||||||
@ -913,6 +957,17 @@ namespace Marco.Pms.Services.Service
|
|||||||
|
|
||||||
// --- Step 6: Map results using AutoMapper and return success ---
|
// --- Step 6: Map results using AutoMapper and return success ---
|
||||||
var resultVm = _mapper.Map<List<ProjectAllocationVM>>(processedAllocations);
|
var resultVm = _mapper.Map<List<ProjectAllocationVM>>(processedAllocations);
|
||||||
|
_ = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
// --- Push Notification Section ---
|
||||||
|
// This section attempts to send a test push notification to the user's device.
|
||||||
|
// It's designed to fail gracefully and handle invalid Firebase Cloud Messaging (FCM) tokens.
|
||||||
|
|
||||||
|
var name = $"{loggedInEmployee.FirstName} {loggedInEmployee.LastName}";
|
||||||
|
|
||||||
|
await _firebase.SendProjectAllocationMessageAsync(processedAllocations, name, tenantId);
|
||||||
|
|
||||||
|
});
|
||||||
return ApiResponse<List<ProjectAllocationVM>>.SuccessResponse(resultVm, "Assignments managed successfully.", 200);
|
return ApiResponse<List<ProjectAllocationVM>>.SuccessResponse(resultVm, "Assignments managed successfully.", 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -995,12 +1050,12 @@ namespace Marco.Pms.Services.Service
|
|||||||
{
|
{
|
||||||
_logger.LogInfo("GetInfraDetails called for ProjectId: {ProjectId}", projectId);
|
_logger.LogInfo("GetInfraDetails called for ProjectId: {ProjectId}", projectId);
|
||||||
|
|
||||||
|
using var scope = _serviceScopeFactory.CreateScope();
|
||||||
|
var _generalHelper = scope.ServiceProvider.GetRequiredService<GeneralHelper>();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using var scope = _serviceScopeFactory.CreateScope();
|
|
||||||
var _permission = scope.ServiceProvider.GetRequiredService<PermissionServices>();
|
var _permission = scope.ServiceProvider.GetRequiredService<PermissionServices>();
|
||||||
|
|
||||||
var _generalHelper = scope.ServiceProvider.GetRequiredService<GeneralHelper>();
|
|
||||||
// --- Step 1: Run independent permission checks in PARALLEL ---
|
// --- Step 1: Run independent permission checks in PARALLEL ---
|
||||||
var projectPermissionTask = _permission.HasProjectPermission(loggedInEmployee, projectId);
|
var projectPermissionTask = _permission.HasProjectPermission(loggedInEmployee, projectId);
|
||||||
var viewInfraPermissionTask = Task.Run(async () =>
|
var viewInfraPermissionTask = Task.Run(async () =>
|
||||||
@ -1070,11 +1125,12 @@ namespace Marco.Pms.Services.Service
|
|||||||
{
|
{
|
||||||
_logger.LogInfo("GetWorkItems called for WorkAreaId: {WorkAreaId} by User: {UserId}", workAreaId, loggedInEmployee.Id);
|
_logger.LogInfo("GetWorkItems called for WorkAreaId: {WorkAreaId} by User: {UserId}", workAreaId, loggedInEmployee.Id);
|
||||||
|
|
||||||
|
using var scope = _serviceScopeFactory.CreateScope();
|
||||||
|
var _generalHelper = scope.ServiceProvider.GetRequiredService<GeneralHelper>();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using var scope = _serviceScopeFactory.CreateScope();
|
|
||||||
var _permission = scope.ServiceProvider.GetRequiredService<PermissionServices>();
|
var _permission = scope.ServiceProvider.GetRequiredService<PermissionServices>();
|
||||||
var _generalHelper = scope.ServiceProvider.GetRequiredService<GeneralHelper>();
|
|
||||||
// --- Step 1: Cache-First Strategy ---
|
// --- Step 1: Cache-First Strategy ---
|
||||||
var cachedWorkItems = await _cache.GetWorkItemDetailsByWorkArea(workAreaId);
|
var cachedWorkItems = await _cache.GetWorkItemDetailsByWorkArea(workAreaId);
|
||||||
if (cachedWorkItems != null)
|
if (cachedWorkItems != null)
|
||||||
@ -1261,15 +1317,15 @@ namespace Marco.Pms.Services.Service
|
|||||||
{
|
{
|
||||||
if (item.Building != null)
|
if (item.Building != null)
|
||||||
{
|
{
|
||||||
ProcessBuilding(item.Building, tenantId, responseData, messages, projectIds, cacheUpdateTasks);
|
ProcessBuilding(item.Building, tenantId, responseData, messages, projectIds, cacheUpdateTasks, loggedInEmployee);
|
||||||
}
|
}
|
||||||
if (item.Floor != null)
|
if (item.Floor != null)
|
||||||
{
|
{
|
||||||
ProcessFloor(item.Floor, tenantId, responseData, messages, projectIds, cacheUpdateTasks, buildingsDict);
|
ProcessFloor(item.Floor, tenantId, responseData, messages, projectIds, cacheUpdateTasks, buildingsDict, loggedInEmployee);
|
||||||
}
|
}
|
||||||
if (item.WorkArea != null)
|
if (item.WorkArea != null)
|
||||||
{
|
{
|
||||||
ProcessWorkArea(item.WorkArea, tenantId, responseData, messages, projectIds, cacheUpdateTasks, floorsDict);
|
ProcessWorkArea(item.WorkArea, tenantId, responseData, messages, projectIds, cacheUpdateTasks, floorsDict, loggedInEmployee);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1311,6 +1367,8 @@ namespace Marco.Pms.Services.Service
|
|||||||
_logger.LogInfo("CreateProjectTask called with {Count} items by user {UserId}", workItemDtos?.Count ?? 0, loggedInEmployee.Id);
|
_logger.LogInfo("CreateProjectTask called with {Count} items by user {UserId}", workItemDtos?.Count ?? 0, loggedInEmployee.Id);
|
||||||
|
|
||||||
using var scope = _serviceScopeFactory.CreateScope();
|
using var scope = _serviceScopeFactory.CreateScope();
|
||||||
|
var _firebase = scope.ServiceProvider.GetRequiredService<IFirebaseService>();
|
||||||
|
|
||||||
var _permission = scope.ServiceProvider.GetRequiredService<PermissionServices>();
|
var _permission = scope.ServiceProvider.GetRequiredService<PermissionServices>();
|
||||||
|
|
||||||
// --- Step 1: Input Validation ---
|
// --- Step 1: Input Validation ---
|
||||||
@ -1424,11 +1482,37 @@ namespace Marco.Pms.Services.Service
|
|||||||
WorkItem = wi
|
WorkItem = wi
|
||||||
}).ToList();
|
}).ToList();
|
||||||
|
|
||||||
|
_ = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
// --- Push Notification Section ---
|
||||||
|
// This section attempts to send a test push notification to the user's device.
|
||||||
|
// It's designed to fail gracefully and handle invalid Firebase Cloud Messaging (FCM) tokens.
|
||||||
|
|
||||||
|
var name = $"{loggedInEmployee.FirstName} {loggedInEmployee.LastName}";
|
||||||
|
|
||||||
|
List<Guid> workItemIds = new List<Guid>();
|
||||||
|
bool IsExist = false;
|
||||||
|
|
||||||
|
if (workItemsToCreate.Any())
|
||||||
|
workItemIds = workItemsToCreate.Select(wi => wi.Id).ToList();
|
||||||
|
if (workItemsToModify.Any())
|
||||||
|
{
|
||||||
|
workItemIds = workItemsToModify.Select(wi => wi.Id).ToList();
|
||||||
|
IsExist = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (workItemIds.Any())
|
||||||
|
await _firebase.SendModifyTaskMeaasgeAsync(workItemIds, name, IsExist, tenantId);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
return ApiResponse<List<WorkItemVM>>.SuccessResponse(responseList, message, 200);
|
return ApiResponse<List<WorkItemVM>>.SuccessResponse(responseList, message, 200);
|
||||||
}
|
}
|
||||||
public async Task<ServiceResponse> DeleteProjectTaskAsync(Guid id, Guid tenantId, Employee loggedInEmployee)
|
public async Task<ServiceResponse> DeleteProjectTaskAsync(Guid id, Guid tenantId, Employee loggedInEmployee)
|
||||||
{
|
{
|
||||||
|
using var scope = _serviceScopeFactory.CreateScope();
|
||||||
|
var _firebase = scope.ServiceProvider.GetRequiredService<IFirebaseService>();
|
||||||
|
|
||||||
// 1. Fetch the task and its parent data in a single query.
|
// 1. Fetch the task and its parent data in a single query.
|
||||||
// This is still a major optimization, avoiding a separate query for the floor/building.
|
// This is still a major optimization, avoiding a separate query for the floor/building.
|
||||||
WorkItem? task = await _context.WorkItems
|
WorkItem? task = await _context.WorkItems
|
||||||
@ -1492,16 +1576,26 @@ namespace Marco.Pms.Services.Service
|
|||||||
|
|
||||||
// 6. Perform cache operations concurrently.
|
// 6. Perform cache operations concurrently.
|
||||||
var cacheTasks = new List<Task>
|
var cacheTasks = new List<Task>
|
||||||
{
|
{
|
||||||
_cache.DeleteWorkItemByIdAsync(task.Id)
|
_cache.DeleteWorkItemByIdAsync(task.Id)
|
||||||
};
|
};
|
||||||
|
|
||||||
if (building?.ProjectId != null)
|
if (building?.ProjectId != null)
|
||||||
{
|
{
|
||||||
cacheTasks.Add(_cache.DeleteProjectByIdAsync(building.ProjectId));
|
cacheTasks.Add(_cache.DeleteProjectByIdAsync(building.ProjectId));
|
||||||
}
|
}
|
||||||
await Task.WhenAll(cacheTasks);
|
await Task.WhenAll(cacheTasks);
|
||||||
|
_ = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
// --- Push Notification Section ---
|
||||||
|
// This section attempts to send a test push notification to the user's device.
|
||||||
|
// It's designed to fail gracefully and handle invalid Firebase Cloud Messaging (FCM) tokens.
|
||||||
|
|
||||||
|
var name = $"{loggedInEmployee.FirstName} {loggedInEmployee.LastName}";
|
||||||
|
|
||||||
|
await _firebase.SendDeleteTaskMeaasgeAsync(task.Id, name, tenantId);
|
||||||
|
|
||||||
|
});
|
||||||
// 7. Return the final success response.
|
// 7. Return the final success response.
|
||||||
return new ServiceResponse
|
return new ServiceResponse
|
||||||
{
|
{
|
||||||
@ -2178,8 +2272,11 @@ namespace Marco.Pms.Services.Service
|
|||||||
_logger.LogError(ex, "An error occurred during background cache update/notification.");
|
_logger.LogError(ex, "An error occurred during background cache update/notification.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private void ProcessBuilding(BuildingDto dto, Guid tenantId, InfraVM responseData, List<string> messages, ISet<Guid> projectIds, List<Task> cacheTasks)
|
private void ProcessBuilding(BuildingDto dto, Guid tenantId, InfraVM responseData, List<string> messages, ISet<Guid> projectIds, List<Task> cacheTasks, Employee loggedInEmployee)
|
||||||
{
|
{
|
||||||
|
using var scope = _serviceScopeFactory.CreateScope();
|
||||||
|
var _firebase = scope.ServiceProvider.GetRequiredService<IFirebaseService>();
|
||||||
|
|
||||||
Building building = _mapper.Map<Building>(dto);
|
Building building = _mapper.Map<Building>(dto);
|
||||||
building.TenantId = tenantId;
|
building.TenantId = tenantId;
|
||||||
|
|
||||||
@ -2199,9 +2296,24 @@ namespace Marco.Pms.Services.Service
|
|||||||
|
|
||||||
responseData.building = building;
|
responseData.building = building;
|
||||||
projectIds.Add(building.ProjectId);
|
projectIds.Add(building.ProjectId);
|
||||||
|
_ = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
// --- Push Notification Section ---
|
||||||
|
// This section attempts to send a test push notification to the user's device.
|
||||||
|
// It's designed to fail gracefully and handle invalid Firebase Cloud Messaging (FCM) tokens.
|
||||||
|
|
||||||
|
var name = $"{loggedInEmployee.FirstName} {loggedInEmployee.LastName}";
|
||||||
|
|
||||||
|
await _firebase.SendModifyBuildingMeaasgeAsync(building.Id, name, !isNew, tenantId);
|
||||||
|
|
||||||
|
});
|
||||||
}
|
}
|
||||||
private void ProcessFloor(FloorDto dto, Guid tenantId, InfraVM responseData, List<string> messages, ISet<Guid> projectIds, List<Task> cacheTasks, IDictionary<Guid, Building> buildings)
|
private void ProcessFloor(FloorDto dto, Guid tenantId, InfraVM responseData, List<string> messages, ISet<Guid> projectIds, List<Task> cacheTasks, IDictionary<Guid, Building> buildings,
|
||||||
|
Employee loggedInEmployee)
|
||||||
{
|
{
|
||||||
|
using var scope = _serviceScopeFactory.CreateScope();
|
||||||
|
var _firebase = scope.ServiceProvider.GetRequiredService<IFirebaseService>();
|
||||||
|
|
||||||
Floor floor = _mapper.Map<Floor>(dto);
|
Floor floor = _mapper.Map<Floor>(dto);
|
||||||
floor.TenantId = tenantId;
|
floor.TenantId = tenantId;
|
||||||
|
|
||||||
@ -2224,9 +2336,25 @@ namespace Marco.Pms.Services.Service
|
|||||||
|
|
||||||
responseData.floor = floor;
|
responseData.floor = floor;
|
||||||
if (parentBuilding != null) projectIds.Add(parentBuilding.ProjectId);
|
if (parentBuilding != null) projectIds.Add(parentBuilding.ProjectId);
|
||||||
|
|
||||||
|
_ = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
// --- Push Notification Section ---
|
||||||
|
// This section attempts to send a test push notification to the user's device.
|
||||||
|
// It's designed to fail gracefully and handle invalid Firebase Cloud Messaging (FCM) tokens.
|
||||||
|
|
||||||
|
var name = $"{loggedInEmployee.FirstName} {loggedInEmployee.LastName}";
|
||||||
|
|
||||||
|
await _firebase.SendModifyFloorMeaasgeAsync(floor.Id, name, !isNew, tenantId);
|
||||||
|
|
||||||
|
});
|
||||||
}
|
}
|
||||||
private void ProcessWorkArea(WorkAreaDto dto, Guid tenantId, InfraVM responseData, List<string> messages, ISet<Guid> projectIds, List<Task> cacheTasks, IDictionary<Guid, Floor> floors)
|
private void ProcessWorkArea(WorkAreaDto dto, Guid tenantId, InfraVM responseData, List<string> messages, ISet<Guid> projectIds, List<Task> cacheTasks, IDictionary<Guid, Floor> floors,
|
||||||
|
Employee loggedInEmployee)
|
||||||
{
|
{
|
||||||
|
using var scope = _serviceScopeFactory.CreateScope();
|
||||||
|
var _firebase = scope.ServiceProvider.GetRequiredService<IFirebaseService>();
|
||||||
|
|
||||||
WorkArea workArea = _mapper.Map<WorkArea>(dto);
|
WorkArea workArea = _mapper.Map<WorkArea>(dto);
|
||||||
workArea.TenantId = tenantId;
|
workArea.TenantId = tenantId;
|
||||||
|
|
||||||
@ -2250,6 +2378,18 @@ namespace Marco.Pms.Services.Service
|
|||||||
|
|
||||||
responseData.workArea = workArea;
|
responseData.workArea = workArea;
|
||||||
if (parentBuilding != null) projectIds.Add(parentBuilding.ProjectId);
|
if (parentBuilding != null) projectIds.Add(parentBuilding.ProjectId);
|
||||||
|
|
||||||
|
_ = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
// --- Push Notification Section ---
|
||||||
|
// This section attempts to send a test push notification to the user's device.
|
||||||
|
// It's designed to fail gracefully and handle invalid Firebase Cloud Messaging (FCM) tokens.
|
||||||
|
|
||||||
|
var name = $"{loggedInEmployee.FirstName} {loggedInEmployee.LastName}";
|
||||||
|
|
||||||
|
await _firebase.SendModifyWorkAreaMeaasgeAsync(workArea.Id, name, !isNew, tenantId);
|
||||||
|
|
||||||
|
});
|
||||||
}
|
}
|
||||||
private async Task<List<Guid>> GetPermissionIdsByProject(Guid projectId, Guid EmployeeId, Guid tenantId)
|
private async Task<List<Guid>> GetPermissionIdsByProject(Guid projectId, Guid EmployeeId, Guid tenantId)
|
||||||
{
|
{
|
||||||
|
@ -0,0 +1,38 @@
|
|||||||
|
using FirebaseAdmin.Messaging;
|
||||||
|
using Marco.Pms.Model.Dtos.Attendance;
|
||||||
|
using Marco.Pms.Model.Expenses;
|
||||||
|
using Marco.Pms.Model.Projects;
|
||||||
|
|
||||||
|
namespace Marco.Pms.Services.Service.ServiceInterfaces
|
||||||
|
{
|
||||||
|
public interface IFirebaseService
|
||||||
|
{
|
||||||
|
Task SendLoginMessageAsync(string name, Guid tenentId);
|
||||||
|
Task SendLoginOnAnotherDeviceMessageAsync(Guid employeeId, string fcmToken, Guid tenentId);
|
||||||
|
|
||||||
|
Task SendEmployeeSuspendMessageAsync(Guid employeeId, Guid tenantId);
|
||||||
|
|
||||||
|
Task SendAttendanceMessageAsync(Guid projectId, string Name, ATTENDANCE_MARK_TYPE markType, Guid employeeId, Guid tenantId);
|
||||||
|
Task SendAssignTaskMessageAsync(Guid workItemId, string name, List<Guid> teamMembers, Guid tenantId);
|
||||||
|
Task SendReportTaskMessageAsync(Guid taskAllocationId, string name, Guid tenantId);
|
||||||
|
Task SendTaskCommentMessageAsync(Guid taskAllocationId, string name, Guid tenantId);
|
||||||
|
Task SendApproveTaskMessageAsync(Guid taskAllocationId, string name, Guid tenantId);
|
||||||
|
Task SendModifyTaskMeaasgeAsync(List<Guid> workItemIds, string name, bool IsExist, Guid tenantId);
|
||||||
|
Task SendModifyWorkAreaMeaasgeAsync(Guid workAreaId, string name, bool IsExist, Guid tenantId);
|
||||||
|
Task SendModifyFloorMeaasgeAsync(Guid floorId, string name, bool IsExist, Guid tenantId);
|
||||||
|
Task SendModifyBuildingMeaasgeAsync(Guid buildingId, string name, bool IsExist, Guid tenantId);
|
||||||
|
Task SendDeleteTaskMeaasgeAsync(Guid workItemId, string name, Guid tenantId);
|
||||||
|
|
||||||
|
Task SendProjectAllocationMessageAsync(List<ProjectAllocation> projectAllocations, string name, Guid tenantId);
|
||||||
|
Task SendModifyProjectMessageAsync(Project project, string name, bool IsExist, Guid tenantId);
|
||||||
|
Task SendExpenseMessageAsync(Expenses expenses, string name, Guid tenantId);
|
||||||
|
|
||||||
|
Task SendContactAsync(Guid contactId, List<Guid> bucketIds, Notification notification, Guid tenantId);
|
||||||
|
Task SendContactNoteAsync(Guid contactId, List<Guid> bucketIds, Notification notification, Guid tenantId);
|
||||||
|
Task SendBucketAsync(Guid bucketId, Notification notification, Guid tenantId);
|
||||||
|
Task SendAssignBucketAsync(List<Guid> employeeIds, Notification notification, Guid tenantId);
|
||||||
|
|
||||||
|
Task SendEmployeeDocumentMessageAsync(Guid documentId, Guid employeeId, Notification notification, Guid tenantId);
|
||||||
|
Task SendProjectDocumentMessageAsync(Guid documentId, Guid projectId, Notification notification, Guid tenantId);
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user