diff --git a/BMA.EHR.Application/ApplicationServicesRegistration.cs b/BMA.EHR.Application/ApplicationServicesRegistration.cs index 350b7a75..bf6dc6df 100644 --- a/BMA.EHR.Application/ApplicationServicesRegistration.cs +++ b/BMA.EHR.Application/ApplicationServicesRegistration.cs @@ -53,6 +53,7 @@ namespace BMA.EHR.Application services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); services.AddTransient(); services.AddTransient(); diff --git a/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/CheckInJobStatusRepository.cs b/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/CheckInJobStatusRepository.cs new file mode 100644 index 00000000..302bdd12 --- /dev/null +++ b/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/CheckInJobStatusRepository.cs @@ -0,0 +1,135 @@ +using BMA.EHR.Application.Common.Interfaces; +using BMA.EHR.Domain.Models.Leave.TimeAttendants; +using Microsoft.AspNetCore.Http; +using Microsoft.EntityFrameworkCore; + +namespace BMA.EHR.Application.Repositories.Leaves.TimeAttendants +{ + public class CheckInJobStatusRepository : GenericLeaveRepository + { + #region " Fields " + + private readonly ILeaveDbContext _dbContext; + + #endregion + + #region " Constructor and Destructor " + + public CheckInJobStatusRepository(ILeaveDbContext dbContext, + IHttpContextAccessor httpContextAccessor) : base(dbContext, httpContextAccessor) + { + _dbContext = dbContext; + } + + #endregion + + #region " Methods " + + /// + /// ดึงข้อมูล Job Status จาก TaskId + /// + public async Task GetByTaskIdAsync(Guid taskId) + { + var data = await _dbContext.Set() + .Where(x => x.TaskId == taskId) + .FirstOrDefaultAsync(); + + return data; + } + + /// + /// ดึงข้อมูล Job Status จาก UserId และสถานะ + /// + public async Task> GetByUserIdAndStatusAsync(Guid userId, string status) + { + var data = await _dbContext.Set() + .Where(x => x.KeycloakUserId == userId && x.Status == status) + .OrderByDescending(x => x.CreatedDate) + .ToListAsync(); + + return data; + } + + /// + /// ดึงข้อมูล Job Status ที่ยัง pending หรือ processing + /// + public async Task> GetPendingOrProcessingJobsAsync(Guid userId) + { + var data = await _dbContext.Set() + .Where(x => x.KeycloakUserId == userId && + (x.Status == "PENDING" || x.Status == "PROCESSING")) + //.OrderByDescending(x => x.CreatedDate) + .ToListAsync(); + + return data; + } + + /// + /// อัปเดตสถานะเป็น Processing + /// + public async Task UpdateToProcessingAsync(Guid taskId) + { + var job = await GetByTaskIdAsync(taskId); + if (job != null) + { + job.Status = "PROCESSING"; + job.ProcessingDate = DateTime.Now; + await UpdateAsync(job); + } + return job!; + } + + /// + /// อัปเดตสถานะเป็น Completed + /// + public async Task UpdateToCompletedAsync(Guid taskId, string? additionalData = null) + { + var job = await GetByTaskIdAsync(taskId); + if (job != null) + { + job.Status = "COMPLETED"; + job.CompletedDate = DateTime.Now; + if (!string.IsNullOrEmpty(additionalData)) + { + job.AdditionalData = additionalData; + } + await UpdateAsync(job); + } + return job!; + } + + /// + /// อัปเดตสถานะเป็น Failed + /// + public async Task UpdateToFailedAsync(Guid taskId, string errorMessage) + { + var job = await GetByTaskIdAsync(taskId); + if (job != null) + { + job.Status = "FAILED"; + job.CompletedDate = DateTime.Now; + job.ErrorMessage = errorMessage; + await UpdateAsync(job); + } + return job!; + } + + /// + /// ล้างข้อมูล Job Status ที่เก่าเกิน X วัน + /// + public async Task CleanupOldJobsAsync(int daysOld = 30) + { + var cutoffDate = DateTime.Now.AddDays(-daysOld); + var oldJobs = await _dbContext.Set() + .Where(x => x.CreatedDate < cutoffDate) + .ToListAsync(); + + _dbContext.Set().RemoveRange(oldJobs); + await _dbContext.SaveChangesAsync(); + + return oldJobs.Count; + } + + #endregion + } +} diff --git a/BMA.EHR.CheckInConsumer/Program.cs b/BMA.EHR.CheckInConsumer/Program.cs index e09c61c6..0ae439cf 100644 --- a/BMA.EHR.CheckInConsumer/Program.cs +++ b/BMA.EHR.CheckInConsumer/Program.cs @@ -21,63 +21,52 @@ var queue = configuration["Rabbit:Queue"] ?? "basic-queue"; // create connection var factory = new ConnectionFactory() { - HostName = host, - UserName = user, - Password = pass + //Uri = new Uri("amqp://admin:P@ssw0rd@192.168.4.11:5672") + HostName = host,// หรือ hostname ของ RabbitMQ Server ที่คุณใช้ + UserName = user, // ใส่ชื่อผู้ใช้ของคุณ + Password = pass // ใส่รหัสผ่านของคุณ }; using var connection = factory.CreateConnection(); using var channel = connection.CreateModel(); +//channel.QueueDeclare(queue: "bma-checkin-queue", durable: true, exclusive: false, autoDelete: false, arguments: null); channel.QueueDeclare(queue: queue, durable: true, exclusive: false, autoDelete: false, arguments: null); var consumer = new EventingBasicConsumer(channel); -string? consumerTag = null; -bool isConsuming = false; consumer.Received += async (model, ea) => { var body = ea.Body.ToArray(); var message = Encoding.UTF8.GetString(body); - - // Double-check time before processing (safety check) - if (!IsWithinOperatingHours()) - { - WriteToConsole($"Message received outside operating hours. Requeuing message."); - channel.BasicNack(ea.DeliveryTag, false, true); // Requeue the message - return; - } - await CallRestApi(message); + + // convert string into object + //var request = JsonConvert.DeserializeObject(message); + //using (var db = new ApplicationDbContext()) + //{ + // var item = new AttendantItem + // { + // Name = request.Name, + // CheckInDateTime = request.CheckInDateTime, + // }; + // db.AttendantItems.Add(item); + // db.SaveChanges(); + + // WriteToConsole($"ได้รับคำขอจาก Queue: {message}"); + // WriteToConsole($"ตอบกลับจาก REST API: {JsonConvert.SerializeObject(item)}"); + //} + WriteToConsole($"ได้รับคำขอจาก Queue: {message}"); + //WriteToConsole($"ตอบกลับจาก REST API: {JsonConvert.SerializeObject(item)}"); }; -// Monitor and control consumer based on time schedule -using var timer = new PeriodicTimer(TimeSpan.FromMinutes(1)); +//channel.BasicConsume(queue: "bma-checkin-queue", autoAck: true, consumer: consumer); +channel.BasicConsume(queue: queue, autoAck: true, consumer: consumer); -while (await timer.WaitForNextTickAsync()) -{ - var shouldBeConsuming = IsWithinOperatingHours(); - - if (shouldBeConsuming && !isConsuming) - { - // Start consuming - consumerTag = channel.BasicConsume(queue: queue, autoAck: true, consumer: consumer); - isConsuming = true; - WriteToConsole($"✅ Started consuming messages at {GetCurrentBangkokTime():yyyy-MM-dd HH:mm:ss}"); - } - else if (!shouldBeConsuming && isConsuming) - { - // Stop consuming - if (consumerTag != null) - { - channel.BasicCancel(consumerTag); - consumerTag = null; - } - isConsuming = false; - WriteToConsole($"⏸️ Stopped consuming messages at {GetCurrentBangkokTime():yyyy-MM-dd HH:mm:ss}. Will resume after 08:10 tomorrow."); - } -} +//Console.WriteLine("\nPress 'Enter' to exit the process..."); + +await Task.Delay(-1); static void WriteToConsole(string message) { @@ -106,25 +95,6 @@ async Task CallRestApi(string requestData) } } -DateTime GetCurrentBangkokTime() -{ - var bangkokTimeZone = TimeZoneInfo.FindSystemTimeZoneById("SE Asia Standard Time"); - return TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, bangkokTimeZone); -} - -bool IsWithinOperatingHours() -{ - var currentTime = GetCurrentBangkokTime(); - var startTime = new TimeSpan(8, 10, 0); // 8:10 AM - var endTime = new TimeSpan(23, 59, 59); // End of day - - var currentTimeOfDay = currentTime.TimeOfDay; - - // Consumer should only work from 8:10 AM to end of day - return currentTimeOfDay >= startTime && currentTimeOfDay <= endTime; -} - - public class ResponseObject { [JsonPropertyName("status")] diff --git a/BMA.EHR.CheckInConsumer/appsettings.json b/BMA.EHR.CheckInConsumer/appsettings.json index b180f90c..76f86c86 100644 --- a/BMA.EHR.CheckInConsumer/appsettings.json +++ b/BMA.EHR.CheckInConsumer/appsettings.json @@ -1,9 +1,9 @@ { - "Rabbit": { - "Host": "192.168.1.40", - "User": "admin", - "Password": "Test123456", - "Queue": "bma-checkin-queue" - }, - "API": "https://localhost:7283/api/v1" -} \ No newline at end of file + "Rabbit": { + "Host": "192.168.1.63", + "User": "admin", + "Password": "12345678", + "Queue": "hrms-checkin-queue-dev" + }, + "API": "https://localhost:7283/api/v1" +} diff --git a/BMA.EHR.Domain/Models/Leave/TimeAttendants/CheckInJobStatus.cs b/BMA.EHR.Domain/Models/Leave/TimeAttendants/CheckInJobStatus.cs new file mode 100644 index 00000000..922f966b --- /dev/null +++ b/BMA.EHR.Domain/Models/Leave/TimeAttendants/CheckInJobStatus.cs @@ -0,0 +1,39 @@ +using BMA.EHR.Domain.Models.Base; +using Microsoft.EntityFrameworkCore; +using System.ComponentModel.DataAnnotations; + +namespace BMA.EHR.Domain.Models.Leave.TimeAttendants +{ + public class CheckInJobStatus : EntityBase + { + [Required, Comment("Task ID สำหรับติดตามสถานะงาน")] + public Guid TaskId { get; set; } = Guid.Empty; + + [Required, Comment("รหัส User ของ Keycloak")] + public Guid KeycloakUserId { get; set; } = Guid.Empty; + + [Comment("วันเวลาที่สร้างงาน")] + public DateTime CreatedDate { get; set; } = DateTime.Now; + + [Comment("วันเวลาที่เริ่มประมวลผล")] + public DateTime? ProcessingDate { get; set; } + + [Comment("วันเวลาที่เสร็จสิ้นการประมวลผล")] + public DateTime? CompletedDate { get; set; } + + [Required, Comment("สถานะงาน: PENDING, PROCESSING, COMPLETED, FAILED")] + public string Status { get; set; } = "PENDING"; + + [Comment("ประเภทการลงเวลา: CHECK_IN, CHECK_OUT")] + public string? CheckType { get; set; } + + [Comment("CheckInId สำหรับ Check-Out")] + public Guid? CheckInId { get; set; } + + [Comment("ข้อความแสดงข้อผิดพลาด")] + public string? ErrorMessage { get; set; } + + [Comment("ข้อมูลเพิ่มเติม (JSON)")] + public string? AdditionalData { get; set; } + } +} diff --git a/BMA.EHR.Infrastructure/Migrations/LeaveDb/20260120032158_Add RMQ Task Control.Designer.cs b/BMA.EHR.Infrastructure/Migrations/LeaveDb/20260120032158_Add RMQ Task Control.Designer.cs new file mode 100644 index 00000000..a7f4e1fb --- /dev/null +++ b/BMA.EHR.Infrastructure/Migrations/LeaveDb/20260120032158_Add RMQ Task Control.Designer.cs @@ -0,0 +1,1705 @@ +// +using System; +using BMA.EHR.Infrastructure.Persistence; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace BMA.EHR.Infrastructure.Migrations.LeaveDb +{ + [DbContext(typeof(LeaveDbContext))] + [Migration("20260120032158_Add RMQ Task Control")] + partial class AddRMQTaskControl + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.9") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Documents.Document", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedDate") + .HasColumnType("datetime(6)"); + + b.Property("Detail") + .IsRequired() + .HasColumnType("text"); + + b.Property("FileName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("FileSize") + .HasColumnType("int"); + + b.Property("FileType") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("varchar(128)"); + + b.Property("ObjectRefId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.ToTable("Document"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.Commons.LeaveType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnOrder(0) + .HasComment("PrimaryKey") + .HasAnnotation("Relational:JsonPropertyName", "id"); + + b.Property("Code") + .IsRequired() + .HasColumnType("longtext") + .HasComment("รหัสประเภทการลา"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(100) + .HasComment("สร้างข้อมูลเมื่อ"); + + b.Property("CreatedFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(104) + .HasComment("ชื่อ User ที่สร้างข้อมูล"); + + b.Property("CreatedUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(101) + .HasComment("User Id ที่สร้างข้อมูล"); + + b.Property("LastUpdateFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(105) + .HasComment("ชื่อ User ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdateUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(103) + .HasComment("User Id ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(102) + .HasComment("แก้ไขข้อมูลล่าสุดเมื่อ"); + + b.Property("Limit") + .HasColumnType("int") + .HasComment("จำนวนวันลาสูงสุดประจำปี"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext") + .HasComment("ชื่อประเภทการลา"); + + b.HasKey("Id"); + + b.ToTable("LeaveTypes"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.Requests.LeaveBeginning", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnOrder(0) + .HasComment("PrimaryKey") + .HasAnnotation("Relational:JsonPropertyName", "id"); + + b.Property("Child1DnaId") + .HasColumnType("char(36)"); + + b.Property("Child2DnaId") + .HasColumnType("char(36)"); + + b.Property("Child3DnaId") + .HasColumnType("char(36)"); + + b.Property("Child4DnaId") + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(100) + .HasComment("สร้างข้อมูลเมื่อ"); + + b.Property("CreatedFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(104) + .HasComment("ชื่อ User ที่สร้างข้อมูล"); + + b.Property("CreatedUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(101) + .HasComment("User Id ที่สร้างข้อมูล"); + + b.Property("FirstName") + .HasColumnType("longtext"); + + b.Property("LastName") + .HasColumnType("longtext"); + + b.Property("LastUpdateFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(105) + .HasComment("ชื่อ User ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdateUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(103) + .HasComment("User Id ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(102) + .HasComment("แก้ไขข้อมูลล่าสุดเมื่อ"); + + b.Property("LeaveDays") + .HasColumnType("double") + .HasComment("จำนวนวันลายกมา"); + + b.Property("LeaveDaysUsed") + .HasColumnType("double") + .HasComment("จำนวนวันลาที่ใช้ไป"); + + b.Property("LeaveTypeId") + .HasColumnType("char(36)") + .HasComment("รหัสประเภทการลา"); + + b.Property("LeaveYear") + .HasColumnType("int") + .HasComment("ปีงบประมาณ"); + + b.Property("Prefix") + .HasColumnType("longtext"); + + b.Property("ProfileId") + .HasColumnType("char(36)") + .HasComment("รหัส Profile ในระบบทะเบียนประวัติ"); + + b.Property("RootDnaId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("LeaveTypeId"); + + b.ToTable("LeaveBeginnings"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.Requests.LeaveDocument", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnOrder(0) + .HasComment("PrimaryKey") + .HasAnnotation("Relational:JsonPropertyName", "id"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(100) + .HasComment("สร้างข้อมูลเมื่อ"); + + b.Property("CreatedFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(104) + .HasComment("ชื่อ User ที่สร้างข้อมูล"); + + b.Property("CreatedUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(101) + .HasComment("User Id ที่สร้างข้อมูล"); + + b.Property("DocumentId") + .HasColumnType("char(36)"); + + b.Property("LastUpdateFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(105) + .HasComment("ชื่อ User ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdateUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(103) + .HasComment("User Id ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(102) + .HasComment("แก้ไขข้อมูลล่าสุดเมื่อ"); + + b.Property("LeaveRequestId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("DocumentId"); + + b.HasIndex("LeaveRequestId"); + + b.ToTable("LeaveDocuments"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.Requests.LeaveRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnOrder(0) + .HasComment("PrimaryKey") + .HasAnnotation("Relational:JsonPropertyName", "id"); + + b.Property("AbsentDayAt") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("AbsentDayGetIn") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("AbsentDayLocation") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("AbsentDayRegistorDate") + .HasColumnType("datetime(6)"); + + b.Property("AbsentDaySummon") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Amount") + .HasColumnType("double"); + + b.Property("ApproveStep") + .HasColumnType("longtext") + .HasComment("step การอนุมัติ st1 = จทน.อนุมัตื,st2 = ผู้บังคับบัญชา อนุมัติ "); + + b.Property("BirthDate") + .HasColumnType("datetime(6)"); + + b.Property("CancelLeaveWrote") + .HasColumnType("longtext") + .HasComment("เขียนที่ (ขอยกเลิก)"); + + b.Property("Child1") + .HasColumnType("longtext"); + + b.Property("Child1DnaId") + .HasColumnType("char(36)"); + + b.Property("Child1Id") + .HasColumnType("char(36)"); + + b.Property("Child2") + .HasColumnType("longtext"); + + b.Property("Child2DnaId") + .HasColumnType("char(36)"); + + b.Property("Child2Id") + .HasColumnType("char(36)"); + + b.Property("Child3") + .HasColumnType("longtext"); + + b.Property("Child3DnaId") + .HasColumnType("char(36)"); + + b.Property("Child3Id") + .HasColumnType("char(36)"); + + b.Property("Child4") + .HasColumnType("longtext"); + + b.Property("Child4DnaId") + .HasColumnType("char(36)"); + + b.Property("Child4Id") + .HasColumnType("char(36)"); + + b.Property("CitizenId") + .HasColumnType("longtext"); + + b.Property("CommanderPosition") + .HasColumnType("longtext"); + + b.Property("CoupleDayCountryHistory") + .HasColumnType("longtext"); + + b.Property("CoupleDayEndDateHistory") + .HasColumnType("datetime(6)"); + + b.Property("CoupleDayLevel") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CoupleDayLevelCountry") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CoupleDayName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CoupleDayPosition") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CoupleDayStartDateHistory") + .HasColumnType("datetime(6)"); + + b.Property("CoupleDaySumTotalHistory") + .HasColumnType("longtext"); + + b.Property("CoupleDayTotalHistory") + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(100) + .HasComment("สร้างข้อมูลเมื่อ"); + + b.Property("CreatedFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(104) + .HasComment("ชื่อ User ที่สร้างข้อมูล"); + + b.Property("CreatedUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(101) + .HasComment("User Id ที่สร้างข้อมูล"); + + b.Property("DateAppoint") + .HasColumnType("datetime(6)"); + + b.Property("Dear") + .HasColumnType("longtext") + .HasComment("เรียนใคร"); + + b.Property("FirstName") + .HasColumnType("longtext"); + + b.Property("Gender") + .HasColumnType("longtext"); + + b.Property("HajjDayStatus") + .HasColumnType("tinyint(1)"); + + b.Property("KeycloakUserId") + .HasColumnType("char(36)"); + + b.Property("LastName") + .HasColumnType("longtext"); + + b.Property("LastUpdateFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(105) + .HasComment("ชื่อ User ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdateUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(103) + .HasComment("User Id ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(102) + .HasComment("แก้ไขข้อมูลล่าสุดเมื่อ"); + + b.Property("LeaveAddress") + .IsRequired() + .HasColumnType("longtext") + .HasComment("สถานที่ติดต่อขณะลา"); + + b.Property("LeaveBirthDate") + .HasColumnType("datetime(6)"); + + b.Property("LeaveCancelComment") + .HasColumnType("longtext") + .HasComment("เหตุผลในการขอยกเลิก"); + + b.Property("LeaveCancelDocumentId") + .HasColumnType("char(36)"); + + b.Property("LeaveCancelStatus") + .HasColumnType("longtext") + .HasComment("สถานะของคำขอยกเลิก"); + + b.Property("LeaveComment") + .HasColumnType("longtext") + .HasComment("ความเห็นของผู้บังคับบัญชา"); + + b.Property("LeaveDetail") + .IsRequired() + .HasColumnType("longtext") + .HasComment("รายละเอียดการลา"); + + b.Property("LeaveDirectorComment") + .HasColumnType("longtext") + .HasComment("ความเห็นของผู้อำนวยการสำนัก"); + + b.Property("LeaveDraftDocumentId") + .HasColumnType("char(36)"); + + b.Property("LeaveEndDate") + .HasColumnType("datetime(6)") + .HasComment("วัน เดือน ปีสิ้นสุดลา"); + + b.Property("LeaveGovernmentDate") + .HasColumnType("datetime(6)"); + + b.Property("LeaveLast") + .HasColumnType("datetime(6)"); + + b.Property("LeaveNumber") + .IsRequired() + .HasColumnType("longtext") + .HasComment("หมายเลขที่ติดต่อขณะลา"); + + b.Property("LeaveRange") + .HasColumnType("longtext") + .HasComment("ช่วงของการลาของวันเริ่ม เช่น ลาทั้งวัน ครึ่งวันเช้า ครึ่งวันบ่าย"); + + b.Property("LeaveRangeEnd") + .HasColumnType("longtext") + .HasComment("ช่วงของการลาของวันสิ้นสุด เช่น ลาทั้งวัน ครึ่งวันเช้า ครึ่งวันบ่าย"); + + b.Property("LeaveSalary") + .HasColumnType("int"); + + b.Property("LeaveSalaryText") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("LeaveStartDate") + .HasColumnType("datetime(6)") + .HasComment("วัน เดือน ปีเริ่มต้นลา"); + + b.Property("LeaveStatus") + .IsRequired() + .HasColumnType("longtext") + .HasComment("สถานะของคำร้อง"); + + b.Property("LeaveSubTypeName") + .HasColumnType("longtext"); + + b.Property("LeaveTotal") + .HasColumnType("double"); + + b.Property("LeaveTypeCode") + .HasColumnType("longtext") + .HasComment("code ของประเภทการลา"); + + b.Property("LeaveWrote") + .IsRequired() + .HasColumnType("longtext") + .HasComment("เขียนที่"); + + b.Property("OrdainDayBuddhistLentAddress") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("OrdainDayBuddhistLentName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("OrdainDayLocationAddress") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("OrdainDayLocationName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("OrdainDayLocationNumber") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("OrdainDayOrdination") + .HasColumnType("datetime(6)"); + + b.Property("OrdainDayStatus") + .HasColumnType("tinyint(1)"); + + b.Property("OrganizationName") + .HasColumnType("longtext") + .HasComment("สังกัดผู้ยื่นขอ"); + + b.Property("PositionLevelName") + .HasColumnType("longtext") + .HasComment("ระดับผู้ยื่นขอ"); + + b.Property("PositionName") + .HasColumnType("longtext") + .HasComment("ตำแหน่งผู้ยื่นขอ"); + + b.Property("Prefix") + .HasColumnType("longtext"); + + b.Property("ProfileId") + .HasColumnType("char(36)"); + + b.Property("ProfileType") + .HasColumnType("longtext"); + + b.Property("RestDayCurrentTotal") + .HasColumnType("double"); + + b.Property("RestDayOldTotal") + .HasColumnType("double"); + + b.Property("Root") + .HasColumnType("longtext"); + + b.Property("RootDnaId") + .HasColumnType("char(36)"); + + b.Property("RootId") + .HasColumnType("char(36)"); + + b.Property("StudyDayCountry") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("StudyDayDegreeLevel") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("StudyDayScholarship") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("StudyDaySubject") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("StudyDayTrainingName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("StudyDayTrainingSubject") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("StudyDayUniversityName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("TypeId") + .HasColumnType("char(36)"); + + b.Property("WifeDayDateBorn") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("WifeDayName") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("LeaveCancelDocumentId"); + + b.HasIndex("LeaveDraftDocumentId"); + + b.HasIndex("TypeId"); + + b.ToTable("LeaveRequests"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.Requests.LeaveRequestApprover", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnOrder(0) + .HasComment("PrimaryKey") + .HasAnnotation("Relational:JsonPropertyName", "id"); + + b.Property("ApproveStatus") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("ApproveType") + .HasColumnType("longtext"); + + b.Property("Comment") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(100) + .HasComment("สร้างข้อมูลเมื่อ"); + + b.Property("CreatedFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(104) + .HasComment("ชื่อ User ที่สร้างข้อมูล"); + + b.Property("CreatedUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(101) + .HasComment("User Id ที่สร้างข้อมูล"); + + b.Property("FirstName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("KeycloakId") + .HasColumnType("char(36)"); + + b.Property("LastName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("LastUpdateFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(105) + .HasComment("ชื่อ User ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdateUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(103) + .HasComment("User Id ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(102) + .HasComment("แก้ไขข้อมูลล่าสุดเมื่อ"); + + b.Property("LeaveRequestId") + .HasColumnType("char(36)"); + + b.Property("OrganizationName") + .IsRequired() + .HasColumnType("longtext") + .HasComment("สังกัด"); + + b.Property("PosExecutiveName") + .IsRequired() + .HasColumnType("longtext") + .HasComment("ตำแหน่งทางการบริหาร"); + + b.Property("PositionLevelName") + .IsRequired() + .HasColumnType("longtext") + .HasComment("ประเภทระดับตำแหน่ง"); + + b.Property("PositionName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("PositionSign") + .HasColumnType("longtext") + .HasComment("ตำแหน่งใต้ลายเช็นต์"); + + b.Property("Prefix") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("ProfileId") + .HasColumnType("char(36)"); + + b.Property("Seq") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("LeaveRequestId"); + + b.ToTable("LeaveRequestApprovers"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.TimeAttendants.AdditionalCheckRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnOrder(0) + .HasComment("PrimaryKey") + .HasAnnotation("Relational:JsonPropertyName", "id"); + + b.Property("CheckDate") + .HasColumnType("datetime(6)") + .HasComment("*วันที่ลงเวลา"); + + b.Property("CheckInEdit") + .HasColumnType("tinyint(1)") + .HasComment("*ขอลงเวลาช่วงเช้า"); + + b.Property("CheckOutEdit") + .HasColumnType("tinyint(1)") + .HasComment("*ขอลงเวลาช่วงบ่าย"); + + b.Property("Child1DnaId") + .HasColumnType("char(36)"); + + b.Property("Child2DnaId") + .HasColumnType("char(36)"); + + b.Property("Child3DnaId") + .HasColumnType("char(36)"); + + b.Property("Child4DnaId") + .HasColumnType("char(36)"); + + b.Property("Comment") + .HasColumnType("longtext") + .HasComment("หมายเหตุในการการอนุมัติ/ไม่อนุมัติ"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(100) + .HasComment("สร้างข้อมูลเมื่อ"); + + b.Property("CreatedFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(104) + .HasComment("ชื่อ User ที่สร้างข้อมูล"); + + b.Property("CreatedUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(101) + .HasComment("User Id ที่สร้างข้อมูล"); + + b.Property("Description") + .IsRequired() + .HasColumnType("longtext") + .HasComment("*หมายเหตุขอลงเวลาพิเศษ"); + + b.Property("FirstName") + .HasColumnType("longtext"); + + b.Property("KeycloakUserId") + .HasColumnType("char(36)") + .HasComment("รหัส User ของ Keycloak ที่ร้องขอ"); + + b.Property("LastName") + .HasColumnType("longtext"); + + b.Property("LastUpdateFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(105) + .HasComment("ชื่อ User ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdateUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(103) + .HasComment("User Id ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(102) + .HasComment("แก้ไขข้อมูลล่าสุดเมื่อ"); + + b.Property("Latitude") + .HasColumnType("double"); + + b.Property("Longitude") + .HasColumnType("double"); + + b.Property("POI") + .HasColumnType("longtext"); + + b.Property("Prefix") + .HasColumnType("longtext"); + + b.Property("RootDnaId") + .HasColumnType("char(36)"); + + b.Property("Status") + .IsRequired() + .HasColumnType("longtext") + .HasComment("สถานะการอนุมัติ"); + + b.HasKey("Id"); + + b.ToTable("AdditionalCheckRequests"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.TimeAttendants.CheckInJobStatus", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnOrder(0) + .HasComment("PrimaryKey") + .HasAnnotation("Relational:JsonPropertyName", "id"); + + b.Property("AdditionalData") + .HasColumnType("longtext") + .HasComment("ข้อมูลเพิ่มเติม (JSON)"); + + b.Property("CheckInId") + .HasColumnType("char(36)") + .HasComment("CheckInId สำหรับ Check-Out"); + + b.Property("CheckType") + .HasColumnType("longtext") + .HasComment("ประเภทการลงเวลา: CHECK_IN, CHECK_OUT"); + + b.Property("CompletedDate") + .HasColumnType("datetime(6)") + .HasComment("วันเวลาที่เสร็จสิ้นการประมวลผล"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(100) + .HasComment("สร้างข้อมูลเมื่อ"); + + b.Property("CreatedDate") + .HasColumnType("datetime(6)") + .HasComment("วันเวลาที่สร้างงาน"); + + b.Property("CreatedFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(104) + .HasComment("ชื่อ User ที่สร้างข้อมูล"); + + b.Property("CreatedUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(101) + .HasComment("User Id ที่สร้างข้อมูล"); + + b.Property("ErrorMessage") + .HasColumnType("longtext") + .HasComment("ข้อความแสดงข้อผิดพลาด"); + + b.Property("KeycloakUserId") + .HasColumnType("char(36)") + .HasComment("รหัส User ของ Keycloak"); + + b.Property("LastUpdateFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(105) + .HasComment("ชื่อ User ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdateUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(103) + .HasComment("User Id ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(102) + .HasComment("แก้ไขข้อมูลล่าสุดเมื่อ"); + + b.Property("ProcessingDate") + .HasColumnType("datetime(6)") + .HasComment("วันเวลาที่เริ่มประมวลผล"); + + b.Property("Status") + .IsRequired() + .HasColumnType("longtext") + .HasComment("สถานะงาน: PENDING, PROCESSING, COMPLETED, FAILED"); + + b.Property("TaskId") + .HasColumnType("char(36)") + .HasComment("Task ID สำหรับติดตามสถานะงาน"); + + b.HasKey("Id"); + + b.ToTable("CheckInJobStatuses"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.TimeAttendants.DutyTime", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnOrder(0) + .HasComment("PrimaryKey") + .HasAnnotation("Relational:JsonPropertyName", "id"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(100) + .HasComment("สร้างข้อมูลเมื่อ"); + + b.Property("CreatedFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(104) + .HasComment("ชื่อ User ที่สร้างข้อมูล"); + + b.Property("CreatedUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(101) + .HasComment("User Id ที่สร้างข้อมูล"); + + b.Property("Description") + .IsRequired() + .HasColumnType("longtext") + .HasComment("คำอธิบาย"); + + b.Property("EndTimeAfternoon") + .IsRequired() + .HasColumnType("longtext") + .HasComment("เวลาออกงานช่วงบ่าย"); + + b.Property("EndTimeMorning") + .IsRequired() + .HasColumnType("longtext") + .HasComment("เวลาออกงานช่วงเช้า"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)") + .HasComment("สถานะการเปิดใช้งาน (เปิด/ปิด)"); + + b.Property("IsDefault") + .HasColumnType("tinyint(1)") + .HasComment("สถานะว่ารอบใดเป็นค่า Default ของข้าราชการ (สำหรับทุกคนที่ยังไม่ได้ทำการเลือกรอบ)"); + + b.Property("LastUpdateFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(105) + .HasComment("ชื่อ User ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdateUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(103) + .HasComment("User Id ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(102) + .HasComment("แก้ไขข้อมูลล่าสุดเมื่อ"); + + b.Property("StartTimeAfternoon") + .IsRequired() + .HasColumnType("longtext") + .HasComment("เวลาเข้างานช่วงบ่าย"); + + b.Property("StartTimeMorning") + .IsRequired() + .HasColumnType("longtext") + .HasComment("เวลาเข้างานช่วงเช้า"); + + b.HasKey("Id"); + + b.ToTable("DutyTimes"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.TimeAttendants.ProcessUserTimeStamp", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnOrder(0) + .HasComment("PrimaryKey") + .HasAnnotation("Relational:JsonPropertyName", "id"); + + b.Property("CheckIn") + .HasColumnType("datetime(6)") + .HasComment("วัน เวลา เข้างาน"); + + b.Property("CheckInImageUrl") + .IsRequired() + .HasColumnType("longtext") + .HasComment("รูปถ่ายสถานที่ Check-In"); + + b.Property("CheckInLat") + .HasColumnType("double") + .HasComment("พิกัดละติจูด Check-In"); + + b.Property("CheckInLocationName") + .HasColumnType("longtext") + .HasComment("กรณีเลือกนอกสถานที่ตั้ง ต้องระบุข้อมูลชื่อสถานะที่ Check-In"); + + b.Property("CheckInLon") + .HasColumnType("double") + .HasComment("พิกัดลองจิจูด Check-In"); + + b.Property("CheckInPOI") + .IsRequired() + .HasColumnType("longtext") + .HasComment("ชื่อสถานที่ ได้มาจากระบบ ArcGis ของกองสารสนเทศภูมิศาสตร์ Check-In"); + + b.Property("CheckInRemark") + .HasColumnType("longtext") + .HasComment("ข้อความหมายเหตุที่ต้องการระบุเพิ่ม(มีเผื่อไว้อาจไม่ได้ใช้) Check-In"); + + b.Property("CheckInStatus") + .IsRequired() + .HasColumnType("longtext") + .HasComment("สถานะ Check-In"); + + b.Property("CheckOut") + .HasColumnType("datetime(6)") + .HasComment("วัน เวลา ออกงาน"); + + b.Property("CheckOutImageUrl") + .IsRequired() + .HasColumnType("longtext") + .HasComment("รูปถ่ายสถานที่ Check-Out"); + + b.Property("CheckOutLat") + .HasColumnType("double") + .HasComment("พิกัดละติจูด Check-Out"); + + b.Property("CheckOutLocationName") + .HasColumnType("longtext") + .HasComment("กรณีเลือกนอกสถานที่ตั้ง ต้องระบุข้อมูลชื่อสถานะที่ Check-Out"); + + b.Property("CheckOutLon") + .HasColumnType("double") + .HasComment("พิกัดลองจิจูด Check-Out"); + + b.Property("CheckOutPOI") + .IsRequired() + .HasColumnType("longtext") + .HasComment("ชื่อสถานที่ ได้มาจากระบบ ArcGis ของกองสารสนเทศภูมิศาสตร์ Check-Out"); + + b.Property("CheckOutRemark") + .HasColumnType("longtext") + .HasComment("ข้อความหมายเหตุที่ต้องการระบุเพิ่ม(มีเผื่อไว้อาจไม่ได้ใช้) Check-Out"); + + b.Property("CheckOutStatus") + .HasColumnType("longtext") + .HasComment("สถานะ Check-Out"); + + b.Property("Child1") + .HasColumnType("longtext"); + + b.Property("Child1DnaId") + .HasColumnType("char(36)"); + + b.Property("Child1Id") + .HasColumnType("char(36)"); + + b.Property("Child2") + .HasColumnType("longtext"); + + b.Property("Child2DnaId") + .HasColumnType("char(36)"); + + b.Property("Child2Id") + .HasColumnType("char(36)"); + + b.Property("Child3") + .HasColumnType("longtext"); + + b.Property("Child3DnaId") + .HasColumnType("char(36)"); + + b.Property("Child3Id") + .HasColumnType("char(36)"); + + b.Property("Child4") + .HasColumnType("longtext"); + + b.Property("Child4DnaId") + .HasColumnType("char(36)"); + + b.Property("Child4Id") + .HasColumnType("char(36)"); + + b.Property("CitizenId") + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(100) + .HasComment("สร้างข้อมูลเมื่อ"); + + b.Property("CreatedFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(104) + .HasComment("ชื่อ User ที่สร้างข้อมูล"); + + b.Property("CreatedUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(101) + .HasComment("User Id ที่สร้างข้อมูล"); + + b.Property("EditReason") + .HasColumnType("longtext") + .HasComment("เหตุผลการอนุมัติ/ไม่อนุมัติขอลงเวลาพิเศษ"); + + b.Property("EditStatus") + .HasColumnType("longtext") + .HasComment("สถานะการของลงเวลาพิเศษ"); + + b.Property("FirstName") + .HasColumnType("longtext"); + + b.Property("Gender") + .HasColumnType("longtext"); + + b.Property("IsLocationCheckIn") + .HasColumnType("tinyint(1)") + .HasComment("true คือ ณ สถานที่ตั้ง, false คือ นอกสถานที่ตั้ง Check-In"); + + b.Property("IsLocationCheckOut") + .HasColumnType("tinyint(1)") + .HasComment("true คือ ณ สถานที่ตั้ง, false คือ นอกสถานที่ตั้ง Check-Out"); + + b.Property("IsProcess") + .HasColumnType("tinyint(1)") + .HasComment("นำไปประมวลผลแล้วหรือยัง"); + + b.Property("KeycloakUserId") + .HasColumnType("char(36)") + .HasComment("รหัส User ของ Keycloak"); + + b.Property("LastName") + .HasColumnType("longtext"); + + b.Property("LastUpdateFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(105) + .HasComment("ชื่อ User ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdateUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(103) + .HasComment("User Id ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(102) + .HasComment("แก้ไขข้อมูลล่าสุดเมื่อ"); + + b.Property("Prefix") + .HasColumnType("longtext"); + + b.Property("ProfileId") + .HasColumnType("char(36)"); + + b.Property("ProfileType") + .HasColumnType("longtext"); + + b.Property("Root") + .HasColumnType("longtext"); + + b.Property("RootDnaId") + .HasColumnType("char(36)"); + + b.Property("RootId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.ToTable("ProcessUserTimeStamps"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.TimeAttendants.UserCalendar", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnOrder(0) + .HasComment("PrimaryKey") + .HasAnnotation("Relational:JsonPropertyName", "id"); + + b.Property("Calendar") + .IsRequired() + .HasColumnType("longtext") + .HasComment("ปฏิทินการทำงานของ ขรก ปกติ หรือ 6 วันต่อสัปดาห์"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(100) + .HasComment("สร้างข้อมูลเมื่อ"); + + b.Property("CreatedFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(104) + .HasComment("ชื่อ User ที่สร้างข้อมูล"); + + b.Property("CreatedUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(101) + .HasComment("User Id ที่สร้างข้อมูล"); + + b.Property("LastUpdateFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(105) + .HasComment("ชื่อ User ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdateUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(103) + .HasComment("User Id ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(102) + .HasComment("แก้ไขข้อมูลล่าสุดเมื่อ"); + + b.Property("ProfileId") + .HasColumnType("char(36)") + .HasComment("รหัส Profile ในระบบทะเบียนประวัติ"); + + b.HasKey("Id"); + + b.ToTable("UserCalendars"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.TimeAttendants.UserDutyTime", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnOrder(0) + .HasComment("PrimaryKey") + .HasAnnotation("Relational:JsonPropertyName", "id"); + + b.Property("Child1DnaId") + .HasColumnType("char(36)"); + + b.Property("Child2DnaId") + .HasColumnType("char(36)"); + + b.Property("Child3DnaId") + .HasColumnType("char(36)"); + + b.Property("Child4DnaId") + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(100) + .HasComment("สร้างข้อมูลเมื่อ"); + + b.Property("CreatedFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(104) + .HasComment("ชื่อ User ที่สร้างข้อมูล"); + + b.Property("CreatedUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(101) + .HasComment("User Id ที่สร้างข้อมูล"); + + b.Property("DutyTimeId") + .HasColumnType("char(36)") + .HasComment("รหัสรอบการลงเวลา"); + + b.Property("EffectiveDate") + .HasColumnType("datetime(6)") + .HasComment("วันที่มีผล"); + + b.Property("IsProcess") + .HasColumnType("tinyint(1)") + .HasComment("ทำการประมวลผลแล้วหรือยัง"); + + b.Property("LastUpdateFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(105) + .HasComment("ชื่อ User ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdateUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(103) + .HasComment("User Id ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(102) + .HasComment("แก้ไขข้อมูลล่าสุดเมื่อ"); + + b.Property("ProfileId") + .HasColumnType("char(36)") + .HasComment("รหัส Profile ในระบบทะเบียนประวัติ"); + + b.Property("Remark") + .HasColumnType("longtext") + .HasComment("หมายเหตุ"); + + b.Property("RootDnaId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("DutyTimeId"); + + b.ToTable("UserDutyTimes"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.TimeAttendants.UserTimeStamp", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnOrder(0) + .HasComment("PrimaryKey") + .HasAnnotation("Relational:JsonPropertyName", "id"); + + b.Property("CheckIn") + .HasColumnType("datetime(6)") + .HasComment("วัน เวลา เข้างาน"); + + b.Property("CheckInImageUrl") + .IsRequired() + .HasColumnType("longtext") + .HasComment("รูปถ่ายสถานที่ Check-In"); + + b.Property("CheckInLat") + .HasColumnType("double") + .HasComment("พิกัดละติจูด Check-In"); + + b.Property("CheckInLocationName") + .HasColumnType("longtext") + .HasComment("กรณีเลือกนอกสถานที่ตั้ง ต้องระบุข้อมูลชื่อสถานะที่ Check-In"); + + b.Property("CheckInLon") + .HasColumnType("double") + .HasComment("พิกัดลองจิจูด Check-In"); + + b.Property("CheckInPOI") + .IsRequired() + .HasColumnType("longtext") + .HasComment("ชื่อสถานที่ ได้มาจากระบบ ArcGis ของกองสารสนเทศภูมิศาสตร์ Check-In"); + + b.Property("CheckInRemark") + .HasColumnType("longtext") + .HasComment("ข้อความหมายเหตุที่ต้องการระบุเพิ่ม(มีเผื่อไว้อาจไม่ได้ใช้) Check-In"); + + b.Property("CheckOut") + .HasColumnType("datetime(6)") + .HasComment("วัน เวลา ออกงาน"); + + b.Property("CheckOutImageUrl") + .IsRequired() + .HasColumnType("longtext") + .HasComment("รูปถ่ายสถานที่ Check-Out"); + + b.Property("CheckOutLat") + .HasColumnType("double") + .HasComment("พิกัดละติจูด Check-Out"); + + b.Property("CheckOutLocationName") + .HasColumnType("longtext") + .HasComment("กรณีเลือกนอกสถานที่ตั้ง ต้องระบุข้อมูลชื่อสถานะที่ Check-Out"); + + b.Property("CheckOutLon") + .HasColumnType("double") + .HasComment("พิกัดลองจิจูด Check-Out"); + + b.Property("CheckOutPOI") + .IsRequired() + .HasColumnType("longtext") + .HasComment("ชื่อสถานที่ ได้มาจากระบบ ArcGis ของกองสารสนเทศภูมิศาสตร์ Check-Out"); + + b.Property("CheckOutRemark") + .HasColumnType("longtext") + .HasComment("ข้อความหมายเหตุที่ต้องการระบุเพิ่ม(มีเผื่อไว้อาจไม่ได้ใช้) Check-Out"); + + b.Property("Child1") + .HasColumnType("longtext"); + + b.Property("Child1DnaId") + .HasColumnType("char(36)"); + + b.Property("Child1Id") + .HasColumnType("char(36)"); + + b.Property("Child2") + .HasColumnType("longtext"); + + b.Property("Child2DnaId") + .HasColumnType("char(36)"); + + b.Property("Child2Id") + .HasColumnType("char(36)"); + + b.Property("Child3") + .HasColumnType("longtext"); + + b.Property("Child3DnaId") + .HasColumnType("char(36)"); + + b.Property("Child3Id") + .HasColumnType("char(36)"); + + b.Property("Child4") + .HasColumnType("longtext"); + + b.Property("Child4DnaId") + .HasColumnType("char(36)"); + + b.Property("Child4Id") + .HasColumnType("char(36)"); + + b.Property("CitizenId") + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(100) + .HasComment("สร้างข้อมูลเมื่อ"); + + b.Property("CreatedFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(104) + .HasComment("ชื่อ User ที่สร้างข้อมูล"); + + b.Property("CreatedUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(101) + .HasComment("User Id ที่สร้างข้อมูล"); + + b.Property("FirstName") + .HasColumnType("longtext"); + + b.Property("Gender") + .HasColumnType("longtext"); + + b.Property("IsLocationCheckIn") + .HasColumnType("tinyint(1)") + .HasComment("true คือ ณ สถานที่ตั้ง, false คือ นอกสถานที่ตั้ง Check-In"); + + b.Property("IsLocationCheckOut") + .HasColumnType("tinyint(1)") + .HasComment("true คือ ณ สถานที่ตั้ง, false คือ นอกสถานที่ตั้ง Check-Out"); + + b.Property("IsProcess") + .HasColumnType("tinyint(1)") + .HasComment("นำไปประมวลผลแล้วหรือยัง"); + + b.Property("KeycloakUserId") + .HasColumnType("char(36)") + .HasComment("รหัส User ของ Keycloak"); + + b.Property("LastName") + .HasColumnType("longtext"); + + b.Property("LastUpdateFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(105) + .HasComment("ชื่อ User ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdateUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(103) + .HasComment("User Id ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(102) + .HasComment("แก้ไขข้อมูลล่าสุดเมื่อ"); + + b.Property("Prefix") + .HasColumnType("longtext"); + + b.Property("ProfileId") + .HasColumnType("char(36)"); + + b.Property("ProfileType") + .HasColumnType("longtext"); + + b.Property("Root") + .HasColumnType("longtext"); + + b.Property("RootDnaId") + .HasColumnType("char(36)"); + + b.Property("RootId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.ToTable("UserTimeStamps"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.Requests.LeaveBeginning", b => + { + b.HasOne("BMA.EHR.Domain.Models.Leave.Commons.LeaveType", "LeaveType") + .WithMany() + .HasForeignKey("LeaveTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("LeaveType"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.Requests.LeaveDocument", b => + { + b.HasOne("BMA.EHR.Domain.Models.Documents.Document", "Document") + .WithMany() + .HasForeignKey("DocumentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("BMA.EHR.Domain.Models.Leave.Requests.LeaveRequest", "LeaveRequest") + .WithMany("LeaveDocument") + .HasForeignKey("LeaveRequestId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Document"); + + b.Navigation("LeaveRequest"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.Requests.LeaveRequest", b => + { + b.HasOne("BMA.EHR.Domain.Models.Documents.Document", "LeaveCancelDocument") + .WithMany() + .HasForeignKey("LeaveCancelDocumentId"); + + b.HasOne("BMA.EHR.Domain.Models.Documents.Document", "LeaveDraftDocument") + .WithMany() + .HasForeignKey("LeaveDraftDocumentId"); + + b.HasOne("BMA.EHR.Domain.Models.Leave.Commons.LeaveType", "Type") + .WithMany() + .HasForeignKey("TypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("LeaveCancelDocument"); + + b.Navigation("LeaveDraftDocument"); + + b.Navigation("Type"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.Requests.LeaveRequestApprover", b => + { + b.HasOne("BMA.EHR.Domain.Models.Leave.Requests.LeaveRequest", "LeaveRequest") + .WithMany("Approvers") + .HasForeignKey("LeaveRequestId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("LeaveRequest"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.TimeAttendants.UserDutyTime", b => + { + b.HasOne("BMA.EHR.Domain.Models.Leave.TimeAttendants.DutyTime", "DutyTime") + .WithMany() + .HasForeignKey("DutyTimeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("DutyTime"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.Requests.LeaveRequest", b => + { + b.Navigation("Approvers"); + + b.Navigation("LeaveDocument"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/BMA.EHR.Infrastructure/Migrations/LeaveDb/20260120032158_Add RMQ Task Control.cs b/BMA.EHR.Infrastructure/Migrations/LeaveDb/20260120032158_Add RMQ Task Control.cs new file mode 100644 index 00000000..f8d2090b --- /dev/null +++ b/BMA.EHR.Infrastructure/Migrations/LeaveDb/20260120032158_Add RMQ Task Control.cs @@ -0,0 +1,58 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace BMA.EHR.Infrastructure.Migrations.LeaveDb +{ + /// + public partial class AddRMQTaskControl : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "CheckInJobStatuses", + columns: table => new + { + Id = table.Column(type: "char(36)", nullable: false, comment: "PrimaryKey", collation: "ascii_general_ci"), + CreatedAt = table.Column(type: "datetime(6)", nullable: false, comment: "สร้างข้อมูลเมื่อ"), + CreatedUserId = table.Column(type: "varchar(40)", maxLength: 40, nullable: false, comment: "User Id ที่สร้างข้อมูล") + .Annotation("MySql:CharSet", "utf8mb4"), + LastUpdatedAt = table.Column(type: "datetime(6)", nullable: true, comment: "แก้ไขข้อมูลล่าสุดเมื่อ"), + LastUpdateUserId = table.Column(type: "varchar(40)", maxLength: 40, nullable: false, comment: "User Id ที่แก้ไขข้อมูลล่าสุด") + .Annotation("MySql:CharSet", "utf8mb4"), + CreatedFullName = table.Column(type: "varchar(200)", maxLength: 200, nullable: false, comment: "ชื่อ User ที่สร้างข้อมูล") + .Annotation("MySql:CharSet", "utf8mb4"), + LastUpdateFullName = table.Column(type: "varchar(200)", maxLength: 200, nullable: false, comment: "ชื่อ User ที่แก้ไขข้อมูลล่าสุด") + .Annotation("MySql:CharSet", "utf8mb4"), + TaskId = table.Column(type: "char(36)", nullable: false, comment: "Task ID สำหรับติดตามสถานะงาน", collation: "ascii_general_ci"), + KeycloakUserId = table.Column(type: "char(36)", nullable: false, comment: "รหัส User ของ Keycloak", collation: "ascii_general_ci"), + CreatedDate = table.Column(type: "datetime(6)", nullable: false, comment: "วันเวลาที่สร้างงาน"), + ProcessingDate = table.Column(type: "datetime(6)", nullable: true, comment: "วันเวลาที่เริ่มประมวลผล"), + CompletedDate = table.Column(type: "datetime(6)", nullable: true, comment: "วันเวลาที่เสร็จสิ้นการประมวลผล"), + Status = table.Column(type: "longtext", nullable: false, comment: "สถานะงาน: PENDING, PROCESSING, COMPLETED, FAILED") + .Annotation("MySql:CharSet", "utf8mb4"), + CheckType = table.Column(type: "longtext", nullable: true, comment: "ประเภทการลงเวลา: CHECK_IN, CHECK_OUT") + .Annotation("MySql:CharSet", "utf8mb4"), + CheckInId = table.Column(type: "char(36)", nullable: true, comment: "CheckInId สำหรับ Check-Out", collation: "ascii_general_ci"), + ErrorMessage = table.Column(type: "longtext", nullable: true, comment: "ข้อความแสดงข้อผิดพลาด") + .Annotation("MySql:CharSet", "utf8mb4"), + AdditionalData = table.Column(type: "longtext", nullable: true, comment: "ข้อมูลเพิ่มเติม (JSON)") + .Annotation("MySql:CharSet", "utf8mb4") + }, + constraints: table => + { + table.PrimaryKey("PK_CheckInJobStatuses", x => x.Id); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "CheckInJobStatuses"); + } + } +} diff --git a/BMA.EHR.Infrastructure/Migrations/LeaveDb/LeaveDbContextModelSnapshot.cs b/BMA.EHR.Infrastructure/Migrations/LeaveDb/LeaveDbContextModelSnapshot.cs index 14e45995..d12bf747 100644 --- a/BMA.EHR.Infrastructure/Migrations/LeaveDb/LeaveDbContextModelSnapshot.cs +++ b/BMA.EHR.Infrastructure/Migrations/LeaveDb/LeaveDbContextModelSnapshot.cs @@ -882,6 +882,99 @@ namespace BMA.EHR.Infrastructure.Migrations.LeaveDb b.ToTable("AdditionalCheckRequests"); }); + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.TimeAttendants.CheckInJobStatus", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnOrder(0) + .HasComment("PrimaryKey") + .HasAnnotation("Relational:JsonPropertyName", "id"); + + b.Property("AdditionalData") + .HasColumnType("longtext") + .HasComment("ข้อมูลเพิ่มเติม (JSON)"); + + b.Property("CheckInId") + .HasColumnType("char(36)") + .HasComment("CheckInId สำหรับ Check-Out"); + + b.Property("CheckType") + .HasColumnType("longtext") + .HasComment("ประเภทการลงเวลา: CHECK_IN, CHECK_OUT"); + + b.Property("CompletedDate") + .HasColumnType("datetime(6)") + .HasComment("วันเวลาที่เสร็จสิ้นการประมวลผล"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(100) + .HasComment("สร้างข้อมูลเมื่อ"); + + b.Property("CreatedDate") + .HasColumnType("datetime(6)") + .HasComment("วันเวลาที่สร้างงาน"); + + b.Property("CreatedFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(104) + .HasComment("ชื่อ User ที่สร้างข้อมูล"); + + b.Property("CreatedUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(101) + .HasComment("User Id ที่สร้างข้อมูล"); + + b.Property("ErrorMessage") + .HasColumnType("longtext") + .HasComment("ข้อความแสดงข้อผิดพลาด"); + + b.Property("KeycloakUserId") + .HasColumnType("char(36)") + .HasComment("รหัส User ของ Keycloak"); + + b.Property("LastUpdateFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(105) + .HasComment("ชื่อ User ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdateUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(103) + .HasComment("User Id ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(102) + .HasComment("แก้ไขข้อมูลล่าสุดเมื่อ"); + + b.Property("ProcessingDate") + .HasColumnType("datetime(6)") + .HasComment("วันเวลาที่เริ่มประมวลผล"); + + b.Property("Status") + .IsRequired() + .HasColumnType("longtext") + .HasComment("สถานะงาน: PENDING, PROCESSING, COMPLETED, FAILED"); + + b.Property("TaskId") + .HasColumnType("char(36)") + .HasComment("Task ID สำหรับติดตามสถานะงาน"); + + b.HasKey("Id"); + + b.ToTable("CheckInJobStatuses"); + }); + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.TimeAttendants.DutyTime", b => { b.Property("Id") diff --git a/BMA.EHR.Infrastructure/Persistence/LeaveDbContext.cs b/BMA.EHR.Infrastructure/Persistence/LeaveDbContext.cs index c6d37b7a..19848c6b 100644 --- a/BMA.EHR.Infrastructure/Persistence/LeaveDbContext.cs +++ b/BMA.EHR.Infrastructure/Persistence/LeaveDbContext.cs @@ -22,6 +22,8 @@ namespace BMA.EHR.Infrastructure.Persistence public DbSet UserCalendars { get; set; } + public DbSet CheckInJobStatuses { get; set; } + #endregion #region " Leave System " diff --git a/BMA.EHR.Leave/Controllers/LeaveController.cs b/BMA.EHR.Leave/Controllers/LeaveController.cs index aa89fc6a..b2544898 100644 --- a/BMA.EHR.Leave/Controllers/LeaveController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveController.cs @@ -58,6 +58,7 @@ namespace BMA.EHR.Leave.Service.Controllers private readonly LeaveRequestRepository _leaveRequestRepository; private readonly UserCalendarRepository _userCalendarRepository; private readonly PermissionRepository _permission; + private readonly CheckInJobStatusRepository _checkInJobStatusRepository; private readonly CommandRepository _commandRepository; @@ -92,6 +93,7 @@ namespace BMA.EHR.Leave.Service.Controllers ObjectPool objectPool, PermissionRepository permission, NotificationRepository notificationRepository, + CheckInJobStatusRepository checkInJobStatusRepository, HttpClient httpClient) { _dutyTimeRepository = dutyTimeRepository; @@ -109,6 +111,7 @@ namespace BMA.EHR.Leave.Service.Controllers _commandRepository = commandRepository; _leaveRequestRepository = leaveRequestRepository; _notificationRepository = notificationRepository; + _checkInJobStatusRepository = checkInJobStatusRepository; _objectPool = objectPool; _permission = permission; @@ -540,11 +543,15 @@ namespace BMA.EHR.Leave.Service.Controllers } } + // add task id for check in queue + string taskId = Guid.NewGuid().ToString(); + var checkData = new CheckTimeDtoRB { UserId = userId, CurrentDate = currentDate, CheckInId = data.CheckInId, + TaskId = Guid.Parse(taskId), Lat = data.Lat, Lon = data.Lon, POI = data.POI, @@ -564,11 +571,27 @@ namespace BMA.EHR.Leave.Service.Controllers var serializedObject = JsonConvert.SerializeObject(checkData); var body = Encoding.UTF8.GetBytes(serializedObject); - // add task id for check in queue - string taskId = Guid.NewGuid().ToString(); var properties = channel.CreateBasicProperties(); properties.Persistent = true; - properties.MessageId = userId.ToString("D");// ระบบลงเวลาต้องมีการเช็คสถานะใน rabbitMQ ด้วยว่ามีการรอรันอยู่ไหม ลงเวลาเข้า/ออกงาน #894 + properties.MessageId = taskId; + + // บันทึกสถานะงานก่อนส่งไป RabbitMQ + var jobStatus = new CheckInJobStatus + { + TaskId = Guid.Parse(taskId), + KeycloakUserId = userId, + CreatedDate = currentDate, + Status = "PENDING", + CheckType = data.CheckInId == null ? "CHECK_IN" : "CHECK_OUT", + CheckInId = data.CheckInId, + AdditionalData = JsonConvert.SerializeObject(new + { + IsLocation = data.IsLocation, + LocationName = data.LocationName, + POI = data.POI + }) + }; + await _checkInJobStatusRepository.AddAsync(jobStatus); channel.BasicPublish(exchange: "", routingKey: queue, @@ -583,6 +606,78 @@ namespace BMA.EHR.Leave.Service.Controllers } } + /// + /// ตรวจสอบสถานะงาน check-in ด้วย Task ID + /// + /// Task ID ที่ได้จากการเรียก CheckInAsync + /// + /// + /// เมื่อทำรายการสำเร็จ + /// ไม่ได้ Login เข้าระบบ + /// ไม่พบข้อมูลงาน + /// เมื่อเกิดข้อผิดพลาดในการทำงาน + [HttpGet("job-status/{taskId:guid}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status500InternalServerError)] + public async Task> GetJobStatusAsync(Guid taskId) + { + var jobStatus = await _checkInJobStatusRepository.GetByTaskIdAsync(taskId); + + if (jobStatus == null) + { + return Error("ไม่พบข้อมูลงาน", StatusCodes.Status404NotFound); + } + + var result = new + { + taskId = jobStatus.TaskId, + keycloakUserId = jobStatus.KeycloakUserId, + status = jobStatus.Status, + checkType = jobStatus.CheckType, + checkInId = jobStatus.CheckInId, + createdDate = jobStatus.CreatedDate, + processingDate = jobStatus.ProcessingDate, + completedDate = jobStatus.CompletedDate, + errorMessage = jobStatus.ErrorMessage, + additionalData = jobStatus.AdditionalData != null ? + JsonConvert.DeserializeObject(jobStatus.AdditionalData) : null + }; + + return Success(result); + } + + /// + /// ดึงรายการงานที่ยัง pending หรือ processing ของผู้ใช้ + /// + /// + /// + /// เมื่อทำรายการสำเร็จ + /// ไม่ได้ Login เข้าระบบ + /// เมื่อเกิดข้อผิดพลาดในการทำงาน + [HttpGet("pending-jobs")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status500InternalServerError)] + public async Task> GetPendingJobsAsync() + { + var userId = UserId == null ? Guid.Empty : Guid.Parse(UserId); + var jobs = await _checkInJobStatusRepository.GetPendingOrProcessingJobsAsync(userId); + + var result = jobs.Select(job => new + { + taskId = job.TaskId, + status = job.Status, + checkType = job.CheckType, + checkInId = job.CheckInId, + createdDate = job.CreatedDate, + processingDate = job.ProcessingDate + }).ToList(); + + return Success(new { count = result.Count, jobs = result }); + } + [HttpGet("check-status")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] @@ -590,61 +685,62 @@ namespace BMA.EHR.Leave.Service.Controllers public async Task> CheckInCheckStatus() { var userId = UserId == null ? Guid.Empty : Guid.Parse(UserId); - var currentDate = DateTime.Now; - var channel = _objectPool.Get(); + // var currentDate = DateTime.Now; + // var channel = _objectPool.Get(); try { - var _url = _configuration["Rabbit:URL"] ?? ""; - var _queue = _configuration["Rabbit:Queue"] ?? "basic-queue"; + // var _url = _configuration["Rabbit:URL"] ?? ""; + // var _queue = _configuration["Rabbit:Queue"] ?? "basic-queue"; - // Step 1: ตรวจสอบจำนวน message ทั้งหมดในคิว - string queueUrl = $"{_url}{_queue}"; - var queueResponse = await _httpClient.GetAsync(queueUrl); - if (!queueResponse.IsSuccessStatusCode) - { - return Error("Error accessing RabbitMQ API", (int)queueResponse.StatusCode); - } + // // Step 1: ตรวจสอบจำนวน message ทั้งหมดในคิว + // string queueUrl = $"{_url}{_queue}"; + // var queueResponse = await _httpClient.GetAsync(queueUrl); + // if (!queueResponse.IsSuccessStatusCode) + // { + // return Error("Error accessing RabbitMQ API", (int)queueResponse.StatusCode); + // } - var queueContent = await queueResponse.Content.ReadAsStringAsync(); - var queueData = JObject.Parse(queueContent); - int totalMessages = queueData["messages"]?.Value() ?? 0; + // var queueContent = await queueResponse.Content.ReadAsStringAsync(); + // var queueData = JObject.Parse(queueContent); + // int totalMessages = queueData["messages"]?.Value() ?? 0; - // Step 2: วนลูปดึง message ทีละ 100 งาน - int batchSize = 100; - var allMessages = new List(); - int processedMessages = 0; + // // Step 2: วนลูปดึง message ทีละ 100 งาน + // int batchSize = 100; + // var allMessages = new List(); + // int processedMessages = 0; - while (processedMessages < totalMessages) - { - var requestBody = new StringContent( - $"{{\"count\":{batchSize},\"requeue\":true,\"encoding\":\"auto\",\"ackmode\":\"ack_requeue_true\"}}", - Encoding.UTF8, - "application/json" - ); + // while (processedMessages < totalMessages) + // { + // var requestBody = new StringContent( + // $"{{\"count\":{batchSize},\"requeue\":true,\"encoding\":\"auto\",\"ackmode\":\"ack_requeue_true\"}}", + // Encoding.UTF8, + // "application/json" + // ); - string getMessagesUrl = $"{_url}{_queue}/get"; - var response = await _httpClient.PostAsync(getMessagesUrl, requestBody); - if (!response.IsSuccessStatusCode) - { - return StatusCode((int)response.StatusCode, "Error retrieving messages from RabbitMQ."); - } + // string getMessagesUrl = $"{_url}{_queue}/get"; + // var response = await _httpClient.PostAsync(getMessagesUrl, requestBody); + // if (!response.IsSuccessStatusCode) + // { + // return StatusCode((int)response.StatusCode, "Error retrieving messages from RabbitMQ."); + // } - var content = await response.Content.ReadAsStringAsync(); - var messages = JArray.Parse(content); + // var content = await response.Content.ReadAsStringAsync(); + // var messages = JArray.Parse(content); - if (messages.Count == 0) - { - break; - } + // if (messages.Count == 0) + // { + // break; + // } - processedMessages += messages.Count; - allMessages.AddRange(messages.Select(m => m["properties"].ToString())); - } + // processedMessages += messages.Count; + // allMessages.AddRange(messages.Select(m => m["properties"].ToString())); + // } + var jobs = await _checkInJobStatusRepository.GetPendingOrProcessingJobsAsync(userId); // Step 3: ค้นหา taskIds ที่อยู่ใน messages ทั้งหมด - var foundTasks = allMessages.FirstOrDefault(x => x.Contains(userId.ToString("D"))); + //var foundTasks = allMessages.FirstOrDefault(x => x.Contains(userId.ToString("D"))); - return Success(new { keycloakId = userId, InQueue = foundTasks != null }); + return Success(new { keycloakId = userId, InQueue = (jobs != null && jobs.Count > 0) }); } catch (Exception ex) @@ -653,7 +749,7 @@ namespace BMA.EHR.Leave.Service.Controllers } finally { - _objectPool.Return(channel); + //_objectPool.Return(channel); } } @@ -790,21 +886,31 @@ namespace BMA.EHR.Leave.Service.Controllers public async Task> ProcessCheckInAsync([FromBody] CheckTimeDtoRB data) { var userId = data.UserId ?? Guid.Empty; - var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(userId, data.Token); + var taskId = data.TaskId ?? Guid.Empty; - if (profile == null) - return Error(GlobalMessages.DataNotFound, StatusCodes.Status404NotFound); - - if (data.CheckInFileName == "no-file") throw new Exception(GlobalMessages.NoFileToUpload); - var currentDate = data.CurrentDate ?? DateTime.Now; - - var check_status = data.CheckInId == null ? "check-in-picture" : "check-out-picture"; - - var fileName = $"{_bucketName}/{userId}/{currentDate.ToString("dd-MM-yyyy")}/{check_status}/{data.CheckInFileName}"; - using (var ms = new MemoryStream(data.CheckInFileBytes ?? new byte[0])) + try { - await _minIOService.UploadFileAsync(fileName, ms); - } + // อัปเดตสถานะเป็น PROCESSING + if (taskId != Guid.Empty) + { + await _checkInJobStatusRepository.UpdateToProcessingAsync(taskId); + } + + var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(userId, data.Token); + + if (profile == null) + return Error(GlobalMessages.DataNotFound, StatusCodes.Status404NotFound); + + if (data.CheckInFileName == "no-file") throw new Exception(GlobalMessages.NoFileToUpload); + var currentDate = data.CurrentDate ?? DateTime.Now; + + var check_status = data.CheckInId == null ? "check-in-picture" : "check-out-picture"; + + var fileName = $"{_bucketName}/{userId}/{currentDate.ToString("dd-MM-yyyy")}/{check_status}/{data.CheckInFileName}"; + using (var ms = new MemoryStream(data.CheckInFileBytes ?? new byte[0])) + { + await _minIOService.UploadFileAsync(fileName, ms); + } var defaultRound = await _dutyTimeRepository.GetDefaultAsync(); if (defaultRound == null) @@ -1058,9 +1164,32 @@ namespace BMA.EHR.Leave.Service.Controllers } } + + // อัปเดตสถานะเป็น COMPLETED + if (taskId != Guid.Empty) + { + var additionalData = JsonConvert.SerializeObject(new + { + CheckInType = data.CheckInId == null ? "check-in" : "check-out", + FileName = fileName, + ProcessedDate = currentDate + }); + await _checkInJobStatusRepository.UpdateToCompletedAsync(taskId, additionalData); + } + var checkInType = data.CheckInId == null ? "check-in" : "check-out"; return Success(new { user = $"{profile.FirstName} {profile.LastName}", date = currentDate, type = checkInType }); ; } + catch (Exception ex) + { + // อัปเดตสถานะเป็น FAILED + if (taskId != Guid.Empty) + { + await _checkInJobStatusRepository.UpdateToFailedAsync(taskId, ex.Message); + } + throw; + } + } /// /// LV1_005 - ลงเวลาเข้า-ออกงาน (USER) diff --git a/BMA.EHR.Leave/DTOs/CheckIn/CheckTimeDto.cs b/BMA.EHR.Leave/DTOs/CheckIn/CheckTimeDto.cs index 3a738170..0d49c24f 100644 --- a/BMA.EHR.Leave/DTOs/CheckIn/CheckTimeDto.cs +++ b/BMA.EHR.Leave/DTOs/CheckIn/CheckTimeDto.cs @@ -54,6 +54,7 @@ namespace BMA.EHR.Leave.Service.DTOs.CheckIn public Guid? CheckInId { get; set; } + public Guid? TaskId { get; set; } public double Lat { get; set; } = 0; diff --git a/BMA.EHR.Leave/appsettings.json b/BMA.EHR.Leave/appsettings.json index e1bb5d10..d1f58a39 100644 --- a/BMA.EHR.Leave/appsettings.json +++ b/BMA.EHR.Leave/appsettings.json @@ -53,7 +53,7 @@ "Host": "192.168.1.63", "User": "admin", "Password": "12345678", - "Queue": "hrms-checkin-queue", + "Queue": "hrms-checkin-queue-dev", "URL": "http://192.168.1.63:9122/api/queues/%2F/" }, "Mail": {