Added the manage job tagging APi and get List of job tagging per job
This commit is contained in:
parent
4cec5860ec
commit
08a336be37
@ -225,8 +225,11 @@ namespace Marco.Pms.DataAccess.Data
|
||||
public DbSet<JobTag> JobTags { get; set; }
|
||||
public DbSet<JobTagMapping> JobTagMappings { get; set; }
|
||||
public DbSet<JobAttachment> JobAttachments { get; set; }
|
||||
public DbSet<JobAttendance> JobAttendance { get; set; }
|
||||
public DbSet<JobAttendanceLog> JobAttendanceLogs { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
8794
Marco.Pms.DataAccess/Migrations/20251117045622_Added_JobAttendance_Related_Table.Designer.cs
generated
Normal file
8794
Marco.Pms.DataAccess/Migrations/20251117045622_Added_JobAttendance_Related_Table.Designer.cs
generated
Normal file
File diff suppressed because one or more lines are too long
@ -0,0 +1,158 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Marco.Pms.DataAccess.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class Added_JobAttendance_Related_Table : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "JobAttendance",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
|
||||
JobTcketId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
|
||||
Action = table.Column<int>(type: "int", nullable: false),
|
||||
EmployeeId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
|
||||
TaggedInTime = table.Column<DateTime>(type: "datetime(6)", nullable: false),
|
||||
TaggedOutTime = table.Column<DateTime>(type: "datetime(6)", nullable: true),
|
||||
TaggedInAt = table.Column<DateTime>(type: "datetime(6)", nullable: false),
|
||||
TaggedOutAt = table.Column<DateTime>(type: "datetime(6)", nullable: true),
|
||||
TenantId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci")
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_JobAttendance", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_JobAttendance_Employees_EmployeeId",
|
||||
column: x => x.EmployeeId,
|
||||
principalTable: "Employees",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_JobAttendance_JobTickets_JobTcketId",
|
||||
column: x => x.JobTcketId,
|
||||
principalTable: "JobTickets",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_JobAttendance_Tenants_TenantId",
|
||||
column: x => x.TenantId,
|
||||
principalTable: "Tenants",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
})
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "JobAttendanceLogs",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
|
||||
JobAttendanceId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
|
||||
JobTcketId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
|
||||
DocumentId = table.Column<Guid>(type: "char(36)", nullable: true, collation: "ascii_general_ci"),
|
||||
Latitude = table.Column<string>(type: "longtext", nullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
Longitude = table.Column<string>(type: "longtext", nullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
Action = table.Column<int>(type: "int", nullable: false),
|
||||
Comment = table.Column<string>(type: "longtext", nullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
EmployeeId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
|
||||
MarkedTIme = table.Column<DateTime>(type: "datetime(6)", nullable: false),
|
||||
MarkedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false),
|
||||
TenantId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci")
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_JobAttendanceLogs", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_JobAttendanceLogs_Documents_DocumentId",
|
||||
column: x => x.DocumentId,
|
||||
principalTable: "Documents",
|
||||
principalColumn: "Id");
|
||||
table.ForeignKey(
|
||||
name: "FK_JobAttendanceLogs_Employees_EmployeeId",
|
||||
column: x => x.EmployeeId,
|
||||
principalTable: "Employees",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_JobAttendanceLogs_JobAttendance_JobAttendanceId",
|
||||
column: x => x.JobAttendanceId,
|
||||
principalTable: "JobAttendance",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_JobAttendanceLogs_JobTickets_JobTcketId",
|
||||
column: x => x.JobTcketId,
|
||||
principalTable: "JobTickets",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_JobAttendanceLogs_Tenants_TenantId",
|
||||
column: x => x.TenantId,
|
||||
principalTable: "Tenants",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
})
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_JobAttendance_EmployeeId",
|
||||
table: "JobAttendance",
|
||||
column: "EmployeeId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_JobAttendance_JobTcketId",
|
||||
table: "JobAttendance",
|
||||
column: "JobTcketId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_JobAttendance_TenantId",
|
||||
table: "JobAttendance",
|
||||
column: "TenantId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_JobAttendanceLogs_DocumentId",
|
||||
table: "JobAttendanceLogs",
|
||||
column: "DocumentId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_JobAttendanceLogs_EmployeeId",
|
||||
table: "JobAttendanceLogs",
|
||||
column: "EmployeeId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_JobAttendanceLogs_JobAttendanceId",
|
||||
table: "JobAttendanceLogs",
|
||||
column: "JobAttendanceId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_JobAttendanceLogs_JobTcketId",
|
||||
table: "JobAttendanceLogs",
|
||||
column: "JobTcketId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_JobAttendanceLogs_TenantId",
|
||||
table: "JobAttendanceLogs",
|
||||
column: "TenantId");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "JobAttendanceLogs");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "JobAttendance");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -5046,6 +5046,101 @@ namespace Marco.Pms.DataAccess.Migrations
|
||||
b.ToTable("JobAttachments");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Marco.Pms.Model.ServiceProject.JobAttendance", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<int>("Action")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<Guid>("EmployeeId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<Guid>("JobTcketId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<DateTime>("TaggedInAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<DateTime>("TaggedInTime")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<DateTime?>("TaggedOutAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<DateTime?>("TaggedOutTime")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<Guid>("TenantId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("EmployeeId");
|
||||
|
||||
b.HasIndex("JobTcketId");
|
||||
|
||||
b.HasIndex("TenantId");
|
||||
|
||||
b.ToTable("JobAttendance");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Marco.Pms.Model.ServiceProject.JobAttendanceLog", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<int>("Action")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Comment")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<Guid?>("DocumentId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<Guid>("EmployeeId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<Guid>("JobAttendanceId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<Guid>("JobTcketId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<string>("Latitude")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("Longitude")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<DateTime>("MarkedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<DateTime>("MarkedTIme")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<Guid>("TenantId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("DocumentId");
|
||||
|
||||
b.HasIndex("EmployeeId");
|
||||
|
||||
b.HasIndex("JobAttendanceId");
|
||||
|
||||
b.HasIndex("JobTcketId");
|
||||
|
||||
b.HasIndex("TenantId");
|
||||
|
||||
b.ToTable("JobAttendanceLogs");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Marco.Pms.Model.ServiceProject.JobComment", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
@ -8108,6 +8203,74 @@ namespace Marco.Pms.DataAccess.Migrations
|
||||
b.Navigation("Tenant");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Marco.Pms.Model.ServiceProject.JobAttendance", b =>
|
||||
{
|
||||
b.HasOne("Marco.Pms.Model.Employees.Employee", "Employee")
|
||||
.WithMany()
|
||||
.HasForeignKey("EmployeeId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Marco.Pms.Model.ServiceProject.JobTicket", "JobTicket")
|
||||
.WithMany()
|
||||
.HasForeignKey("JobTcketId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Marco.Pms.Model.TenantModels.Tenant", "Tenant")
|
||||
.WithMany()
|
||||
.HasForeignKey("TenantId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Employee");
|
||||
|
||||
b.Navigation("JobTicket");
|
||||
|
||||
b.Navigation("Tenant");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Marco.Pms.Model.ServiceProject.JobAttendanceLog", b =>
|
||||
{
|
||||
b.HasOne("Marco.Pms.Model.DocumentManager.Document", "Document")
|
||||
.WithMany()
|
||||
.HasForeignKey("DocumentId");
|
||||
|
||||
b.HasOne("Marco.Pms.Model.Employees.Employee", "Employee")
|
||||
.WithMany()
|
||||
.HasForeignKey("EmployeeId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Marco.Pms.Model.ServiceProject.JobAttendance", "JobAttendance")
|
||||
.WithMany()
|
||||
.HasForeignKey("JobAttendanceId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Marco.Pms.Model.ServiceProject.JobTicket", "JobTicket")
|
||||
.WithMany()
|
||||
.HasForeignKey("JobTcketId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Marco.Pms.Model.TenantModels.Tenant", "Tenant")
|
||||
.WithMany()
|
||||
.HasForeignKey("TenantId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Document");
|
||||
|
||||
b.Navigation("Employee");
|
||||
|
||||
b.Navigation("JobAttendance");
|
||||
|
||||
b.Navigation("JobTicket");
|
||||
|
||||
b.Navigation("Tenant");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Marco.Pms.Model.ServiceProject.JobComment", b =>
|
||||
{
|
||||
b.HasOne("Marco.Pms.Model.Employees.Employee", "CreatedBy")
|
||||
|
||||
16
Marco.Pms.Model/Dtos/ServiceProject/JobAttendanceDto.cs
Normal file
16
Marco.Pms.Model/Dtos/ServiceProject/JobAttendanceDto.cs
Normal file
@ -0,0 +1,16 @@
|
||||
using Marco.Pms.Model.ServiceProject;
|
||||
using Marco.Pms.Model.Utilities;
|
||||
|
||||
namespace Marco.Pms.Model.Dtos.ServiceProject
|
||||
{
|
||||
public class JobAttendanceDto
|
||||
{
|
||||
public Guid JobTcketId { get; set; }
|
||||
public TAGGING_MARK_TYPE Action { get; set; }
|
||||
public string? Latitude { get; set; }
|
||||
public string? Longitude { get; set; }
|
||||
public string? Comment { get; set; }
|
||||
public DateTime? MarkedAt { get; set; }
|
||||
public FileUploadModel? Attachment { get; set; }
|
||||
}
|
||||
}
|
||||
32
Marco.Pms.Model/ServiceProject/JobAttendance.cs
Normal file
32
Marco.Pms.Model/ServiceProject/JobAttendance.cs
Normal file
@ -0,0 +1,32 @@
|
||||
using Marco.Pms.Model.Employees;
|
||||
using Marco.Pms.Model.Utilities;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace Marco.Pms.Model.ServiceProject
|
||||
{
|
||||
public class JobAttendance : TenantRelation
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public Guid JobTcketId { get; set; }
|
||||
|
||||
[ValidateNever]
|
||||
[ForeignKey("JobTcketId")]
|
||||
public JobTicket? JobTicket { get; set; }
|
||||
public TAGGING_MARK_TYPE Action { get; set; }
|
||||
public Guid EmployeeId { get; set; }
|
||||
|
||||
[ValidateNever]
|
||||
[ForeignKey("EmployeeId")]
|
||||
public Employee? Employee { get; set; }
|
||||
public DateTime TaggedInTime { get; set; }
|
||||
public DateTime? TaggedOutTime { get; set; }
|
||||
public DateTime TaggedInAt { get; set; }
|
||||
public DateTime? TaggedOutAt { get; set; }
|
||||
}
|
||||
|
||||
public enum TAGGING_MARK_TYPE
|
||||
{
|
||||
TAG_IN = 0, TAG_OUT = 1
|
||||
}
|
||||
}
|
||||
39
Marco.Pms.Model/ServiceProject/JobAttendanceLog.cs
Normal file
39
Marco.Pms.Model/ServiceProject/JobAttendanceLog.cs
Normal file
@ -0,0 +1,39 @@
|
||||
using Marco.Pms.Model.DocumentManager;
|
||||
using Marco.Pms.Model.Employees;
|
||||
using Marco.Pms.Model.Utilities;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace Marco.Pms.Model.ServiceProject
|
||||
{
|
||||
public class JobAttendanceLog : TenantRelation
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public Guid JobAttendanceId { get; set; }
|
||||
|
||||
[ValidateNever]
|
||||
[ForeignKey("JobAttendanceId")]
|
||||
public JobAttendance? JobAttendance { get; set; }
|
||||
public Guid JobTcketId { get; set; }
|
||||
|
||||
[ValidateNever]
|
||||
[ForeignKey("JobTcketId")]
|
||||
public JobTicket? JobTicket { get; set; }
|
||||
public Guid? DocumentId { get; set; }
|
||||
|
||||
[ValidateNever]
|
||||
[ForeignKey("DocumentId")]
|
||||
public Document? Document { get; set; }
|
||||
public string? Latitude { get; set; }
|
||||
public string? Longitude { get; set; }
|
||||
public TAGGING_MARK_TYPE Action { get; set; }
|
||||
public string? Comment { get; set; }
|
||||
public Guid EmployeeId { get; set; }
|
||||
|
||||
[ValidateNever]
|
||||
[ForeignKey("EmployeeId")]
|
||||
public Employee? Employee { get; set; }
|
||||
public DateTime MarkedTIme { get; set; }
|
||||
public DateTime MarkedAt { get; set; }
|
||||
}
|
||||
}
|
||||
19
Marco.Pms.Model/ViewModels/ServiceProject/JobAttendanceVM.cs
Normal file
19
Marco.Pms.Model/ViewModels/ServiceProject/JobAttendanceVM.cs
Normal file
@ -0,0 +1,19 @@
|
||||
using Marco.Pms.Model.ServiceProject;
|
||||
using Marco.Pms.Model.ViewModels.Activities;
|
||||
|
||||
namespace Marco.Pms.Model.ViewModels.ServiceProject
|
||||
{
|
||||
public class JobAttendanceVM
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public Guid JobTcketId { get; set; }
|
||||
public BasicJobTicketVM? JobTicket { get; set; }
|
||||
public TAGGING_MARK_TYPE Action { get; set; }
|
||||
public TAGGING_MARK_TYPE? NextAction { get; set; }
|
||||
public BasicEmployeeVM? Employee { get; set; }
|
||||
public DateTime TaggedInTime { get; set; }
|
||||
public DateTime? TaggedOutTime { get; set; }
|
||||
public DateTime TaggedInAt { get; set; }
|
||||
public DateTime? TaggedOutAt { get; set; }
|
||||
}
|
||||
}
|
||||
@ -259,5 +259,30 @@ namespace Marco.Pms.Services.Controllers
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region =================================================================== Job Tagging Functions ===================================================================
|
||||
|
||||
[HttpGet("job/attendance/team")]
|
||||
public async Task<IActionResult> GetAttendanceForJobTeam([FromQuery] Guid jobTicketId, [FromQuery] DateTime? fromDate, [FromQuery] DateTime? toDate)
|
||||
{
|
||||
Employee loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
|
||||
var response = await _serviceProject.GetAttendanceForJobTeamAsync(jobTicketId, fromDate, toDate, loggedInEmployee, tenantId);
|
||||
|
||||
return StatusCode(response.StatusCode, response);
|
||||
}
|
||||
|
||||
[HttpPost("job/attendance")]
|
||||
public async Task<IActionResult> ManageJobTagging(JobAttendanceDto model)
|
||||
{
|
||||
Employee loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
|
||||
var response = await _serviceProject.ManageJobTaggingAsync(model, loggedInEmployee, tenantId);
|
||||
if (response.Success)
|
||||
{
|
||||
var notification = new { LoggedInUserId = loggedInEmployee.Id, Keyword = "Job_Ticket_Attendance", Response = response.Data };
|
||||
await _signalR.SendNotificationAsync(notification);
|
||||
}
|
||||
return StatusCode(response.StatusCode, response);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@ -212,6 +212,11 @@ namespace Marco.Pms.Services.MappingProfiles
|
||||
|
||||
CreateMap<JobComment, JobCommentVM>();
|
||||
|
||||
CreateMap<JobAttendance, JobAttendanceVM>()
|
||||
.ForMember(
|
||||
dest => dest.NextAction,
|
||||
opt => opt.MapFrom(src => src.Action == TAGGING_MARK_TYPE.TAG_IN ? TAGGING_MARK_TYPE.TAG_OUT : TAGGING_MARK_TYPE.TAG_IN));
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
|
||||
@ -36,6 +36,11 @@ namespace Marco.Pms.Services.Service.ServiceInterfaces
|
||||
Task<ApiResponse<object>> UpdateCommentAsync(Guid id, JobCommentDto model, Employee loggedInEmployee, Guid tenantId);
|
||||
#endregion
|
||||
|
||||
#region =================================================================== Job Tagging Functions ===================================================================
|
||||
Task<ApiResponse<object>> GetAttendanceForJobTeamAsync(Guid jobTicketId, DateTime? startDate, DateTime? endDate, Employee loggedInEmployee, Guid tenantId);
|
||||
Task<ApiResponse<object>> ManageJobTaggingAsync(JobAttendanceDto model, Employee loggedInEmployee, Guid tenantId);
|
||||
#endregion
|
||||
|
||||
#region =================================================================== Helper Functions ===================================================================
|
||||
Task<JobTicket?> GetJobTicketByIdAsync(Guid id, Guid tenantId);
|
||||
#endregion
|
||||
|
||||
@ -2011,6 +2011,240 @@ namespace Marco.Pms.Services.Service
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region =================================================================== Job Tagging Functions ===================================================================
|
||||
public async Task<ApiResponse<object>> GetAttendanceForJobTeamAsync(Guid jobTicketId, DateTime? startDate, DateTime? endDate, Employee loggedInEmployee, Guid tenantId)
|
||||
{
|
||||
_logger.LogInfo("GetAttendanceForJobTeamAsync called for JobTicketId: {JobTicketId}, TenantId: {TenantId}, EmployeeId: {EmployeeId}", jobTicketId, tenantId, loggedInEmployee.Id);
|
||||
|
||||
try
|
||||
{
|
||||
// Validate the existence and active status of the job ticket including its status related data
|
||||
var jobTicket = await _context.JobTickets
|
||||
.AsNoTracking()
|
||||
.Include(jt => jt.Status)
|
||||
.FirstOrDefaultAsync(jt => jt.Id == jobTicketId && jt.TenantId == tenantId && jt.IsActive);
|
||||
|
||||
if (jobTicket == null)
|
||||
{
|
||||
_logger.LogWarning("JobTicket not found. JobTicketId: {JobTicketId}, TenantId: {TenantId}", jobTicketId, tenantId);
|
||||
return ApiResponse<object>.ErrorResponse("Job is not found", "Job is not found", 404);
|
||||
}
|
||||
|
||||
// Define date range for attendance query: default from today to next 7 days if not provided
|
||||
DateTime fromDate = startDate?.Date ?? DateTime.UtcNow.Date;
|
||||
DateTime toDate = endDate?.Date ?? fromDate.AddDays(7);
|
||||
|
||||
// Fetch attendance records within the date range for the specified job ticket and tenant
|
||||
var attendances = await _context.JobAttendance
|
||||
.AsNoTracking()
|
||||
.Include(ja => ja.JobTicket).ThenInclude(jt => jt.Status)
|
||||
.Include(ja => ja.Employee).ThenInclude(e => e.JobRole)
|
||||
.Where(ja => ja.JobTcketId == jobTicketId
|
||||
&& ja.TaggedInTime.Date >= fromDate
|
||||
&& ja.TaggedInTime.Date <= toDate
|
||||
&& ja.TenantId == tenantId)
|
||||
.ToListAsync();
|
||||
|
||||
// Map attendance entities to view models
|
||||
var response = attendances.Select(ja =>
|
||||
{
|
||||
var result = _mapper.Map<JobAttendanceVM>(ja);
|
||||
|
||||
// Determine if current attendance record is not the latest, if so clear NextAction
|
||||
var isNotLast = attendances.Any(attendance => attendance.TaggedInTime.Date > ja.TaggedInTime.Date);
|
||||
if (isNotLast || (ja.TaggedOutTime.HasValue && ja.TaggedInTime.Date != DateTime.UtcNow.Date))
|
||||
{
|
||||
result.NextAction = null;
|
||||
}
|
||||
|
||||
return result;
|
||||
}).ToList();
|
||||
|
||||
_logger.LogInfo("Attendance for job team fetched successfully. JobTicketId: {JobTicketId}, RecordsCount: {Count}", jobTicketId, response.Count);
|
||||
|
||||
return ApiResponse<object>.SuccessResponse(response, "Attendance for job team fetched successfully", 200);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error occurred in GetAttendanceForJobTeamAsync for JobTicketId: {JobTicketId}, TenantId: {TenantId}", jobTicketId, tenantId);
|
||||
return ApiResponse<object>.ErrorResponse("An unexpected error occurred.", ex.Message, 500);
|
||||
}
|
||||
}
|
||||
public async Task<ApiResponse<object>> ManageJobTaggingAsync(JobAttendanceDto model, Employee loggedInEmployee, Guid tenantId)
|
||||
{
|
||||
_logger.LogInfo("ManageJobTaggingAsync called for EmployeeId: {EmployeeId}, JobTicketId: {JobTicketId}", loggedInEmployee.Id, model.JobTcketId);
|
||||
|
||||
try
|
||||
{
|
||||
// Validate the job ticket existence and status
|
||||
var jobTicket = await _context.JobTickets
|
||||
.AsNoTracking()
|
||||
.FirstOrDefaultAsync(jt => jt.Id == model.JobTcketId && jt.TenantId == tenantId && jt.IsActive);
|
||||
if (jobTicket == null)
|
||||
{
|
||||
_logger.LogWarning("JobTicket not found. JobTicketId: {JobTicketId}, TenantId: {TenantId}", model.JobTcketId, tenantId);
|
||||
return ApiResponse<object>.ErrorResponse("Job not found", "Job not found", 404);
|
||||
}
|
||||
|
||||
// Check if the current user is part of the job team
|
||||
var jobEmployeeMapping = await _context.JobEmployeeMappings
|
||||
.AsNoTracking()
|
||||
.FirstOrDefaultAsync(jem => jem.JobTicketId == model.JobTcketId && jem.AssigneeId == loggedInEmployee.Id && jem.TenantId == tenantId);
|
||||
if (jobEmployeeMapping == null)
|
||||
{
|
||||
_logger.LogWarning("User is not part of job team. EmployeeId: {EmployeeId}, JobTicketId: {JobTicketId}", loggedInEmployee.Id, model.JobTcketId);
|
||||
return ApiResponse<object>.ErrorResponse("User is not part of job team", "User is not part of job team", 400);
|
||||
}
|
||||
|
||||
// Get the last attendance record for the user and job
|
||||
var jobAttendance = await _context.JobAttendance
|
||||
.AsNoTracking()
|
||||
.Where(ja => ja.EmployeeId == loggedInEmployee.Id && ja.JobTcketId == model.JobTcketId)
|
||||
.OrderByDescending(ja => ja.TaggedInAt)
|
||||
.FirstOrDefaultAsync();
|
||||
|
||||
JobAttendance updateJobAttendance;
|
||||
DateTime markedAt = model.MarkedAt ?? DateTime.UtcNow;
|
||||
DateTime currentTime = DateTime.UtcNow;
|
||||
|
||||
// Handle TAG_IN action
|
||||
if (model.Action == TAGGING_MARK_TYPE.TAG_IN)
|
||||
{
|
||||
var isLastTaggedOut = jobAttendance != null && jobAttendance.Action == TAGGING_MARK_TYPE.TAG_OUT && jobAttendance.TaggedOutTime != null;
|
||||
|
||||
if (jobAttendance == null || (isLastTaggedOut && jobAttendance.TaggedInTime.Date != currentTime.Date))
|
||||
{
|
||||
// Create new JobAttendance record for Tag In
|
||||
var newJobAttendance = new JobAttendance
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
JobTcketId = model.JobTcketId,
|
||||
EmployeeId = loggedInEmployee.Id,
|
||||
Action = TAGGING_MARK_TYPE.TAG_IN,
|
||||
TaggedInTime = markedAt,
|
||||
TaggedInAt = currentTime,
|
||||
TenantId = tenantId
|
||||
};
|
||||
_context.JobAttendance.Add(newJobAttendance);
|
||||
updateJobAttendance = newJobAttendance;
|
||||
_logger.LogInfo("New Tag In created for EmployeeId: {EmployeeId}, JobTicketId: {JobTicketId}", loggedInEmployee.Id, model.JobTcketId);
|
||||
}
|
||||
else if (isLastTaggedOut && jobAttendance.TaggedInTime.Date == currentTime.Date)
|
||||
{
|
||||
// Update the existing last JobAttendance to Tag In
|
||||
jobAttendance.Action = TAGGING_MARK_TYPE.TAG_IN;
|
||||
jobAttendance.TaggedInTime = markedAt;
|
||||
jobAttendance.TaggedInAt = currentTime;
|
||||
jobAttendance.TaggedOutTime = null;
|
||||
jobAttendance.TaggedOutAt = null;
|
||||
|
||||
_context.JobAttendance.Update(jobAttendance);
|
||||
updateJobAttendance = jobAttendance;
|
||||
_logger.LogInfo("Existing JobAttendance updated to Tag In for EmployeeId: {EmployeeId}, JobTicketId: {JobTicketId}", loggedInEmployee.Id, model.JobTcketId);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogWarning("Attempted to Tag In without tagging out last session. EmployeeId: {EmployeeId}, JobTicketId: {JobTicketId}", loggedInEmployee.Id, model.JobTcketId);
|
||||
return ApiResponse<object>.ErrorResponse("First, mark the last tag as out before tagging in.", "First, mark the last tag as out before tagging in.", 400);
|
||||
}
|
||||
}
|
||||
// Handle TAG_OUT action
|
||||
else if (model.Action == TAGGING_MARK_TYPE.TAG_OUT)
|
||||
{
|
||||
if (jobAttendance != null && jobAttendance.Action == TAGGING_MARK_TYPE.TAG_IN && jobAttendance.TaggedOutTime == null)
|
||||
{
|
||||
jobAttendance.Action = TAGGING_MARK_TYPE.TAG_OUT;
|
||||
jobAttendance.TaggedOutTime = markedAt;
|
||||
jobAttendance.TaggedOutAt = currentTime;
|
||||
|
||||
_context.JobAttendance.Update(jobAttendance);
|
||||
updateJobAttendance = jobAttendance;
|
||||
_logger.LogInfo("JobAttendance updated to Tag Out for EmployeeId: {EmployeeId}, JobTicketId: {JobTicketId}", loggedInEmployee.Id, model.JobTcketId);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogWarning("Attempted to Tag Out without previous Tag In. EmployeeId: {EmployeeId}, JobTicketId: {JobTicketId}", loggedInEmployee.Id, model.JobTcketId);
|
||||
return ApiResponse<object>.ErrorResponse("First, mark the last tag as in before tagging out.", "First, mark the last tag as in before tagging out.", 400);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogWarning("Invalid action provided: {Action}. EmployeeId: {EmployeeId}, JobTicketId: {JobTicketId}", model.Action, loggedInEmployee.Id, model.JobTcketId);
|
||||
return ApiResponse<object>.ErrorResponse("Provided invalid action", "Provided invalid action", 400);
|
||||
}
|
||||
|
||||
Document? document = null;
|
||||
|
||||
// Handle attachment upload if present
|
||||
if (model.Attachment != null && model.Attachment.ContentType != null)
|
||||
{
|
||||
string base64 = model.Attachment.Base64Data?.Split(',').LastOrDefault() ?? "";
|
||||
if (string.IsNullOrWhiteSpace(base64))
|
||||
{
|
||||
_logger.LogWarning("Base64 data missing in attachment. EmployeeId: {EmployeeId}, JobTicketId: {JobTicketId}", loggedInEmployee.Id, model.JobTcketId);
|
||||
return ApiResponse<object>.ErrorResponse("Base64 data is missing", "Attachment data missing", 400);
|
||||
}
|
||||
|
||||
var fileType = _s3Service.GetContentTypeFromBase64(base64);
|
||||
var fileName = _s3Service.GenerateFileName(fileType, tenantId, "job_attendance");
|
||||
var objectKey = $"tenant-{tenantId}/ServiceProject/{jobTicket.ProjectId}/Job/{jobTicket.Id}/Attendance/{updateJobAttendance.Id}/{fileName}";
|
||||
|
||||
await _s3Service.UploadFileAsync(base64, fileType, objectKey);
|
||||
|
||||
document = new Document
|
||||
{
|
||||
BatchId = Guid.NewGuid(),
|
||||
UploadedById = loggedInEmployee.Id,
|
||||
FileName = model.Attachment.FileName ?? "",
|
||||
ContentType = model.Attachment.ContentType,
|
||||
S3Key = objectKey,
|
||||
FileSize = model.Attachment.FileSize,
|
||||
UploadedAt = currentTime,
|
||||
TenantId = tenantId
|
||||
};
|
||||
|
||||
_context.Documents.Add(document);
|
||||
_logger.LogInfo("Attachment uploaded and document record created. EmployeeId: {EmployeeId}, JobTicketId: {JobTicketId}, DocumentId: {DocumentId}", loggedInEmployee.Id, model.JobTcketId, document.Id);
|
||||
}
|
||||
|
||||
// Create attendance log entry for audit trail
|
||||
var attendanceLog = new JobAttendanceLog
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
EmployeeId = updateJobAttendance.EmployeeId,
|
||||
Action = updateJobAttendance.Action,
|
||||
MarkedTIme = markedAt,
|
||||
MarkedAt = currentTime,
|
||||
Latitude = model.Latitude,
|
||||
Longitude = model.Longitude,
|
||||
Comment = model.Comment,
|
||||
JobAttendanceId = updateJobAttendance.Id,
|
||||
JobTcketId = model.JobTcketId,
|
||||
DocumentId = document?.Id,
|
||||
TenantId = tenantId
|
||||
};
|
||||
|
||||
_context.JobAttendanceLogs.Add(attendanceLog);
|
||||
|
||||
// Persist all changes in one save operation
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
var response = _mapper.Map<JobAttendanceVM>(updateJobAttendance);
|
||||
response.JobTicket = _mapper.Map<BasicJobTicketVM>(jobTicket);
|
||||
response.Employee = _mapper.Map<BasicEmployeeVM>(loggedInEmployee);
|
||||
|
||||
_logger.LogInfo("Tagging managed successfully for EmployeeId: {EmployeeId}, JobTicketId: {JobTicketId}", loggedInEmployee.Id, model.JobTcketId);
|
||||
|
||||
return ApiResponse<object>.SuccessResponse(response, "Tagging managed successfully", 200);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error occurred in ManageJobTaggingAsync for EmployeeId: {EmployeeId}, JobTicketId: {JobTicketId}", loggedInEmployee.Id, model.JobTcketId);
|
||||
return ApiResponse<object>.ErrorResponse("An unexpected error occurred.", ex.Message, 500);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user