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<ExpensesStatusMapping> ExpensesStatusMapping { get; set; }
|
||||
|
||||
public DbSet<FCMTokenMapping> FCMTokenMappings { get; set; }
|
||||
|
||||
public DbSet<EntityTypeMaster> EntityTypeMasters { get; set; }
|
||||
public DbSet<DocumentTypeMaster> DocumentTypeMasters { 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.",
|
||||
NoOfPersonsRequired = false,
|
||||
IsActive = true,
|
||||
IsAttachmentRequried = true,
|
||||
TenantId = Guid.Parse("b3466e83-7e11-464c-b93a-daf047838b26")
|
||||
},
|
||||
new ExpensesTypeMaster
|
||||
@ -635,6 +638,7 @@ namespace Marco.Pms.DataAccess.Data
|
||||
Description = "Vehicle fuel, logistics services and delivery of goods or personnel.",
|
||||
NoOfPersonsRequired = false,
|
||||
IsActive = true,
|
||||
IsAttachmentRequried = false,
|
||||
TenantId = Guid.Parse("b3466e83-7e11-464c-b93a-daf047838b26")
|
||||
},
|
||||
new ExpensesTypeMaster
|
||||
@ -644,6 +648,7 @@ namespace Marco.Pms.DataAccess.Data
|
||||
Description = "Delivery of personnel.",
|
||||
NoOfPersonsRequired = true,
|
||||
IsActive = true,
|
||||
IsAttachmentRequried = false,
|
||||
TenantId = Guid.Parse("b3466e83-7e11-464c-b93a-daf047838b26")
|
||||
},
|
||||
new ExpensesTypeMaster
|
||||
@ -653,6 +658,7 @@ namespace Marco.Pms.DataAccess.Data
|
||||
Description = "Site setup costs including equipment deployment and temporary infrastructure.",
|
||||
NoOfPersonsRequired = false,
|
||||
IsActive = true,
|
||||
IsAttachmentRequried = true,
|
||||
TenantId = Guid.Parse("b3466e83-7e11-464c-b93a-daf047838b26")
|
||||
},
|
||||
new ExpensesTypeMaster
|
||||
@ -662,6 +668,7 @@ namespace Marco.Pms.DataAccess.Data
|
||||
Description = " Worker amenities like snacks, meals, safety gear, accommodation, medical support etc.",
|
||||
NoOfPersonsRequired = true,
|
||||
IsActive = true,
|
||||
IsAttachmentRequried = true,
|
||||
TenantId = Guid.Parse("b3466e83-7e11-464c-b93a-daf047838b26")
|
||||
},
|
||||
new ExpensesTypeMaster
|
||||
@ -671,6 +678,7 @@ namespace Marco.Pms.DataAccess.Data
|
||||
Description = "Machinery servicing, electricity, water, and temporary office needs.",
|
||||
NoOfPersonsRequired = false,
|
||||
IsActive = true,
|
||||
IsAttachmentRequried = true,
|
||||
TenantId = Guid.Parse("b3466e83-7e11-464c-b93a-daf047838b26")
|
||||
},
|
||||
new ExpensesTypeMaster
|
||||
@ -680,6 +688,7 @@ namespace Marco.Pms.DataAccess.Data
|
||||
Description = "Scheduled payments for external services or goods.",
|
||||
NoOfPersonsRequired = false,
|
||||
IsActive = true,
|
||||
IsAttachmentRequried = true,
|
||||
TenantId = Guid.Parse("b3466e83-7e11-464c-b93a-daf047838b26")
|
||||
},
|
||||
new ExpensesTypeMaster
|
||||
@ -689,6 +698,7 @@ namespace Marco.Pms.DataAccess.Data
|
||||
Description = "Government fees, insurance, inspections and safety-related expenditures.",
|
||||
NoOfPersonsRequired = false,
|
||||
IsActive = true,
|
||||
IsAttachmentRequried = true,
|
||||
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
|
||||
{
|
||||
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.",
|
||||
EntityTypeId = new Guid("c8fe7115-aa27-43bc-99f4-7b05fabe436e"),
|
||||
Name = "Project Documents",
|
||||
@ -940,7 +940,7 @@ namespace Marco.Pms.DataAccess.Migrations
|
||||
new
|
||||
{
|
||||
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.",
|
||||
EntityTypeId = new Guid("dbb9555a-7a0c-40f2-a9ed-f0463f1ceed7"),
|
||||
Name = "Employee Documents",
|
||||
@ -1026,7 +1026,7 @@ namespace Marco.Pms.DataAccess.Migrations
|
||||
{
|
||||
Id = new Guid("336225ac-67f3-4e14-ba7a-8fad03cf2832"),
|
||||
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"),
|
||||
IsActive = true,
|
||||
IsMandatory = true,
|
||||
@ -1041,7 +1041,7 @@ namespace Marco.Pms.DataAccess.Migrations
|
||||
{
|
||||
Id = new Guid("6344393b-9bb1-45f8-b620-9f6e279d012c"),
|
||||
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"),
|
||||
IsActive = true,
|
||||
IsMandatory = true,
|
||||
@ -1056,7 +1056,7 @@ namespace Marco.Pms.DataAccess.Migrations
|
||||
{
|
||||
Id = new Guid("2d1d7441-46a8-425e-9395-94d0956f8e91"),
|
||||
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"),
|
||||
IsActive = true,
|
||||
IsMandatory = true,
|
||||
@ -1071,7 +1071,7 @@ namespace Marco.Pms.DataAccess.Migrations
|
||||
{
|
||||
Id = new Guid("16c40b80-c207-4a0c-a4d3-381414afe35a"),
|
||||
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"),
|
||||
IsActive = true,
|
||||
IsMandatory = true,
|
||||
@ -1086,7 +1086,7 @@ namespace Marco.Pms.DataAccess.Migrations
|
||||
{
|
||||
Id = new Guid("f76d8215-d399-4f0e-b414-12e427f50be3"),
|
||||
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"),
|
||||
IsActive = true,
|
||||
IsMandatory = true,
|
||||
@ -1101,7 +1101,7 @@ namespace Marco.Pms.DataAccess.Migrations
|
||||
{
|
||||
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",
|
||||
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"),
|
||||
IsActive = true,
|
||||
IsMandatory = false,
|
||||
@ -1115,7 +1115,7 @@ namespace Marco.Pms.DataAccess.Migrations
|
||||
{
|
||||
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",
|
||||
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"),
|
||||
IsActive = true,
|
||||
IsMandatory = false,
|
||||
@ -1129,7 +1129,7 @@ namespace Marco.Pms.DataAccess.Migrations
|
||||
{
|
||||
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",
|
||||
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"),
|
||||
IsActive = true,
|
||||
IsMandatory = false,
|
||||
@ -1143,7 +1143,7 @@ namespace Marco.Pms.DataAccess.Migrations
|
||||
{
|
||||
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",
|
||||
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"),
|
||||
IsActive = true,
|
||||
IsMandatory = false,
|
||||
@ -1157,7 +1157,7 @@ namespace Marco.Pms.DataAccess.Migrations
|
||||
{
|
||||
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",
|
||||
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"),
|
||||
IsActive = true,
|
||||
IsMandatory = false,
|
||||
@ -1171,7 +1171,7 @@ namespace Marco.Pms.DataAccess.Migrations
|
||||
{
|
||||
Id = new Guid("5668de00-5d84-47f7-b9b5-7fefd1219f05"),
|
||||
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"),
|
||||
IsActive = true,
|
||||
IsMandatory = false,
|
||||
@ -2606,6 +2606,9 @@ namespace Marco.Pms.DataAccess.Migrations
|
||||
b.Property<bool>("IsActive")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<bool>("IsAttachmentRequried")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
@ -2628,6 +2631,7 @@ namespace Marco.Pms.DataAccess.Migrations
|
||||
Id = new Guid("5e0c6227-d49d-41ff-9f1f-781f0aee2469"),
|
||||
Description = "Materials, equipment and supplies purchased for site operations.",
|
||||
IsActive = true,
|
||||
IsAttachmentRequried = true,
|
||||
Name = "Procurement",
|
||||
NoOfPersonsRequired = false,
|
||||
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"),
|
||||
Description = "Vehicle fuel, logistics services and delivery of goods or personnel.",
|
||||
IsActive = true,
|
||||
IsAttachmentRequried = false,
|
||||
Name = "Transport",
|
||||
NoOfPersonsRequired = false,
|
||||
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"),
|
||||
Description = "Delivery of personnel.",
|
||||
IsActive = true,
|
||||
IsAttachmentRequried = false,
|
||||
Name = "Travelling",
|
||||
NoOfPersonsRequired = true,
|
||||
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"),
|
||||
Description = "Site setup costs including equipment deployment and temporary infrastructure.",
|
||||
IsActive = true,
|
||||
IsAttachmentRequried = true,
|
||||
Name = "Mobilization",
|
||||
NoOfPersonsRequired = false,
|
||||
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"),
|
||||
Description = " Worker amenities like snacks, meals, safety gear, accommodation, medical support etc.",
|
||||
IsActive = true,
|
||||
IsAttachmentRequried = true,
|
||||
Name = "Employee Welfare",
|
||||
NoOfPersonsRequired = true,
|
||||
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"),
|
||||
Description = "Machinery servicing, electricity, water, and temporary office needs.",
|
||||
IsActive = true,
|
||||
IsAttachmentRequried = true,
|
||||
Name = "Maintenance & Utilities",
|
||||
NoOfPersonsRequired = false,
|
||||
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"),
|
||||
Description = "Scheduled payments for external services or goods.",
|
||||
IsActive = true,
|
||||
IsAttachmentRequried = true,
|
||||
Name = "Vendor/Supplier Payments",
|
||||
NoOfPersonsRequired = false,
|
||||
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"),
|
||||
Description = "Government fees, insurance, inspections and safety-related expenditures.",
|
||||
IsActive = true,
|
||||
IsAttachmentRequried = true,
|
||||
Name = "Compliance & Safety",
|
||||
NoOfPersonsRequired = false,
|
||||
TenantId = new Guid("b3466e83-7e11-464c-b93a-daf047838b26")
|
||||
@ -3878,6 +3889,32 @@ namespace Marco.Pms.DataAccess.Migrations
|
||||
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 =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
@ -5452,6 +5489,17 @@ namespace Marco.Pms.DataAccess.Migrations
|
||||
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 =>
|
||||
{
|
||||
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.Logging;
|
||||
using MongoDB.Bson;
|
||||
@ -146,5 +147,86 @@ namespace Marco.Pms.Helpers.Utility
|
||||
}
|
||||
|
||||
#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 FileUploadModel? Image { get; set; }
|
||||
public string DeviceToken { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public enum ATTENDANCE_MARK_TYPE
|
||||
|
@ -2,7 +2,7 @@
|
||||
{
|
||||
public class LoginDto
|
||||
{
|
||||
public string? Username { get; set; }
|
||||
public string? Password { get; set; }
|
||||
public required string Username { get; set; }
|
||||
public required string Password { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
namespace Marco.Pms.Model.Dtos.Authentication
|
||||
{
|
||||
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 string? Token { get; set; }
|
||||
public string? RefreshToken { get; set; }
|
||||
public required string Token { get; set; }
|
||||
public required string RefreshToken { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -2,8 +2,9 @@
|
||||
{
|
||||
public class VerifyMPINDto
|
||||
{
|
||||
public Guid EmployeeId { get; set; }
|
||||
public string? MPIN { get; set; }
|
||||
public string? MPINToken { get; set; }
|
||||
public required Guid EmployeeId { get; set; }
|
||||
public required string MPIN { get; set; }
|
||||
public required string MPINToken { get; set; }
|
||||
public required string FcmToken { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -3,18 +3,18 @@
|
||||
public class CreateUserDto
|
||||
{
|
||||
public Guid? Id { get; set; }
|
||||
public string? FirstName { get; set; }
|
||||
public required string FirstName { get; set; }
|
||||
public string? LastName { get; set; }
|
||||
public string? MiddleName { get; set; }
|
||||
public string? Email { get; set; }
|
||||
|
||||
public string? Gender { get; set; }
|
||||
public string? BirthDate { get; set; }
|
||||
public string? JoiningDate { get; set; }
|
||||
public required string Gender { get; set; }
|
||||
public required string BirthDate { get; set; }
|
||||
public required string JoiningDate { get; set; }
|
||||
|
||||
public string? PermanentAddress { get; set; }
|
||||
public string? CurrentAddress { get; set; }
|
||||
public string? PhoneNumber { get; set; }
|
||||
public required string PermanentAddress { get; set; }
|
||||
public required string CurrentAddress { get; set; }
|
||||
public required string PhoneNumber { get; set; }
|
||||
|
||||
public string? EmergencyPhoneNumber { get; set; }
|
||||
public string? EmergencyContactPerson { get; set; }
|
||||
@ -33,10 +33,11 @@
|
||||
public class MobileUserManageDto
|
||||
{
|
||||
public Guid? Id { get; set; }
|
||||
public string FirstName { get; set; } = string.Empty;
|
||||
public string? LastName { get; set; }
|
||||
public string PhoneNumber { get; set; } = string.Empty;
|
||||
public string? Gender { get; set; }
|
||||
public required string FirstName { get; set; }
|
||||
public required string LastName { get; set; }
|
||||
public required string PhoneNumber { get; set; }
|
||||
public required DateTime JoiningDate { get; set; }
|
||||
public required string Gender { get; set; }
|
||||
public Guid JobRoleId { get; set; }
|
||||
public string? ProfileImage { get; set; }
|
||||
}
|
||||
|
@ -5,6 +5,7 @@
|
||||
public Guid? Id { get; set; }
|
||||
public required string Name { get; set; }
|
||||
public required bool NoOfPersonsRequired { get; set; }
|
||||
public required bool IsAttachmentRequried { get; set; }
|
||||
public string? Description { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -74,7 +74,7 @@ namespace Marco.Pms.Model.Mapper
|
||||
PhoneNumber = model.PhoneNumber,
|
||||
Photo = image,
|
||||
JobRoleId = model.JobRoleId,
|
||||
JoiningDate = null,
|
||||
JoiningDate = model.JoiningDate,
|
||||
|
||||
};
|
||||
}
|
||||
|
@ -9,5 +9,6 @@ namespace Marco.Pms.Model.Master
|
||||
public bool NoOfPersonsRequired { get; set; }
|
||||
public string Description { get; set; } = string.Empty;
|
||||
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 Marco.Pms.Model.Utilities;
|
||||
|
||||
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 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 long FileSize { get; set; } // File size in bytes
|
||||
public string? Description { get; set; } // Optional: Description or purpose of the file
|
||||
|
@ -5,6 +5,7 @@
|
||||
public Guid Id { get; set; }
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public bool NoOfPersonsRequired { get; set; }
|
||||
public bool IsAttachmentRequried { get; set; }
|
||||
public string Description { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
||||
|
@ -35,10 +35,11 @@ namespace MarcoBMS.Services.Controllers
|
||||
private readonly PermissionServices _permission;
|
||||
private readonly ILoggingService _logger;
|
||||
private readonly IHubContext<MarcoHub> _signalR;
|
||||
|
||||
private readonly IFirebaseService _firebase;
|
||||
|
||||
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;
|
||||
_employeeHelper = employeeHelper;
|
||||
@ -48,6 +49,7 @@ namespace MarcoBMS.Services.Controllers
|
||||
_logger = logger;
|
||||
_permission = permission;
|
||||
_signalR = signalR;
|
||||
_firebase = firebase;
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
}
|
||||
[HttpGet("log/employee/{employeeId}")]
|
||||
|
||||
[HttpGet("log/employee/{employeeId}")]
|
||||
public async Task<IActionResult> GetAttendanceLogByEmployeeId(Guid employeeId, [FromQuery] string? dateFrom = null, [FromQuery] string? dateTo = null)
|
||||
{
|
||||
Guid TenantId = GetTenantId();
|
||||
@ -258,7 +260,6 @@ namespace MarcoBMS.Services.Controllers
|
||||
/// <param name="date">YYYY-MM-dd</param>
|
||||
/// <returns></returns>
|
||||
[HttpGet("project/team")]
|
||||
|
||||
public async Task<IActionResult> EmployeeAttendanceByProject([FromQuery] Guid projectId, [FromQuery] bool IncludeInActive, [FromQuery] string? date = null)
|
||||
{
|
||||
Guid TenantId = GetTenantId();
|
||||
@ -363,7 +364,6 @@ namespace MarcoBMS.Services.Controllers
|
||||
}
|
||||
|
||||
[HttpGet("regularize")]
|
||||
|
||||
public async Task<IActionResult> GetRequestRegularizeAttendance([FromQuery] Guid projectId, [FromQuery] bool IncludeInActive)
|
||||
{
|
||||
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 };
|
||||
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);
|
||||
|
||||
_ = 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));
|
||||
}
|
||||
_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);
|
||||
|
||||
_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));
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
@ -5,6 +5,7 @@ using Marco.Pms.Model.Dtos.Util;
|
||||
using Marco.Pms.Model.Employees;
|
||||
using Marco.Pms.Model.Entitlements;
|
||||
using Marco.Pms.Model.Utilities;
|
||||
using Marco.Pms.Services.Service.ServiceInterfaces;
|
||||
using MarcoBMS.Services.Helpers;
|
||||
using MarcoBMS.Services.Service;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
@ -31,9 +32,10 @@ namespace MarcoBMS.Services.Controllers
|
||||
private readonly IConfiguration _configuration;
|
||||
private readonly EmployeeHelper _employeeHelper;
|
||||
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,
|
||||
IEmailSender emailSender, IConfiguration configuration, EmployeeHelper employeeHelper, UserHelper userHelper, ILoggingService logger)
|
||||
IEmailSender emailSender, IConfiguration configuration, EmployeeHelper employeeHelper, UserHelper userHelper, ILoggingService logger, IFirebaseService firebase)
|
||||
{
|
||||
_userManager = userManager;
|
||||
_jwtSettings = jwtSettings;
|
||||
@ -44,6 +46,8 @@ namespace MarcoBMS.Services.Controllers
|
||||
_context = context;
|
||||
_userHelper = userHelper;
|
||||
_logger = logger;
|
||||
_firebase = firebase;
|
||||
tenantId = userHelper.GetTenantId();
|
||||
}
|
||||
|
||||
[HttpPost("login")]
|
||||
@ -51,7 +55,7 @@ namespace MarcoBMS.Services.Controllers
|
||||
{
|
||||
try
|
||||
{
|
||||
// Find user by email or phone number
|
||||
|
||||
var user = await _context.ApplicationUsers
|
||||
.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")]
|
||||
public async Task<IActionResult> LoginMobile([FromBody] LoginDto loginDto)
|
||||
{
|
||||
// Validate input DTO
|
||||
if (loginDto == null || string.IsNullOrWhiteSpace(loginDto.Username) || string.IsNullOrWhiteSpace(loginDto.Password))
|
||||
// Log the start of the login attempt for traceability.
|
||||
_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));
|
||||
}
|
||||
|
||||
// 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)
|
||||
catch (Exception ex)
|
||||
{
|
||||
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")]
|
||||
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));
|
||||
}
|
||||
|
||||
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
|
||||
var jwtToken = _refreshTokenService.GenerateJwtToken(requestEmployee.Email, tenantId, _jwtSettings);
|
||||
var refreshToken = await _refreshTokenService.CreateRefreshToken(requestEmployee.ApplicationUserId, tenantId.ToString(), _jwtSettings);
|
||||
@ -278,6 +387,7 @@ namespace MarcoBMS.Services.Controllers
|
||||
[HttpPost("logout")]
|
||||
public async Task<IActionResult> Logout([FromBody] LogoutDto logoutDto)
|
||||
{
|
||||
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
|
||||
if (string.IsNullOrWhiteSpace(logoutDto.RefreshToken))
|
||||
{
|
||||
_logger.LogWarning("Logout failed: Refresh token is missing");
|
||||
@ -301,7 +411,15 @@ namespace MarcoBMS.Services.Controllers
|
||||
await _refreshTokenService.BlacklistJwtTokenAsync(jwtToken);
|
||||
_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");
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
[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)
|
||||
{
|
||||
using (SHA256 sha256 = SHA256.Create())
|
||||
|
@ -1,4 +1,5 @@
|
||||
using AutoMapper;
|
||||
using FirebaseAdmin.Messaging;
|
||||
using Marco.Pms.DataAccess.Data;
|
||||
using Marco.Pms.Helpers.Utility;
|
||||
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.DocumentManager;
|
||||
using Marco.Pms.Services.Service;
|
||||
using Marco.Pms.Services.Service.ServiceInterfaces;
|
||||
using MarcoBMS.Services.Helpers;
|
||||
using MarcoBMS.Services.Service;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
@ -163,9 +165,8 @@ namespace Marco.Pms.Services.Controllers
|
||||
if (documentFilter.IsUploadedAt)
|
||||
{
|
||||
documentQuery = documentQuery.Where(da =>
|
||||
da.UpdatedAt.HasValue &&
|
||||
da.UpdatedAt.Value.Date >= documentFilter.StartDate.Value.Date &&
|
||||
da.UpdatedAt.Value.Date <= documentFilter.EndDate.Value.Date);
|
||||
da.UploadedAt.Date >= documentFilter.StartDate.Value.Date &&
|
||||
da.UploadedAt.Date <= documentFilter.EndDate.Value.Date);
|
||||
}
|
||||
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));
|
||||
}
|
||||
|
||||
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
|
||||
var attachment = _mapper.Map<DocumentAttachment>(model);
|
||||
attachment.UploadedAt = DateTime.UtcNow;
|
||||
@ -841,6 +835,39 @@ namespace Marco.Pms.Services.Controllers
|
||||
response.ParentAttachmentId = versionMapping.ParentAttachmentId;
|
||||
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));
|
||||
}
|
||||
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);
|
||||
|
||||
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));
|
||||
}
|
||||
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));
|
||||
}
|
||||
|
||||
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
|
||||
var oldVersionMapping = await _context.AttachmentVersionMappings
|
||||
.FirstOrDefaultAsync(av => av.ChildAttachmentId == oldAttachment.Id && av.TenantId == tenantId);
|
||||
@ -1176,7 +1231,7 @@ namespace Marco.Pms.Services.Controllers
|
||||
oldAttachment.DocumentId = model.DocumentId;
|
||||
oldAttachment.Description = model.Description;
|
||||
oldAttachment.DocumentDataId = document.Id;
|
||||
if (oldAttachment.IsVerified == true)
|
||||
if (oldAttachment.IsVerified != null)
|
||||
{
|
||||
oldAttachment.IsVerified = null;
|
||||
_logger.LogInfo("Reset verification flag for AttachmentId: {AttachmentId}", oldAttachment.Id);
|
||||
@ -1196,7 +1251,7 @@ namespace Marco.Pms.Services.Controllers
|
||||
oldAttachment.Name = model.Name;
|
||||
oldAttachment.DocumentId = model.DocumentId;
|
||||
oldAttachment.Description = model.Description;
|
||||
if (oldAttachment.IsVerified == true)
|
||||
if (oldAttachment.IsVerified != null)
|
||||
{
|
||||
oldAttachment.IsVerified = null;
|
||||
_logger.LogInfo("Reset verification flag for AttachmentId: {AttachmentId}", oldAttachment.Id);
|
||||
@ -1288,6 +1343,39 @@ namespace Marco.Pms.Services.Controllers
|
||||
response.Version = newVersionMapping.Version;
|
||||
_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));
|
||||
}
|
||||
catch (Exception ex)
|
||||
@ -1370,6 +1458,42 @@ namespace Marco.Pms.Services.Controllers
|
||||
// Log the successful completion of the operation
|
||||
_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 Ok(ApiResponse<object>.SuccessResponse(new { }, $"Document attachment is {message}", 200));
|
||||
}
|
||||
|
@ -33,6 +33,7 @@ namespace MarcoBMS.Services.Controllers
|
||||
{
|
||||
|
||||
private readonly ApplicationDbContext _context;
|
||||
private readonly IServiceScopeFactory _serviceScope;
|
||||
private readonly UserManager<ApplicationUser> _userManager;
|
||||
private readonly IEmailSender _emailSender;
|
||||
private readonly EmployeeHelper _employeeHelper;
|
||||
@ -47,10 +48,21 @@ namespace MarcoBMS.Services.Controllers
|
||||
private readonly Guid tenantId;
|
||||
|
||||
|
||||
public EmployeeController(UserManager<ApplicationUser> userManager, IEmailSender emailSender,
|
||||
ApplicationDbContext context, EmployeeHelper employeeHelper, UserHelper userHelper, IConfiguration configuration, ILoggingService logger,
|
||||
IHubContext<MarcoHub> signalR, PermissionServices permission, IProjectServices projectServices, IMapper mapper, GeneralHelper generalHelper)
|
||||
public EmployeeController(IServiceScopeFactory serviceScope,
|
||||
UserManager<ApplicationUser> userManager,
|
||||
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;
|
||||
_userManager = userManager;
|
||||
_emailSender = emailSender;
|
||||
@ -503,6 +515,7 @@ namespace MarcoBMS.Services.Controllers
|
||||
existingEmployee.LastName = model.LastName;
|
||||
existingEmployee.Gender = model.Gender;
|
||||
existingEmployee.PhoneNumber = model.PhoneNumber;
|
||||
existingEmployee.JoiningDate = model.JoiningDate;
|
||||
existingEmployee.JobRoleId = model.JobRoleId;
|
||||
existingEmployee.Photo = imageBytes;
|
||||
|
||||
@ -518,6 +531,8 @@ namespace MarcoBMS.Services.Controllers
|
||||
[HttpDelete("{id}")]
|
||||
public async Task<IActionResult> SuspendEmployee(Guid id, [FromQuery] bool active = false)
|
||||
{
|
||||
using var scope = _serviceScope.CreateScope();
|
||||
|
||||
Guid tenantId = _userHelper.GetTenantId();
|
||||
var LoggedEmployee = await _userHelper.GetCurrentEmployeeAsync();
|
||||
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("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
|
||||
{
|
||||
|
@ -9,6 +9,7 @@ using Marco.Pms.Model.ViewModels.Activities;
|
||||
using Marco.Pms.Services.Helpers;
|
||||
using Marco.Pms.Services.Hubs;
|
||||
using Marco.Pms.Services.Service;
|
||||
using Marco.Pms.Services.Service.ServiceInterfaces;
|
||||
using MarcoBMS.Services.Helpers;
|
||||
using MarcoBMS.Services.Service;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
@ -33,9 +34,10 @@ namespace MarcoBMS.Services.Controllers
|
||||
private readonly IHubContext<MarcoHub> _signalR;
|
||||
private readonly CacheUpdateHelper _cache;
|
||||
private readonly PermissionServices _permissionServices;
|
||||
private readonly IFirebaseService _firebase;
|
||||
|
||||
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;
|
||||
_userHelper = userHelper;
|
||||
@ -44,6 +46,7 @@ namespace MarcoBMS.Services.Controllers
|
||||
_signalR = signalR;
|
||||
_cache = cache;
|
||||
_permissionServices = permissionServices;
|
||||
_firebase = firebase;
|
||||
}
|
||||
|
||||
private Guid GetTenantId()
|
||||
@ -66,28 +69,28 @@ namespace MarcoBMS.Services.Controllers
|
||||
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid data", errors, 400));
|
||||
}
|
||||
|
||||
// Retrieve tenant and employee context
|
||||
// Retrieve tenant and loggedInEmployee context
|
||||
var tenantId = GetTenantId();
|
||||
var employee = await _userHelper.GetCurrentEmployeeAsync();
|
||||
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
|
||||
|
||||
// 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)
|
||||
{
|
||||
_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));
|
||||
}
|
||||
|
||||
_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
|
||||
var taskAllocation = assignTask.ToTaskAllocationFromAssignTaskDto(employee.Id, tenantId);
|
||||
var taskAllocation = assignTask.ToTaskAllocationFromAssignTaskDto(loggedInEmployee.Id, tenantId);
|
||||
_context.TaskAllocations.Add(taskAllocation);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
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();
|
||||
|
||||
@ -117,6 +120,18 @@ namespace MarcoBMS.Services.Controllers
|
||||
var team = employees.Select(e => e.ToBasicEmployeeVMFromEmployee()).ToList();
|
||||
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));
|
||||
}
|
||||
|
||||
@ -146,7 +161,14 @@ namespace MarcoBMS.Services.Controllers
|
||||
|
||||
var taskAllocation = await _context.TaskAllocations
|
||||
.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)
|
||||
{
|
||||
@ -278,6 +300,18 @@ namespace MarcoBMS.Services.Controllers
|
||||
|
||||
_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));
|
||||
}
|
||||
|
||||
@ -378,6 +412,18 @@ namespace MarcoBMS.Services.Controllers
|
||||
var notification = new { LoggedInUserId = loggedInEmployee.Id, Keyword = "Task_Comment", NumberOfImages = numberofImages, ProjectId = projectId };
|
||||
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));
|
||||
}
|
||||
|
||||
@ -724,16 +770,6 @@ namespace MarcoBMS.Services.Controllers
|
||||
"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
|
||||
taskAllocation.ApprovedById = loggedInEmployee.Id;
|
||||
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);
|
||||
|
||||
_ = 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));
|
||||
}
|
||||
}
|
||||
|
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>
|
||||
<PackageReference Include="AutoMapper" Version="13.0.1" />
|
||||
<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="Microsoft.AspNetCore.Authentication.JwtBearer" Version="7.0.20" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.12" />
|
||||
|
@ -1,4 +1,6 @@
|
||||
using Marco.Pms.CacheHelper;
|
||||
using FirebaseAdmin;
|
||||
using Google.Apis.Auth.OAuth2;
|
||||
using Marco.Pms.DataAccess.Data;
|
||||
using Marco.Pms.Helpers;
|
||||
using Marco.Pms.Helpers.CacheHelper;
|
||||
@ -179,6 +181,7 @@ builder.Services.AddScoped<IProjectServices, ProjectServices>();
|
||||
builder.Services.AddScoped<IExpensesService, ExpensesService>();
|
||||
builder.Services.AddScoped<IMasterService, MasterService>();
|
||||
builder.Services.AddScoped<IDirectoryService, DirectoryService>();
|
||||
builder.Services.AddScoped<IFirebaseService, FirebaseService>();
|
||||
#endregion
|
||||
|
||||
#region Helpers
|
||||
@ -202,6 +205,13 @@ builder.Services.AddScoped<SidebarMenuHelper>();
|
||||
|
||||
// Singleton services (one instance for the app's lifetime)
|
||||
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
|
||||
|
||||
#region Web Server (Kestrel)
|
||||
|
@ -1,4 +1,5 @@
|
||||
using AutoMapper;
|
||||
using FirebaseAdmin.Messaging;
|
||||
using Marco.Pms.DataAccess.Data;
|
||||
using Marco.Pms.Helpers.Utility;
|
||||
using Marco.Pms.Model.Directory;
|
||||
@ -119,15 +120,18 @@ namespace Marco.Pms.Services.Service
|
||||
|
||||
// --- Advanced Filtering from 'filter' parameter ---
|
||||
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.
|
||||
contactQuery = contactQuery.Where(c => dbContext.ContactBucketMappings.Any(cbm =>
|
||||
cbm.ContactId == c.Id && contactFilter.BucketIds.Contains(cbm.BucketId)));
|
||||
}
|
||||
if (contactFilter?.CategoryIds?.Any() ?? false)
|
||||
{
|
||||
contactQuery = contactQuery.Where(c => c.ContactCategoryId.HasValue && contactFilter.CategoryIds.Contains(c.ContactCategoryId.Value));
|
||||
if (contactFilter.BucketIds?.Any() ?? false)
|
||||
{
|
||||
// Note: Permission filtering is already applied. Here we further restrict by the user's filter choice.
|
||||
contactQuery = contactQuery.Where(c => dbContext.ContactBucketMappings.Any(cbm =>
|
||||
cbm.ContactId == c.Id && contactFilter.BucketIds.Contains(cbm.BucketId)));
|
||||
}
|
||||
if (contactFilter.CategoryIds?.Any() ?? false)
|
||||
{
|
||||
contactQuery = contactQuery.Where(c => c.ContactCategoryId.HasValue && contactFilter.CategoryIds.Contains(c.ContactCategoryId.Value));
|
||||
}
|
||||
}
|
||||
|
||||
// --- 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>
|
||||
public async Task<ApiResponse<object>> CreateContactAsync(CreateContactDto createContact, Guid tenantId, Employee loggedInEmployee)
|
||||
{
|
||||
using var scope = _serviceScopeFactory.CreateScope();
|
||||
Guid loggedInEmployeeId = loggedInEmployee.Id;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(createContact.Name) ||
|
||||
@ -951,6 +956,10 @@ namespace Marco.Pms.Services.Service
|
||||
try
|
||||
{
|
||||
var contact = _mapper.Map<Contact>(createContact);
|
||||
if (string.IsNullOrWhiteSpace(createContact.Name))
|
||||
{
|
||||
contact.Description = string.Empty;
|
||||
}
|
||||
contact.CreatedAt = DateTime.UtcNow;
|
||||
contact.CreatedById = loggedInEmployeeId;
|
||||
contact.TenantId = tenantId;
|
||||
@ -1007,6 +1016,24 @@ namespace Marco.Pms.Services.Service
|
||||
contactVM.BucketIds = contactBucketMappings.Select(cb => cb.BucketId).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);
|
||||
}
|
||||
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)
|
||||
{
|
||||
using var scope = _serviceScopeFactory.CreateScope();
|
||||
if (updateContact == null)
|
||||
{
|
||||
_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
|
||||
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.CreatedAt = contact.CreatedAt;
|
||||
updatedContact.CreatedById = contact.CreatedById;
|
||||
@ -1454,6 +1490,28 @@ namespace Marco.Pms.Services.Service
|
||||
var contactVM = responseTask.Result;
|
||||
|
||||
_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);
|
||||
}
|
||||
@ -1471,6 +1529,15 @@ namespace Marco.Pms.Services.Service
|
||||
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
|
||||
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);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
@ -1929,6 +2031,14 @@ namespace Marco.Pms.Services.Service
|
||||
|
||||
try
|
||||
{
|
||||
var bucketIds = await _context.ContactBucketMappings.Where(cb => cb.ContactId == noteDto.ContactId).Select(cb => cb.BucketId).ToListAsync();
|
||||
var hasContactAccess = await _context.EmployeeBucketMappings.AnyAsync(eb => bucketIds.Contains(eb.BucketId) && eb.EmployeeId == loggedInEmployee.Id);
|
||||
if (!hasContactAccess)
|
||||
{
|
||||
_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
|
||||
Contact? contact = await _context.Contacts
|
||||
.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}.",
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
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
|
||||
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}",
|
||||
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);
|
||||
}
|
||||
catch (DbUpdateException ex)
|
||||
@ -2103,6 +2265,28 @@ namespace Marco.Pms.Services.Service
|
||||
return ApiResponse<object>.ErrorResponse("Note not found", "Note not found", 404);
|
||||
}
|
||||
|
||||
var bucketIds = await _context.ContactBucketMappings.Where(cb => cb.ContactId == note.ContactId).Select(cb => cb.BucketId).ToListAsync();
|
||||
var hasContactAccess = await _context.EmployeeBucketMappings.AnyAsync(eb => bucketIds.Contains(eb.BucketId) && eb.EmployeeId == loggedInEmployee.Id);
|
||||
if (hasContactAccess)
|
||||
{
|
||||
_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
|
||||
var oldObject = _updateLogsHelper.EntityToBsonDocument(note);
|
||||
|
||||
@ -2146,6 +2330,38 @@ namespace Marco.Pms.Services.Service
|
||||
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 { },
|
||||
active ? "Note restored successfully" : "Note deleted successfully",
|
||||
200);
|
||||
@ -2264,6 +2480,7 @@ namespace Marco.Pms.Services.Service
|
||||
|
||||
_logger.LogInfo("Fetched {BucketCount} buckets for Employee {EmployeeId} successfully", bucketVMs.Count, loggedInEmployee.Id);
|
||||
|
||||
|
||||
return ApiResponse<object>.SuccessResponse(bucketVMs, $"{bucketVMs.Count} buckets fetched successfully", 200);
|
||||
}
|
||||
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);
|
||||
|
||||
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);
|
||||
}
|
||||
catch (Exception ex)
|
||||
@ -2427,6 +2665,7 @@ namespace Marco.Pms.Services.Service
|
||||
});
|
||||
|
||||
// Save changes to bucket and logs
|
||||
_context.Buckets.Update(bucket);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
// 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);
|
||||
|
||||
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);
|
||||
}
|
||||
catch (Exception ex)
|
||||
@ -2519,6 +2779,9 @@ namespace Marco.Pms.Services.Service
|
||||
int assignedEmployeesCount = 0;
|
||||
int removedEmployeesCount = 0;
|
||||
|
||||
List<Guid> assignedEmployeeIds = new List<Guid>();
|
||||
List<Guid> removedEmployeeIds = new List<Guid>();
|
||||
|
||||
// Process each assignment request
|
||||
foreach (var assignBucket in assignBuckets)
|
||||
{
|
||||
@ -2541,6 +2804,7 @@ namespace Marco.Pms.Services.Service
|
||||
};
|
||||
_context.EmployeeBucketMappings.Add(newMapping);
|
||||
assignedEmployeesCount++;
|
||||
assignedEmployeeIds.Add(assignBucket.EmployeeId);
|
||||
}
|
||||
}
|
||||
else
|
||||
@ -2549,6 +2813,7 @@ namespace Marco.Pms.Services.Service
|
||||
var existingMapping = employeeBuckets.FirstOrDefault(eb => eb.EmployeeId == assignBucket.EmployeeId);
|
||||
if (existingMapping != null && bucket.CreatedByID != assignBucket.EmployeeId)
|
||||
{
|
||||
removedEmployeeIds.Add(existingMapping.EmployeeId);
|
||||
_context.EmployeeBucketMappings.Remove(existingMapping);
|
||||
removedEmployeesCount++;
|
||||
}
|
||||
@ -2607,7 +2872,41 @@ namespace Marco.Pms.Services.Service
|
||||
{
|
||||
_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);
|
||||
}
|
||||
public async Task<ApiResponse<object>> DeleteBucketAsync(Guid id, Guid tenantId, Employee loggedInEmployee)
|
||||
@ -2720,6 +3019,27 @@ namespace Marco.Pms.Services.Service
|
||||
UpdatedAt = DateTime.UtcNow
|
||||
}, 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);
|
||||
}
|
||||
catch (Exception ex)
|
||||
@ -2739,14 +3059,25 @@ namespace Marco.Pms.Services.Service
|
||||
|
||||
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.
|
||||
var hasAdminTask = permissionService.HasPermission(PermissionsMaster.DirectoryAdmin, employeeId);
|
||||
var hasManagerTask = permissionService.HasPermission(PermissionsMaster.DirectoryManager, employeeId);
|
||||
var hasUserTask = permissionService.HasPermission(PermissionsMaster.DirectoryUser, employeeId);
|
||||
var hasAdminTask = Task.Run(async () =>
|
||||
{
|
||||
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);
|
||||
|
||||
|
@ -525,6 +525,7 @@ namespace Marco.Pms.Services.Service
|
||||
if (expenseType == null) validationErrors.Add("Expense Type not found.");
|
||||
if (paymentMode == null) validationErrors.Add("Payment Mode 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())
|
||||
{
|
||||
@ -596,6 +597,9 @@ namespace Marco.Pms.Services.Service
|
||||
/// <returns>An ApiResponse containing the updated expense details or an error.</returns>
|
||||
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)
|
||||
var expense = await _context.Expenses
|
||||
.Include(e => e.ExpensesType)
|
||||
@ -670,7 +674,6 @@ namespace Marco.Pms.Services.Service
|
||||
}
|
||||
else if (requiredPermissions.Any())
|
||||
{
|
||||
using var scope = _serviceScopeFactory.CreateScope();
|
||||
var permissionService = scope.ServiceProvider.GetRequiredService<PermissionServices>();
|
||||
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);
|
||||
}
|
||||
|
||||
_ = 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)
|
||||
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.",
|
||||
NoOfPersonsRequired = false,
|
||||
IsActive = true,
|
||||
IsAttachmentRequried = true,
|
||||
TenantId = tenantId
|
||||
},
|
||||
new ExpensesTypeMaster
|
||||
@ -231,6 +232,7 @@ namespace Marco.Pms.Services.Service
|
||||
Description = "Vehicle fuel, logistics services and delivery of goods or personnel.",
|
||||
NoOfPersonsRequired = false,
|
||||
IsActive = true,
|
||||
IsAttachmentRequried = false,
|
||||
TenantId = tenantId
|
||||
},
|
||||
new ExpensesTypeMaster
|
||||
@ -240,6 +242,7 @@ namespace Marco.Pms.Services.Service
|
||||
Description = "Delivery of personnel.",
|
||||
NoOfPersonsRequired = true,
|
||||
IsActive = true,
|
||||
IsAttachmentRequried = false,
|
||||
TenantId = tenantId
|
||||
},
|
||||
new ExpensesTypeMaster
|
||||
@ -249,6 +252,7 @@ namespace Marco.Pms.Services.Service
|
||||
Description = "Site setup costs including equipment deployment and temporary infrastructure.",
|
||||
NoOfPersonsRequired = false,
|
||||
IsActive = true,
|
||||
IsAttachmentRequried = true,
|
||||
TenantId = tenantId
|
||||
},
|
||||
new ExpensesTypeMaster
|
||||
@ -258,6 +262,7 @@ namespace Marco.Pms.Services.Service
|
||||
Description = " Worker amenities like snacks, meals, safety gear, accommodation, medical support etc.",
|
||||
NoOfPersonsRequired = true,
|
||||
IsActive = true,
|
||||
IsAttachmentRequried = true,
|
||||
TenantId = tenantId
|
||||
},
|
||||
new ExpensesTypeMaster
|
||||
@ -267,6 +272,7 @@ namespace Marco.Pms.Services.Service
|
||||
Description = "Machinery servicing, electricity, water, and temporary office needs.",
|
||||
NoOfPersonsRequired = false,
|
||||
IsActive = true,
|
||||
IsAttachmentRequried = true,
|
||||
TenantId = tenantId
|
||||
},
|
||||
new ExpensesTypeMaster
|
||||
@ -276,6 +282,7 @@ namespace Marco.Pms.Services.Service
|
||||
Description = "Scheduled payments for external services or goods.",
|
||||
NoOfPersonsRequired = false,
|
||||
IsActive = true,
|
||||
IsAttachmentRequried = true,
|
||||
TenantId = tenantId
|
||||
},
|
||||
new ExpensesTypeMaster
|
||||
@ -285,6 +292,7 @@ namespace Marco.Pms.Services.Service
|
||||
Description = "Government fees, insurance, inspections and safety-related expenditures.",
|
||||
NoOfPersonsRequired = false,
|
||||
IsActive = true,
|
||||
IsAttachmentRequried = true,
|
||||
TenantId = tenantId
|
||||
}
|
||||
};
|
||||
|
@ -27,16 +27,16 @@ namespace Marco.Pms.Services.Service
|
||||
public class ProjectServices : IProjectServices
|
||||
{
|
||||
private readonly IDbContextFactory<ApplicationDbContext> _dbContextFactory;
|
||||
private readonly IServiceScopeFactory _serviceScopeFactory;
|
||||
private readonly ApplicationDbContext _context; // Keeping this for direct scoped context use where appropriate
|
||||
private readonly ILoggingService _logger;
|
||||
private readonly CacheUpdateHelper _cache;
|
||||
private readonly IMapper _mapper;
|
||||
private readonly IServiceScopeFactory _serviceScopeFactory;
|
||||
public ProjectServices(
|
||||
IDbContextFactory<ApplicationDbContext> dbContextFactory,
|
||||
IServiceScopeFactory serviceScopeFactory,
|
||||
ApplicationDbContext context,
|
||||
ILoggingService logger,
|
||||
IServiceScopeFactory serviceScopeFactory,
|
||||
CacheUpdateHelper cache,
|
||||
IMapper mapper)
|
||||
{
|
||||
@ -349,6 +349,9 @@ namespace Marco.Pms.Services.Service
|
||||
|
||||
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
|
||||
var loggedInUserId = loggedInEmployee.Id;
|
||||
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);
|
||||
}
|
||||
|
||||
_ = 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.
|
||||
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>
|
||||
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
|
||||
{
|
||||
using var scope = _serviceScopeFactory.CreateScope();
|
||||
var _permission = scope.ServiceProvider.GetRequiredService<PermissionServices>();
|
||||
// --- 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.
|
||||
@ -451,6 +468,18 @@ namespace Marco.Pms.Services.Service
|
||||
// 4a. Update Cache
|
||||
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 ---
|
||||
// The client gets a fast response without waiting for caching or SignalR.
|
||||
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>
|
||||
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 ---
|
||||
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);
|
||||
|
||||
using var scope = _serviceScopeFactory.CreateScope();
|
||||
var _permission = scope.ServiceProvider.GetRequiredService<PermissionServices>();
|
||||
|
||||
// --- (Placeholder) Security Check ---
|
||||
@ -724,6 +755,17 @@ namespace Marco.Pms.Services.Service
|
||||
|
||||
// --- Step 5: Map results and return success ---
|
||||
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);
|
||||
}
|
||||
|
||||
@ -814,6 +856,9 @@ namespace Marco.Pms.Services.Service
|
||||
/// <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)
|
||||
{
|
||||
using var scope = _serviceScopeFactory.CreateScope();
|
||||
var _firebase = scope.ServiceProvider.GetRequiredService<IFirebaseService>();
|
||||
|
||||
// --- Step 1: Input Validation ---
|
||||
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);
|
||||
|
||||
using var scope = _serviceScopeFactory.CreateScope();
|
||||
var _permission = scope.ServiceProvider.GetRequiredService<PermissionServices>();
|
||||
|
||||
// --- (Placeholder) Security Check ---
|
||||
@ -913,6 +957,17 @@ namespace Marco.Pms.Services.Service
|
||||
|
||||
// --- Step 6: Map results using AutoMapper and return success ---
|
||||
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);
|
||||
}
|
||||
|
||||
@ -995,12 +1050,12 @@ namespace Marco.Pms.Services.Service
|
||||
{
|
||||
_logger.LogInfo("GetInfraDetails called for ProjectId: {ProjectId}", projectId);
|
||||
|
||||
using var scope = _serviceScopeFactory.CreateScope();
|
||||
var _generalHelper = scope.ServiceProvider.GetRequiredService<GeneralHelper>();
|
||||
|
||||
try
|
||||
{
|
||||
using var scope = _serviceScopeFactory.CreateScope();
|
||||
var _permission = scope.ServiceProvider.GetRequiredService<PermissionServices>();
|
||||
|
||||
var _generalHelper = scope.ServiceProvider.GetRequiredService<GeneralHelper>();
|
||||
// --- Step 1: Run independent permission checks in PARALLEL ---
|
||||
var projectPermissionTask = _permission.HasProjectPermission(loggedInEmployee, projectId);
|
||||
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);
|
||||
|
||||
using var scope = _serviceScopeFactory.CreateScope();
|
||||
var _generalHelper = scope.ServiceProvider.GetRequiredService<GeneralHelper>();
|
||||
|
||||
try
|
||||
{
|
||||
using var scope = _serviceScopeFactory.CreateScope();
|
||||
var _permission = scope.ServiceProvider.GetRequiredService<PermissionServices>();
|
||||
var _generalHelper = scope.ServiceProvider.GetRequiredService<GeneralHelper>();
|
||||
// --- Step 1: Cache-First Strategy ---
|
||||
var cachedWorkItems = await _cache.GetWorkItemDetailsByWorkArea(workAreaId);
|
||||
if (cachedWorkItems != null)
|
||||
@ -1261,15 +1317,15 @@ namespace Marco.Pms.Services.Service
|
||||
{
|
||||
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)
|
||||
{
|
||||
ProcessFloor(item.Floor, tenantId, responseData, messages, projectIds, cacheUpdateTasks, buildingsDict);
|
||||
ProcessFloor(item.Floor, tenantId, responseData, messages, projectIds, cacheUpdateTasks, buildingsDict, loggedInEmployee);
|
||||
}
|
||||
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);
|
||||
|
||||
using var scope = _serviceScopeFactory.CreateScope();
|
||||
var _firebase = scope.ServiceProvider.GetRequiredService<IFirebaseService>();
|
||||
|
||||
var _permission = scope.ServiceProvider.GetRequiredService<PermissionServices>();
|
||||
|
||||
// --- Step 1: Input Validation ---
|
||||
@ -1424,11 +1482,37 @@ namespace Marco.Pms.Services.Service
|
||||
WorkItem = wi
|
||||
}).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);
|
||||
}
|
||||
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.
|
||||
// This is still a major optimization, avoiding a separate query for the floor/building.
|
||||
WorkItem? task = await _context.WorkItems
|
||||
@ -1492,16 +1576,26 @@ namespace Marco.Pms.Services.Service
|
||||
|
||||
// 6. Perform cache operations concurrently.
|
||||
var cacheTasks = new List<Task>
|
||||
{
|
||||
_cache.DeleteWorkItemByIdAsync(task.Id)
|
||||
};
|
||||
{
|
||||
_cache.DeleteWorkItemByIdAsync(task.Id)
|
||||
};
|
||||
|
||||
if (building?.ProjectId != null)
|
||||
{
|
||||
cacheTasks.Add(_cache.DeleteProjectByIdAsync(building.ProjectId));
|
||||
}
|
||||
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.
|
||||
return new ServiceResponse
|
||||
{
|
||||
@ -2178,8 +2272,11 @@ namespace Marco.Pms.Services.Service
|
||||
_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.TenantId = tenantId;
|
||||
|
||||
@ -2199,9 +2296,24 @@ namespace Marco.Pms.Services.Service
|
||||
|
||||
responseData.building = building;
|
||||
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.TenantId = tenantId;
|
||||
|
||||
@ -2224,9 +2336,25 @@ namespace Marco.Pms.Services.Service
|
||||
|
||||
responseData.floor = floor;
|
||||
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.TenantId = tenantId;
|
||||
|
||||
@ -2250,6 +2378,18 @@ namespace Marco.Pms.Services.Service
|
||||
|
||||
responseData.workArea = workArea;
|
||||
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)
|
||||
{
|
||||
|
@ -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