diff --git a/.gitignore b/.gitignore
index 7391825f..f60d8ec1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -373,4 +373,10 @@ MigrationBackup/
.ionide/
# Fody - auto-generated XML schema
-FodyWeavers.xsd
\ No newline at end of file
+FodyWeavers.xsd
+
+# VS Code C# Dev Kit cache
+*.lscache
+
+# Claude Code
+.claude/
\ No newline at end of file
diff --git a/BMA.EHR.Application/Repositories/Leaves/LeaveRequests/LeaveBeginingRepository.cs b/BMA.EHR.Application/Repositories/Leaves/LeaveRequests/LeaveBeginingRepository.cs
index 2d14db9c..add1361b 100644
--- a/BMA.EHR.Application/Repositories/Leaves/LeaveRequests/LeaveBeginingRepository.cs
+++ b/BMA.EHR.Application/Repositories/Leaves/LeaveRequests/LeaveBeginingRepository.cs
@@ -9,6 +9,7 @@ using BMA.EHR.Domain.Shared;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
+using System.Collections.Concurrent;
namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests
{
@@ -23,6 +24,12 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests
private readonly IConfiguration _configuration;
private readonly EmailSenderService _emailSenderService;
+ ///
+ /// Keyed locks to serialize get-or-create for LeaveBeginning rows by (ProfileId, LeaveYear, LeaveTypeId).
+ /// Prevents duplicate inserts when concurrent requests (e.g. UI calling /user/check twice) hit the same key.
+ ///
+ private static readonly ConcurrentDictionary _getOrAddLocks = new();
+
#endregion
#region " Constructor and Destuctor "
@@ -80,7 +87,7 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests
public async Task UpdateLeaveUsageAsync(int year, Guid typeId, Guid userId, double day)
{
// var pf = await _userProfileRepository.GetProfileByKeycloakIdAsync(userId, AccessToken);
- var pf = await _userProfileRepository.GetProfileByKeycloakIdNewAsync(userId, AccessToken);
+ var pf = await _userProfileRepository.GetProfileByKeycloakIdNew2Async(userId, AccessToken);
if (pf == null)
{
throw new Exception(GlobalMessages.DataNotFound);
@@ -102,7 +109,7 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests
public async Task UpdateLeaveCountAsync(int year, Guid typeId, Guid userId, int count)
{
// var pf = await _userProfileRepository.GetProfileByKeycloakIdAsync(userId, AccessToken);
- var pf = await _userProfileRepository.GetProfileByKeycloakIdNewAsync(userId, AccessToken);
+ var pf = await _userProfileRepository.GetProfileByKeycloakIdNew2Async(userId, AccessToken);
if (pf == null)
{
throw new Exception(GlobalMessages.DataNotFound);
@@ -121,10 +128,34 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests
await _dbContext.SaveChangesAsync();
}
+ public async Task ProcessEarlyLeaveRequest(int year)
+ {
+ // Get Early Leave Request (กรองตามปีงบประมาณ: 1 ต.ค. (year-1) – 30 ก.ย. (year))
+ var fiscalStart = new DateTime(year - 1, 10, 1);
+ var fiscalEnd = new DateTime(year, 9, 30);
+
+ var leaveReq = await _dbContext.Set()
+ .Include(x => x.Type)
+ .Where(x => x.LeaveStatus == "APPROVE")
+ .Where(x => x.LeaveStartDate.Date <= fiscalEnd && x.LeaveEndDate.Date >= fiscalStart)
+ .ToListAsync();
+
+ foreach (var leave in leaveReq)
+ {
+ await GetByYearAndTypeIdForUserWithUpdateAsync(year, leave.Type.Id, leave.KeycloakUserId);
+ }
+ }
+
+ public async Task ProcessEarlyLeaveRequestSchedule()
+ {
+ int year = DateTime.Now.Year;
+ await ProcessEarlyLeaveRequest(year);
+ }
+
public async Task GetByYearAndTypeIdForUserAsync(int year, Guid typeId, Guid userId)
{
// var pf = await _userProfileRepository.GetProfileByKeycloakIdAsync(userId, AccessToken);
- var pf = await _userProfileRepository.GetProfileByKeycloakIdNewAsync(userId, AccessToken);
+ var pf = await _userProfileRepository.GetProfileByKeycloakIdNew2Async(userId, AccessToken);
if (pf == null)
{
throw new Exception(GlobalMessages.DataNotFound);
@@ -134,22 +165,22 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests
var leaveType = await _dbContext.Set().FirstOrDefaultAsync(x => x.Id == typeId);
- var data = await _dbContext.Set()
- .Include(x => x.LeaveType)
- .FirstOrDefaultAsync(x => x.LeaveYear == year && x.LeaveTypeId == typeId && x.ProfileId == pf.Id);
-
- if (data == null)
+ LeaveBeginning Factory()
{
var limit = 0.0;
- var prev = await _dbContext.Set()
+ var prev = _dbContext.Set()
.Include(x => x.LeaveType)
- .FirstOrDefaultAsync(x => x.LeaveYear == year - 1 && x.LeaveTypeId == typeId && x.ProfileId == pf.Id);
+ .FirstOrDefault(x => x.LeaveYear == year - 1 && x.LeaveTypeId == typeId && x.ProfileId == pf.Id);
+
+ // คำนวณปีงบประมาณจาก startDate (ปีงบประมาณเริ่ม 1 ต.ค. และสิ้นสุด 30 ก.ย.)
+ var isCurrentYear = DateTime.Now.Year == year;
+
var prevRemain = 0.0;
if (prev != null)
{
- prevRemain = prev.LeaveDays - prev.LeaveDaysUsed;
+ prevRemain = isCurrentYear ? prev.LeaveDays - (prev.LeaveDaysUsed ?? 0.0) : 0.0;
}
if (govAge >= 180)
@@ -170,7 +201,7 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests
limit = 0.0;
}
- data = new LeaveBeginning
+ return new LeaveBeginning
{
LeaveYear = year,
LeaveTypeId = typeId,
@@ -186,36 +217,110 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests
Child3DnaId = pf.Child3DnaId,
Child4DnaId = pf.Child4DnaId
};
+ }
- _dbContext.Set().Add(data);
+ return await GetOrAddForUserAsync(year, typeId, pf.Id, Factory);
+ }
+
+ public async Task GetByYearAndTypeIdForUserWithUpdateAsync(int year, Guid typeId, Guid userId)
+ {
+ // var pf = await _userProfileRepository.GetProfileByKeycloakIdAsync(userId, AccessToken);
+ var pf = await _userProfileRepository.GetProfileByKeycloakIdNew2Async(userId, AccessToken);
+ if (pf == null)
+ {
+ throw new Exception(GlobalMessages.DataNotFound);
+ }
+
+ var govAge = (pf?.DateStart?.Date ?? DateTime.Now.Date).DiffDay(DateTime.Now.Date);
+
+ var leaveType = await _dbContext.Set().FirstOrDefaultAsync(x => x.Id == typeId);
+
+
+ var limit = 0.0;
+
+ var prev = _dbContext.Set()
+ .Include(x => x.LeaveType)
+ .FirstOrDefault(x => x.LeaveYear == year - 1 && x.LeaveTypeId == typeId && x.ProfileId == pf.Id);
+
+ var prevRemain = 0.0;
+
+
+
+ if (prev != null)
+ {
+ prevRemain = prev.LeaveDays - (prev.LeaveDaysUsed ?? 0.0);
+ }
+
+ if (govAge >= 180)
+ {
+ if (govAge >= 3650)
+ {
+ limit = 10 + prevRemain;
+ if (limit > 30) limit = 30;
+ }
+ else
+ {
+ limit = 10 + prevRemain;
+ if (limit > 20) limit = 20;
+ }
+ }
+ else
+ {
+ limit = 0.0;
+ }
+
+ var data = await _dbContext.Set()
+ .Where(x => x.LeaveYear == year && x.LeaveTypeId == typeId && x.ProfileId == pf.Id)
+ .FirstOrDefaultAsync();
+
+ if (data != null)
+ {
+ data.LeaveDays = leaveType?.Code == "LV-005" ? limit : 0;
await _dbContext.SaveChangesAsync();
}
+ // return new LeaveBeginning
+ // {
+ // LeaveYear = year,
+ // LeaveTypeId = typeId,
+ // ProfileId = pf.Id,
+ // Prefix = pf.Prefix,
+ // FirstName = pf.FirstName,
+ // LastName = pf.LastName,
+ // LeaveDaysUsed = 0,
+ // LeaveDays = leaveType?.Code == "LV-005" ? limit : 0,
+ // RootDnaId = pf.RootDnaId,
+ // Child1DnaId = pf.Child1DnaId,
+ // Child2DnaId = pf.Child2DnaId,
+ // Child3DnaId = pf.Child3DnaId,
+ // Child4DnaId = pf.Child4DnaId
+ // };
return data;
- }
+ }
+
+
public async Task GetByYearAndTypeIdForUser(int year, Guid typeId, GetProfileByKeycloakIdDto? pf)
{
var govAge = (pf?.DateStart?.Date ?? DateTime.Now.Date).DiffDay(DateTime.Now.Date);
var leaveType = await _dbContext.Set().FirstOrDefaultAsync(x => x.Id == typeId);
- var data = await _dbContext.Set()
- .Include(x => x.LeaveType)
- .FirstOrDefaultAsync(x => x.LeaveYear == year && x.LeaveTypeId == typeId && x.ProfileId == pf.Id);
-
- if (data == null)
+ LeaveBeginning Factory()
{
var limit = 0.0;
- var prev = await _dbContext.Set()
+ var prev = _dbContext.Set()
.Include(x => x.LeaveType)
- .FirstOrDefaultAsync(x => x.LeaveYear == year - 1 && x.LeaveTypeId == typeId && x.ProfileId == pf.Id);
+ .FirstOrDefault(x => x.LeaveYear == year - 1 && x.LeaveTypeId == typeId && x.ProfileId == pf.Id);
+
+ // คำนวณปีงบประมาณจาก startDate (ปีงบประมาณเริ่ม 1 ต.ค. และสิ้นสุด 30 ก.ย.)
+ var isCurrentYear = DateTime.Now.Year == year;
var prevRemain = 0.0;
if (prev != null)
{
- prevRemain = prev.LeaveDays - prev.LeaveDaysUsed;
+ prevRemain = isCurrentYear ? prev.LeaveDays - (prev.LeaveDaysUsed ?? 0.0) : 0.0;
}
if (govAge >= 180)
@@ -236,7 +341,7 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests
limit = 0.0;
}
- data = new LeaveBeginning
+ return new LeaveBeginning
{
LeaveYear = year,
LeaveTypeId = typeId,
@@ -252,18 +357,15 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests
Child3DnaId = pf.Child3DnaId,
Child4DnaId = pf.Child4DnaId
};
-
- _dbContext.Set().Add(data);
- await _dbContext.SaveChangesAsync();
}
- return data;
+ return await GetOrAddForUserAsync(year, typeId, pf.Id, Factory);
}
public async Task GetByYearAndTypeIdForUser2Async(int year, Guid typeId, Guid userId)
{
// var pf = await _userProfileRepository.GetProfileByKeycloakIdAsync(userId, AccessToken);
- var pf = await _userProfileRepository.GetProfileByKeycloakIdNewAsync(userId, AccessToken);
+ var pf = await _userProfileRepository.GetProfileByKeycloakIdNew2Async(userId, AccessToken);
if (pf == null)
{
return null;
@@ -273,22 +375,21 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests
var leaveType = await _dbContext.Set().FirstOrDefaultAsync(x => x.Id == typeId);
- var data = await _dbContext.Set()
- .Include(x => x.LeaveType)
- .FirstOrDefaultAsync(x => x.LeaveYear == year && x.LeaveTypeId == typeId && x.ProfileId == pf.Id);
-
- if (data == null)
+ LeaveBeginning Factory()
{
var limit = 0.0;
- var prev = await _dbContext.Set()
+ var prev = _dbContext.Set()
.Include(x => x.LeaveType)
- .FirstOrDefaultAsync(x => x.LeaveYear == year - 1 && x.LeaveTypeId == typeId && x.ProfileId == pf.Id);
+ .FirstOrDefault(x => x.LeaveYear == year - 1 && x.LeaveTypeId == typeId && x.ProfileId == pf.Id);
+
+ // คำนวณปีงบประมาณจาก startDate (ปีงบประมาณเริ่ม 1 ต.ค. และสิ้นสุด 30 ก.ย.)
+ var isCurrentYear = DateTime.Now.Year == year;
var prevRemain = 0.0;
if (prev != null)
{
- prevRemain = prev.LeaveDays - prev.LeaveDaysUsed;
+ prevRemain = isCurrentYear ? prev.LeaveDays - (prev.LeaveDaysUsed ?? 0.0) : 0.0;
}
if (govAge >= 180)
@@ -309,7 +410,7 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests
limit = 0.0;
}
- data = new LeaveBeginning
+ return new LeaveBeginning
{
LeaveYear = year,
LeaveTypeId = typeId,
@@ -325,17 +426,59 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests
Child3DnaId = pf.Child3DnaId,
Child4DnaId = pf.Child4DnaId
};
-
- _dbContext.Set().Add(data);
- await _dbContext.SaveChangesAsync();
}
- return data;
+ return await GetOrAddForUserAsync(year, typeId, pf.Id, Factory);
+ }
+
+ ///
+ /// Get-or-create a LeaveBeginning row for (ProfileId, LeaveYear, LeaveTypeId) with concurrency protection.
+ /// Uses a keyed SemaphoreSlim to serialize within-process requests, and re-queries after acquiring the lock.
+ /// If a cross-process insert wins (unique index violation), the duplicate key exception is caught and the row
+ /// created by the winner is returned.
+ ///
+ private async Task GetOrAddForUserAsync(int year, Guid typeId, Guid profileId, Func factory)
+ {
+ var key = $"{profileId}_{year}_{typeId}";
+ var semaphore = _getOrAddLocks.GetOrAdd(key, _ => new SemaphoreSlim(1, 1));
+ await semaphore.WaitAsync();
+ try
+ {
+ // Re-query inside the lock — another thread may have created it while we waited.
+ var existing = await _dbContext.Set()
+ .Include(x => x.LeaveType)
+ .FirstOrDefaultAsync(x => x.LeaveYear == year && x.LeaveTypeId == typeId && x.ProfileId == profileId);
+ if (existing != null)
+ {
+ return existing;
+ }
+
+ var entity = factory();
+ _dbContext.Set().Add(entity);
+ try
+ {
+ await _dbContext.SaveChangesAsync();
+ return entity;
+ }
+ catch (DbUpdateException)
+ {
+ // Cross-process/cross-server race hit the unique index (IX_LeaveBeginnings_ProfileId_LeaveYear_LeaveTypeId).
+ // Detach the failed insert and return the row created by the winner.
+ _dbContext.Detach(entity);
+ var winner = await _dbContext.Set()
+ .Include(x => x.LeaveType)
+ .FirstOrDefaultAsync(x => x.LeaveYear == year && x.LeaveTypeId == typeId && x.ProfileId == profileId);
+ return winner;
+ }
+ }
+ finally
+ {
+ semaphore.Release();
+ }
}
public async Task> GetAllByYearAndTypeAsync(int year, Guid typeId, List userIdList)
{
-
var updateList = new List();
var result = new List();
@@ -376,7 +519,7 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests
var prevRemain = 0.0;
if (prev != null)
{
- prevRemain = prev.LeaveDays - prev.LeaveDaysUsed;
+ prevRemain = prev.LeaveDays - (prev.LeaveDaysUsed ?? 0.0);
}
if (govAge >= 180)
diff --git a/BMA.EHR.Application/Repositories/Leaves/LeaveRequests/LeaveRequestRepository.cs b/BMA.EHR.Application/Repositories/Leaves/LeaveRequests/LeaveRequestRepository.cs
index 8416af4e..6363e258 100644
--- a/BMA.EHR.Application/Repositories/Leaves/LeaveRequests/LeaveRequestRepository.cs
+++ b/BMA.EHR.Application/Repositories/Leaves/LeaveRequests/LeaveRequestRepository.cs
@@ -11,6 +11,8 @@ using Microsoft.Extensions.Configuration;
using System.IO.Compression;
using System.Net.Http.Headers;
using System.Net.Http.Json;
+using BMA.EHR.Application.Repositories.Leaves.TimeAttendants;
+using BMA.EHR.Domain.Models.Leave.TimeAttendants;
namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests
{
@@ -29,6 +31,7 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests
private readonly MinIOLeaveService _minIOService;
private readonly LeaveBeginningRepository _leaveBeginningRepository;
+ private readonly ProcessUserTimeStampRepository _processUserTimeStampRepository;
private readonly string URL = string.Empty;
@@ -44,7 +47,8 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests
EmailSenderService emailSenderService,
IApplicationDBContext appDbContext,
MinIOLeaveService minIOService,
- LeaveBeginningRepository leaveBeginningRepository) : base(dbContext, httpContextAccessor)
+ LeaveBeginningRepository leaveBeginningRepository,
+ ProcessUserTimeStampRepository processUserTimeStampRepository) : base(dbContext, httpContextAccessor)
{
_dbContext = dbContext;
_httpContextAccessor = httpContextAccessor;
@@ -58,6 +62,7 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests
Console.WriteLine($"URL : {URL}");
_minIOService = minIOService;
_leaveBeginningRepository = leaveBeginningRepository;
+ _processUserTimeStampRepository = processUserTimeStampRepository;
}
#endregion
@@ -254,7 +259,7 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests
public async Task> GetLeaveRequestByYearAsync(int year, Guid userId)
{
// var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(userId, AccessToken);
- var profile = await _userProfileRepository.GetProfileByKeycloakIdNewAsync(userId, AccessToken);
+ var profile = await _userProfileRepository.GetProfileByKeycloakIdNew2Async(userId, AccessToken);
if (profile == null)
{
@@ -354,7 +359,7 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests
var rawData = _dbContext.Set().AsNoTracking()
.Include(x => x.Type)
.Where(x => x.LeaveStatus != "DRAFT")
- .OrderByDescending(x => x.CreatedAt)
+ .OrderByDescending(x => (x.DateSendLeave ?? x.CreatedAt))
.AsQueryable();
if (year != 0)
@@ -380,7 +385,7 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests
var rawData = _dbContext.Set().AsNoTracking()
.Include(x => x.Type)
.Where(x => x.LeaveStatus != "DRAFT")
- .OrderByDescending(x => x.CreatedAt)
+ .OrderByDescending(x => (x.DateSendLeave ?? x.CreatedAt))
.AsQueryable();
// fix issue : 1830
if (year != 0)
@@ -447,7 +452,7 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests
.Include(x => x.Type)
.Where(x => keycloakIdList.Contains(x.KeycloakUserId))
.Where(x => x.LeaveStatus != "DRAFT")
- .OrderByDescending(x => x.CreatedAt)
+ .OrderByDescending(x =>(x.DateSendLeave ?? x.CreatedAt))
.AsQueryable();
if (year != 0)
@@ -497,7 +502,7 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests
public async Task GetSumLeaveByTypeForUserAsync(Guid keycloakUserId, Guid leaveTypeId, int year)
{
// var pf = await _userProfileRepository.GetProfileByKeycloakIdAsync(keycloakUserId, AccessToken);
- var pf = await _userProfileRepository.GetProfileByKeycloakIdNewAsync(keycloakUserId, AccessToken);
+ var pf = await _userProfileRepository.GetProfileByKeycloakIdNew2Async(keycloakUserId, AccessToken);
if (pf == null)
throw new Exception(GlobalMessages.DataNotFound);
@@ -522,7 +527,7 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests
//.Where(x => x.LeaveStatus != "REJECT" && x.LeaveStatus != "DELETE")
.ToListAsync();
- return data.Sum(x => x.LeaveTotal) + (beginningLeave == null ? 0 : beginningLeave.LeaveDaysUsed);
+ return data.Sum(x => x.LeaveTotal) + (beginningLeave == null ? 0 : (beginningLeave.LeaveDaysUsed ?? 0.0));
}
//public async Task GetSumApproveLeaveByTypeForUserAsync(Guid keycloakUserId, Guid leaveTypeId, int year)
@@ -574,12 +579,12 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests
var data = await _dbContext.Set().AsQueryable().AsNoTracking()
.Include(x => x.Type)
//.Where(x => x.LeaveStartDate.Date < beforeDate.Date)
- .Where(x => x.CreatedAt < beforeDate)
+ .Where(x => (x.DateSendLeave ?? x.CreatedAt) < beforeDate)
.Where(x => x.KeycloakUserId == keycloakUserId)
.Where(x => x.Type.Id == leaveTypeId)
.Where(x => x.LeaveStatus == "APPROVE" || x.LeaveStatus == "DELETING")
//.Where(x => x.LeaveStatus != "REJECT" && x.LeaveStatus != "DELETE")
- .OrderByDescending(x => x.CreatedAt)
+ .OrderByDescending(x => (x.DateSendLeave ?? x.CreatedAt))
.FirstOrDefaultAsync();
return data;
@@ -651,7 +656,7 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests
try
{
// var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(data.KeycloakUserId, AccessToken ?? "");
- var profile = await _userProfileRepository.GetProfileByKeycloakIdNewAsync(data.KeycloakUserId, AccessToken ?? "");
+ var profile = await _userProfileRepository.GetProfileByKeycloakIdNew2Async(data.KeycloakUserId, AccessToken ?? "");
if (profile == null)
{
throw new Exception(GlobalMessages.DataNotFound);
@@ -728,7 +733,7 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests
}
// var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(rawData.KeycloakUserId, AccessToken ?? "");
- var profile = await _userProfileRepository.GetProfileByKeycloakIdNewAsync(rawData.KeycloakUserId, AccessToken ?? "");
+ var profile = await _userProfileRepository.GetProfileByKeycloakIdNew2Async(rawData.KeycloakUserId, AccessToken ?? "");
if (profile == null)
{
throw new Exception(GlobalMessages.DataNotFound);
@@ -817,7 +822,7 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests
}
// var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(rawData.KeycloakUserId, AccessToken ?? "");
- var profile = await _userProfileRepository.GetProfileByKeycloakIdNewAsync(rawData.KeycloakUserId, AccessToken ?? "");
+ var profile = await _userProfileRepository.GetProfileByKeycloakIdNew2Async(rawData.KeycloakUserId, AccessToken ?? "");
if (profile == null)
{
throw new Exception(GlobalMessages.DataNotFound);
@@ -904,6 +909,7 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests
rawData.LeaveStatus = "NEW";
+ rawData.DateSendLeave = DateTime.Now; // Update วันที่ยื่นลาเป็นวันที่ปัจจุบัน
//rawData.ApproveStep = "st2";
await UpdateAsync(rawData);
@@ -1242,7 +1248,7 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests
else
{
// var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(rawData.KeycloakUserId, AccessToken);
- var profile = await _userProfileRepository.GetProfileByKeycloakIdNewAsync(rawData.KeycloakUserId, AccessToken);
+ var profile = await _userProfileRepository.GetProfileByKeycloakIdNew2Async(rawData.KeycloakUserId, AccessToken);
if (profile == null)
{
throw new Exception(GlobalMessages.DataNotFound);
@@ -1324,9 +1330,68 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests
}
await _appDbContext.SaveChangesAsync();
- // insert to process timestamp
-
-
+ // ปรับสถานะการลงเวลา
+ if (rawData.LeaveStartDate.Date == rawData.LeaveEndDate.Date)
+ {
+ var processCheckIn = await _dbContext.Set()
+ .Where(x => x.KeycloakUserId == rawData.KeycloakUserId)
+ .Where(x => x.CheckIn.Date == rawData.LeaveStartDate.Date)
+ .FirstOrDefaultAsync();
+
+
+ if (processCheckIn is not null)
+ {
+ switch (rawData.LeaveRange.Trim().ToUpper())
+ {
+ case "MORNING":
+ processCheckIn.CheckInStatus = "NORMAL";
+ break;
+ case "AFTERNOON":
+ processCheckIn.CheckOutStatus = "NORMAL";
+ break;
+ case "ALL":
+ processCheckIn.CheckInStatus = "NORMAL";
+ processCheckIn.CheckOutStatus = "NORMAL";
+ break;
+ default:
+ break;
+ }
+ }
+ await _dbContext.SaveChangesAsync();
+ }
+ else
+ {
+ var from = rawData.LeaveStartDate.Date;
+ var to = rawData.LeaveEndDate.Date;
+ for (var day = from.Date; day <= to.Date; day = day.AddDays(1))
+ {
+ var processCheckIn = await _dbContext.Set()
+ .Where(x => x.KeycloakUserId == rawData.KeycloakUserId)
+ .Where(x => x.CheckIn.Date == day.Date)
+ .FirstOrDefaultAsync();
+
+ if (processCheckIn is not null)
+ {
+ switch (rawData.LeaveRange.Trim().ToUpper())
+ {
+ case "MORNING":
+ processCheckIn.CheckInStatus = "NORMAL";
+ break;
+ case "AFTERNOON":
+ processCheckIn.CheckOutStatus = "NORMAL";
+ break;
+ case "ALL":
+ processCheckIn.CheckInStatus = "NORMAL";
+ processCheckIn.CheckOutStatus = "NORMAL";
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ await _dbContext.SaveChangesAsync();
+ }
+
// Send Noti
var noti = new Notification
{
@@ -1412,7 +1477,7 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests
else
{
// var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(rawData.KeycloakUserId, AccessToken);
- var profile = await _userProfileRepository.GetProfileByKeycloakIdNewAsync(rawData.KeycloakUserId, AccessToken);
+ var profile = await _userProfileRepository.GetProfileByKeycloakIdNew2Async(rawData.KeycloakUserId, AccessToken);
if (profile == null)
{
throw new Exception(GlobalMessages.DataNotFound);
@@ -1485,7 +1550,7 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests
KeycloakUserId = pf.Keycloak == null ? Guid.Empty : pf.Keycloak.Value,
LeaveTypeId = b.LeaveTypeId,
LeaveTypeCode = b.LeaveType!.Code,
- SumLeaveDay = b.LeaveDaysUsed
+ SumLeaveDay = b.LeaveDaysUsed ?? 0.0
});
}
}
@@ -1870,15 +1935,131 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests
return 0;
}
- public async Task GetSumApproveLeaveTotalByTypeAndRangeForUser2(Guid keycloakUserId, Guid leaveTypeId, DateTime startDate, DateTime endDate)
+ public async Task GetSumApproveLeaveTotalByTypeAndRangeForUser2(Guid keycloakUserId, Guid leaveTypeId, DateTime startDate, DateTime endDate, DateTime sendLeaveDate)
+ {
+ // startDate/endDate คือขอบเขตปีงบประมาณ (fiscalStart/fiscalEnd) ที่ caller ส่งมา
+ // ใช้ LeaveStartDate เป็นหลักในการ filter เพื่อให้กรณียื่นลาล่วงหน้าข้ามปีงบประมาณ
+ // ถูกนับในปีงบประมาณของวันลาจริง (ไม่ใช้วันที่ยื่นลา)
+ var data = await _dbContext.Set().AsQueryable().AsNoTracking()
+ .Include(x => x.Type)
+ .Where(x => x.KeycloakUserId == keycloakUserId)
+ .Where(x => x.Type.Id == leaveTypeId)
+ .Where(x => (x.DateSendLeave ?? x.CreatedAt) < sendLeaveDate)
+ .Where(x => x.LeaveStartDate.Date >= startDate.Date && x.LeaveStartDate.Date <= endDate.Date)
+ .Where(x => x.LeaveStatus == "APPROVE" || x.LeaveStatus == "DELETING")
+ .ToListAsync();
+
+ if (data.Count > 0)
+ return data.Sum(x => x.LeaveTotal);
+ else
+ return 0;
+ }
+
+ public async Task GetSumApproveLeaveTotalByTypeAndRangeForUserBefore(Guid keycloakUserId, Guid leaveTypeId, DateTime startDate, DateTime endDate,DateTime sendLeaveDate)
+ {
+ var data = await _dbContext.Set().AsQueryable().AsNoTracking()
+ .Include(x => x.Type)
+ .Where(x => x.KeycloakUserId == keycloakUserId)
+ .Where(x => x.Type.Id == leaveTypeId)
+ .Where(x => (x.DateSendLeave ?? x.CreatedAt) < sendLeaveDate)
+ .Where(x => x.LeaveStartDate.Date >= startDate.Date && x.LeaveStartDate.Date <= endDate.Date)
+ .Where(x => x.LeaveStatus == "APPROVE" || x.LeaveStatus == "DELETING")
+ .ToListAsync();
+
+ if (data.Count > 0)
+ return data.Sum(x => x.LeaveTotal);
+ else
+ return 0;
+ }
+
+ public async Task GetSumApproveLeaveTotalByTypeAndRangeForUserByProfile(Guid profileId, Guid leaveTypeId, DateTime startDate, DateTime endDate,DateTime sendLeaveDate)
+ {
+ var data = await _dbContext.Set().AsQueryable().AsNoTracking()
+ .Include(x => x.Type)
+ .Where(x => x.ProfileId == profileId)
+ .Where(x => x.Type.Id == leaveTypeId)
+ .Where(x => (x.DateSendLeave ?? x.CreatedAt) < sendLeaveDate)
+ .Where(x => x.LeaveStartDate.Date >= startDate.Date && x.LeaveStartDate.Date <= endDate.Date)
+ .Where(x => x.LeaveStatus == "APPROVE" || x.LeaveStatus == "DELETING")
+ .ToListAsync();
+
+ if (data.Count > 0)
+ return data.Sum(x => x.LeaveTotal);
+ else
+ return 0;
+ }
+
+ public async Task GetSumApproveLeaveCountByTypeAndRangeForUserByProfile(Guid profileId, Guid leaveTypeId, DateTime startDate, DateTime endDate, DateTime sendLeaveDate)
+ {
+ var data = await _dbContext.Set().AsQueryable().AsNoTracking()
+ .Include(x => x.Type)
+ .Where(x => x.ProfileId == profileId)
+ .Where(x => x.Type.Id == leaveTypeId)
+ .Where(x => (x.DateSendLeave ?? x.CreatedAt) < sendLeaveDate)
+ .Where(x => x.LeaveStartDate.Date >= startDate.Date && x.LeaveStartDate.Date <= endDate.Date)
+ .Where(x => x.LeaveStatus == "APPROVE" || x.LeaveStatus == "DELETING")
+ .ToListAsync();
+
+ return data.Count;
+ }
+
+ public async Task GetSumApproveLeaveCountByTypeAndRangeForUser2(Guid keycloakUserId, Guid leaveTypeId, DateTime startDate, DateTime endDate, DateTime sendLeaveDate)
+ {
+ var data = await _dbContext.Set().AsQueryable().AsNoTracking()
+ .Include(x => x.Type)
+ .Where(x => x.KeycloakUserId == keycloakUserId)
+ .Where(x => x.Type.Id == leaveTypeId)
+ .Where(x => (x.DateSendLeave ?? x.CreatedAt) < sendLeaveDate)
+ .Where(x => x.LeaveStartDate.Date >= startDate.Date && x.LeaveStartDate.Date <= endDate.Date)
+ .Where(x => x.LeaveStatus == "APPROVE" || x.LeaveStatus == "DELETING")
+ .ToListAsync();
+
+ return data.Count;
+ }
+
+ ///
+ /// วันลาที่สร้างแบบร่างยังไม่ได้ยื่น
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public async Task GetSumDraftLeaveTotalByTypeAndRangeForUser2(Guid keycloakUserId, Guid leaveTypeId, DateTime startDate, DateTime endDate, DateTime sendLeaveDate)
{
var data = await _dbContext.Set().AsQueryable().AsNoTracking()
.Include(x => x.Type)
.Where(x => x.KeycloakUserId == keycloakUserId)
.Where(x => x.Type.Id == leaveTypeId)
- .Where(x => x.CreatedAt.Date >= startDate && x.CreatedAt < endDate)
- //.Where(x => x.LeaveStartDate.Date >= startDate.Date && x.LeaveStartDate.Date <= endDate.Date)
- .Where(x => x.LeaveStatus == "APPROVE" || x.LeaveStatus == "DELETING")
+ .Where(x => (x.DateSendLeave ?? x.CreatedAt) < sendLeaveDate)
+ .Where(x => x.LeaveStartDate.Date >= startDate.Date && x.LeaveStartDate.Date <= endDate.Date)
+ .Where(x => x.LeaveStatus == "DRAFT")
+ .ToListAsync();
+
+ if (data.Count > 0)
+ return data.Sum(x => x.LeaveTotal);
+ else
+ return 0;
+ }
+
+ ///
+ /// วันลาที่ยื่นแล้วรอพิจารณา
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public async Task GetSumNewLeaveTotalByTypeAndRangeForUser2(Guid keycloakUserId, Guid leaveTypeId, DateTime startDate, DateTime endDate,DateTime sendLeaveDate)
+ {
+ var data = await _dbContext.Set().AsQueryable().AsNoTracking()
+ .Include(x => x.Type)
+ .Where(x => x.KeycloakUserId == keycloakUserId)
+ .Where(x => x.Type.Id == leaveTypeId)
+ .Where(x => (x.DateSendLeave ?? x.CreatedAt) < sendLeaveDate)
+ .Where(x => x.LeaveStartDate.Date >= startDate.Date && x.LeaveStartDate.Date <= endDate.Date)
+ .Where(x => (x.LeaveStatus == "NEW" || x.LeaveStatus == "PENDING"))
.ToListAsync();
if (data.Count > 0)
diff --git a/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/AdditionalCheckRequestRepository.cs b/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/AdditionalCheckRequestRepository.cs
index 0544e261..d4bc0ea8 100644
--- a/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/AdditionalCheckRequestRepository.cs
+++ b/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/AdditionalCheckRequestRepository.cs
@@ -213,6 +213,79 @@ namespace BMA.EHR.Application.Repositories.Leaves.TimeAttendants
}
}
+ public async Task> GetAdditionalCheckRequestsByAdminRole2(DateTime startDate, DateTime endDate, string role, string nodeId, int? node, string? keyword, string? status)
+ {
+ try
+ {
+ var data = await _dbContext.Set().AsQueryable()
+ .Where(x => (x.CheckDate.Date >= startDate.Date && x.CheckDate.Date <= endDate.Date))
+ .OrderByDescending(x => x.CreatedAt.Date)
+ .ToListAsync();
+
+ if(!string.IsNullOrEmpty(status))
+ data = data.Where(x => x.Status == status).ToList();
+
+
+ if (!string.IsNullOrEmpty(keyword))
+ {
+ data = data.Where(x =>
+ (
+ (x.Prefix ?? "") + (x.FirstName ?? "") + " " + (x.LastName ?? "")).Contains(keyword)
+ || x.Description.Contains(keyword)
+
+ ).ToList();
+ }
+
+ if (role == "OWNER")
+ {
+ node = null;
+ }
+ if (role == "OWNER" || role == "CHILD")
+ {
+ data = data
+ .Where(x => node == 4 ? x.Child4DnaId == Guid.Parse(nodeId!) : (node == 3 ? x.Child3DnaId == Guid.Parse(nodeId!) : (node == 2 ? x.Child2DnaId == Guid.Parse(nodeId!) : (node == 1 ? x.Child1DnaId == Guid.Parse(nodeId!) : (node == 0 ? x.RootDnaId == Guid.Parse(nodeId!) : (node == null ? true : true))))))
+ .ToList();
+ }
+ else if (role == "BROTHER")
+ {
+ data = data
+ .Where(x => node == 4 ? x.Child3DnaId == Guid.Parse(nodeId!) : (node == 3 ? x.Child2DnaId == Guid.Parse(nodeId!) : (node == 2 ? x.Child1DnaId == Guid.Parse(nodeId!) : (node == 1 || node == 0 ? x.RootDnaId == Guid.Parse(nodeId!) : (node == null ? true : true)))))
+ .ToList();
+ }
+ else if (role == "ROOT")
+ {
+ data = data
+ .Where(x => x.RootDnaId == Guid.Parse(nodeId!)).ToList();
+ }
+ // else if (role == "PARENT")
+ // {
+ // data = data
+ // .Where(x => x.RootDnaId == Guid.Parse(nodeId!) && x.Child1DnaId != null && x.Child1DnaId != Guid.Empty).ToList();
+ // }
+ else if (role == "NORMAL")
+ {
+ data = data.Where(x =>
+ node == 0 ? x.RootDnaId == Guid.Parse(nodeId!) &&
+ (x.Child1DnaId == Guid.Empty || x.Child1DnaId == null) :
+ node == 1 ? x.Child1DnaId == Guid.Parse(nodeId!) &&
+ (x.Child2DnaId == Guid.Empty || x.Child2DnaId == null) :
+ node == 2 ? x.Child2DnaId == Guid.Parse(nodeId!) &&
+ (x.Child3DnaId == Guid.Empty || x.Child3DnaId == null) :
+ node == 3 ? x.Child3DnaId == Guid.Parse(nodeId!) &&
+ (x.Child4DnaId == Guid.Empty || x.Child4DnaId == null) :
+ node == 4 ? x.Child4DnaId == Guid.Parse(nodeId!) :
+ true
+ ).ToList();
+ }
+ return data;
+ }
+ catch
+ {
+ throw;
+ }
+ }
+
+
#endregion
}
}
diff --git a/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/CheckInJobStatusRepository.cs b/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/CheckInJobStatusRepository.cs
index 302bdd12..e0967a5c 100644
--- a/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/CheckInJobStatusRepository.cs
+++ b/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/CheckInJobStatusRepository.cs
@@ -114,6 +114,62 @@ namespace BMA.EHR.Application.Repositories.Leaves.TimeAttendants
return job!;
}
+ ///
+ /// ดึงข้อมูลงานที่ค้างอยู่ในสถานะ PENDING หรือ PROCESSING เกินเวลาที่กำหนด (นาที)
+ ///
+ public async Task> GetStalePendingOrProcessingJobsAsync(int timeoutMinutes = 30)
+ {
+ //var cutoffDate = DateTime.Now.AddMinutes(-timeoutMinutes);
+ var cutoffDate = DateTime.Now.AddMinutes(-timeoutMinutes);
+ var staleJobs = await _dbContext.Set()
+ .Where(x => (x.Status == "PENDING" || x.Status == "PROCESSING")
+ && x.CreatedDate <= cutoffDate)
+ .OrderBy(x => x.CreatedDate)
+ .ToListAsync();
+
+ return staleJobs;
+ }
+
+ ///
+ /// ดึงข้อมูลงานที่ค้างอยู่ในสถานะ PENDING หรือ PROCESSING เกินเวลาที่กำหนด (นาที) ของ user คนใดคนหนึ่ง
+ ///
+ public async Task> GetStalePendingOrProcessingJobsByUserAsync(Guid userId, int timeoutMinutes = 30)
+ {
+ var cutoffDate = DateTime.Now.AddMinutes(-timeoutMinutes);
+ //var cutoffDate = new DateTime(2026, 5, 28, 23, 59, 59);
+ var staleJobs = await _dbContext.Set()
+ .Where(x => x.KeycloakUserId == userId
+ && (x.Status == "PENDING" || x.Status == "PROCESSING")
+ && x.CreatedDate < cutoffDate)
+ .OrderBy(x => x.CreatedDate)
+ .ToListAsync();
+
+ return staleJobs;
+ }
+
+ ///
+ /// Mark งานที่ค้างเกินเวลาที่กำหนดเป็น FAILED
+ ///
+ public async Task MarkStaleJobsAsFailedAsync(int timeoutMinutes = 30)
+ {
+ var staleJobs = await GetStalePendingOrProcessingJobsAsync(timeoutMinutes);
+
+ foreach (var job in staleJobs)
+ {
+ job.Status = "FAILED";
+ job.CompletedDate = DateTime.Now;
+ job.ErrorMessage = $"งานค้างในสถานะ {job.Status} เกิน {timeoutMinutes} นาที ระบบทำเครื่องหมายเป็น FAILED อัตโนมัติ";
+ }
+
+ if (staleJobs.Any())
+ {
+ _dbContext.Set().UpdateRange(staleJobs);
+ await _dbContext.SaveChangesAsync();
+ }
+
+ return staleJobs.Count;
+ }
+
///
/// ล้างข้อมูล Job Status ที่เก่าเกิน X วัน
///
diff --git a/BMA.EHR.Application/Repositories/MessageQueue/NotificationRepository.cs b/BMA.EHR.Application/Repositories/MessageQueue/NotificationRepository.cs
index a9d5ceb7..9a8d71e3 100644
--- a/BMA.EHR.Application/Repositories/MessageQueue/NotificationRepository.cs
+++ b/BMA.EHR.Application/Repositories/MessageQueue/NotificationRepository.cs
@@ -187,6 +187,44 @@ namespace BMA.EHR.Application.Repositories.MessageQueue
}
}
+ private async Task GetMyProfileIdAsync()
+ {
+ var apiUrl = $"{_configuration["API"]}/org/dotnet/get-profileId";
+ var response = await GetExternalAPIAsync(apiUrl, AccessToken!, _configuration["API_KEY"]!);
+
+ if (string.IsNullOrWhiteSpace(response))
+ return string.Empty;
+
+ var org = JsonConvert.DeserializeObject(response);
+ if (org == null || org.result == null)
+ return string.Empty;
+
+ return org.result.profileId ?? string.Empty;
+ }
+
+ public async Task DeleteAllMyNotificationsAsync()
+ {
+ try
+ {
+ var profileId = await GetMyProfileIdAsync();
+ if (string.IsNullOrEmpty(profileId))
+ return 0;
+
+ var notifications = await _dbContext.Set()
+ .Where(x => x.ReceiverUserId == Guid.Parse(profileId))
+ .Where(x => x.DeleteDate == null)
+ .ToListAsync();
+
+ _dbContext.Set().RemoveRange(notifications);
+ await _dbContext.SaveChangesAsync();
+ return notifications.Count;
+ }
+ catch
+ {
+ throw;
+ }
+ }
+
public async Task PushNotificationAsync(Guid ReceiverUserId, string Subject, string Body, string Payload = "", string NotiLink = "", bool IsSendInbox = false, bool IsSendMail = false)
{
try
diff --git a/BMA.EHR.Application/Repositories/MetaData/HolidayRepository.cs b/BMA.EHR.Application/Repositories/MetaData/HolidayRepository.cs
index c5c09a44..1c8ae006 100644
--- a/BMA.EHR.Application/Repositories/MetaData/HolidayRepository.cs
+++ b/BMA.EHR.Application/Repositories/MetaData/HolidayRepository.cs
@@ -49,12 +49,16 @@ namespace BMA.EHR.Application.Repositories.MetaData
public async Task GetHolidayCountAsync(DateTime startDate, DateTime endDate, string category = "NORMAL")
{
- var data = await _dbContext.Set().AsQueryable()
+ var query = _dbContext.Set().AsQueryable()
.Where(x => x.Category == category)
- .Where(x => x.HolidayDate.Date >= startDate && x.HolidayDate.Date <= endDate)
- .CountAsync();
+ .Where(x => x.HolidayDate.Date >= startDate && x.HolidayDate.Date <= endDate);
- return data;
+ if (category == "NORMAL")
+ query = query.Where(x => x.HolidayDate.DayOfWeek != DayOfWeek.Saturday && x.HolidayDate.DayOfWeek != DayOfWeek.Sunday);
+ else
+ query = query.Where(x => x.HolidayDate.DayOfWeek != DayOfWeek.Sunday);
+
+ return await query.CountAsync();
}
public List GetWeekEnd(DateTime startDate, DateTime endDate, string category = "NORMAL")
diff --git a/BMA.EHR.Application/Repositories/PermissionRepository.cs b/BMA.EHR.Application/Repositories/PermissionRepository.cs
index 84191f91..a63207ec 100644
--- a/BMA.EHR.Application/Repositories/PermissionRepository.cs
+++ b/BMA.EHR.Application/Repositories/PermissionRepository.cs
@@ -10,6 +10,7 @@ using System.Net.Http.Headers;
using Microsoft.Extensions.Configuration;
using System.Security.Claims;
using System.Net.Http.Json;
+using BMA.EHR.Application.Responses.Leaves;
namespace BMA.EHR.Application.Repositories
{
@@ -76,6 +77,39 @@ namespace BMA.EHR.Application.Repositories
}
}
+ public async Task GetPermissionWithActingAPIAsync(string action, string system)
+ {
+ try
+ {
+ var apiPath = $"{_configuration["API"]}/org/permission/dotnet-acting/{action}/{system}";
+
+ using (var client = new HttpClient())
+ {
+ client.DefaultRequestHeaders.Authorization =
+ new AuthenticationHeaderValue("Bearer", AccessToken.Replace("Bearer ", ""));
+ client.DefaultRequestHeaders.Add("api-key", _configuration["API_KEY"]);
+ var req = await client.GetAsync(apiPath);
+ if (!req.IsSuccessStatusCode)
+ {
+ throw new Exception("Error calling permission API");
+ }
+ var apiResult = await req.Content.ReadAsStringAsync();
+ //return res;
+
+ if (apiResult != null)
+ {
+ var raw = JsonConvert.DeserializeObject(apiResult);
+ return raw;
+ }
+ return null;
+ }
+ }
+ catch
+ {
+ throw;
+ }
+ }
+
public async Task GetPermissionOrgAPIAsync(string action, string system, string profileId)
{
try
diff --git a/BMA.EHR.Application/Repositories/PlacementRepository.cs b/BMA.EHR.Application/Repositories/PlacementRepository.cs
index 49f1175c..eb7c3617 100644
--- a/BMA.EHR.Application/Repositories/PlacementRepository.cs
+++ b/BMA.EHR.Application/Repositories/PlacementRepository.cs
@@ -2,24 +2,40 @@
using BMA.EHR.Domain.Models.Placement;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.Configuration;
+using System.Net.Http.Headers;
+using Newtonsoft.Json;
namespace BMA.EHR.Application.Repositories
{
+ ///
+ /// Response model จาก Org API (check-isLeave)
+ ///
+ public class OrgProfileResult
+ {
+ public string citizenId { get; set; } = "";
+ public string? profileId { get; set; }
+ public bool isLeave { get; set; }
+ public bool isActive { get; set; }
+ }
+
public class PlacementRepository : GenericRepository
{
#region " Fields "
private readonly IApplicationDBContext _dbContext;
private readonly IHttpContextAccessor _httpContextAccessor;
+ private readonly IConfiguration _configuration;
#endregion
#region " Constructor and Destructor "
- public PlacementRepository(IApplicationDBContext dbContext, IHttpContextAccessor httpContextAccessor) : base(dbContext, httpContextAccessor)
+ public PlacementRepository(IApplicationDBContext dbContext, IHttpContextAccessor httpContextAccessor, IConfiguration configuration) : base(dbContext, httpContextAccessor)
{
_dbContext = dbContext;
_httpContextAccessor = httpContextAccessor;
+ _configuration = configuration;
}
#endregion
@@ -76,6 +92,148 @@ namespace BMA.EHR.Application.Repositories
return data;
}
+ ///
+ /// Job อัพเดทสถานะผู้สอบผ่านที่ลาออกไปแล้วแต่ยังไม่ส่งไปออกคำสั่ง
+ /// และอัพเดทบุคคลภายนอกที่เข้ามาอยู่ในระบบแล้ว
+ /// ทำงานทุกวันเวลา 05:00 น.
+ ///
+ public async Task UpdateStatusPlacementProfiles()
+ {
+ Console.WriteLine("[Job:UpdateStatusPlacementProfiles] === STARTED ===");
+
+ // 1. Query ทั้ง 2 กรณี: ทุกคนที่ยังไม่ DONE
+ var allCitizenIds = await _dbContext.Set()
+ .Where(p => !string.IsNullOrEmpty(p.CitizenId)
+ && p.PlacementStatus != "DONE"
+ // && p.CitizenId == "2536721883131"
+ )
+ .Select(p => new { p.CitizenId, p.IsOfficer })
+ .ToListAsync();
+
+ if (!allCitizenIds.Any())
+ {
+ Console.WriteLine("[Job:UpdateStatusPlacementProfiles] No profiles to process");
+ return;
+ }
+
+ var officerCount = allCitizenIds.Count(x => x.IsOfficer == true);
+ var notOfficerCount = allCitizenIds.Count(x => x.IsOfficer == false);
+ Console.WriteLine($"[Job:UpdateStatusPlacementProfiles] พบข้าราชการ {officerCount} คน, บุคคลภายนอก {notOfficerCount} คน");
+
+ // 2. ส่ง citizenIds ทั้งหมดไป Org API ครั้งเดียว
+ var citizenIds = allCitizenIds.Select(x => x.CitizenId).Distinct().ToList();
+ var apiUrl = $"{_configuration["API"]}/org/dotnet/check-isLeave";
+
+ List orgResults = new();
+
+ using (var client = new HttpClient())
+ {
+ client.DefaultRequestHeaders.Add("api-key", _configuration["API_KEY"]);
+
+ var payload = new { citizenIds };
+ var jsonPayload = JsonConvert.SerializeObject(payload);
+ var content = new StringContent(jsonPayload, System.Text.Encoding.UTF8, "application/json");
+
+ try
+ {
+ var response = await client.PostAsync(apiUrl, content);
+ var result = await response.Content.ReadAsStringAsync();
+
+ var responseObj = JsonConvert.DeserializeAnonymousType(result, new
+ {
+ status = 0,
+ message = "",
+ result = new List()
+ });
+
+ orgResults = responseObj?.result ?? new();
+ Console.WriteLine($"[Job:UpdateStatusPlacementProfiles] Org API ตอบกลับ {orgResults.Count} รายการ");
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"[Job:UpdateStatusPlacementProfiles] Call API failed: {ex.Message}");
+ return;
+ }
+ }
+
+ if (!orgResults.Any())
+ {
+ Console.WriteLine("[Job:UpdateStatusPlacementProfiles] ไม่มีรายการต้องอัปเดต");
+ Console.WriteLine("[Job:UpdateStatusPlacementProfiles] === COMPLETED ===");
+ return;
+ }
+
+ // 3. แยกข้อมูลตามเงื่อนไข
+ var leaveCitizenIds = orgResults.Where(x => x.isLeave).Select(x => x.citizenId).ToList();
+ var inSystemProfiles = orgResults.Where(x => x.isActive && !x.isLeave && !string.IsNullOrEmpty(x.profileId)).ToList();
+
+ Console.WriteLine($"[Job:UpdateStatusPlacementProfiles] ลาออก {leaveCitizenIds.Count} รายการ, อยู่ที่ทะเบียนประวัติ {inSystemProfiles.Count} รายการ");
+
+ // 4. Split Batch Update (500 รายการ/batch)
+ var batchSize = 500;
+ var totalUpdated = 0;
+
+ // 4.1 Update คนลาออก → IsOfficer = false
+ if (leaveCitizenIds.Any())
+ {
+ var totalBatches = (int)Math.Ceiling((double)leaveCitizenIds.Count / batchSize);
+ for (int i = 0; i < totalBatches; i++)
+ {
+ var batch = leaveCitizenIds.Skip(i * batchSize).Take(batchSize).ToList();
+
+ var profilesToUpdate = await _dbContext.Set()
+ .Where(p => !string.IsNullOrEmpty(p.CitizenId)
+ && batch.Contains(p.CitizenId)
+ && p.IsOfficer == true)
+ .ToListAsync();
+
+ foreach (var profile in profilesToUpdate)
+ {
+ profile.profileId = null;
+ profile.IsOfficer = false;
+ }
+
+ await _dbContext.SaveChangesAsync();
+ totalUpdated += profilesToUpdate.Count;
+ Console.WriteLine($"[Job:UpdateStatusPlacementProfiles] [ลาออก] Batch {i + 1}/{totalBatches} → อัปเดต {profilesToUpdate.Count} รายการ");
+ }
+ }
+
+ // 4.2 Update คนที่อยู่ในทะเบียนประวัติ → profileId + IsOfficer = true
+ if (inSystemProfiles.Any())
+ {
+ var totalBatches = (int)Math.Ceiling((double)inSystemProfiles.Count / batchSize);
+ for (int i = 0; i < totalBatches; i++)
+ {
+ var batch = inSystemProfiles.Skip(i * batchSize).Take(batchSize).ToList();
+ var batchCitizenIds = batch.Select(x => x.citizenId).ToList();
+
+ var profilesToUpdate = await _dbContext.Set()
+ .Where(p => !string.IsNullOrEmpty(p.CitizenId)
+ && batchCitizenIds.Contains(p.CitizenId)
+ && p.IsOfficer == false)
+ .ToListAsync();
+
+ foreach (var profile in profilesToUpdate)
+ {
+ var orgProfile = batch.FirstOrDefault(x => x.citizenId == profile.CitizenId);
+ if (orgProfile != null)
+ {
+ profile.profileId = orgProfile.profileId;
+ profile.IsOfficer = true;
+ }
+ }
+
+ await _dbContext.SaveChangesAsync();
+ totalUpdated += profilesToUpdate.Count;
+ Console.WriteLine($"[Job:UpdateStatusPlacementProfiles] [เข้าระบบ] Batch {i + 1}/{totalBatches} → อัปเดต {profilesToUpdate.Count} รายการ");
+ }
+ }
+
+ Console.WriteLine($"[Job:UpdateStatusPlacementProfiles] อัปเดตรวมทั้งหมด {totalUpdated} รายการ");
+ Console.WriteLine("[Job:UpdateStatusPlacementProfiles] === COMPLETED ===");
+ }
+
#endregion
}
}
diff --git a/BMA.EHR.Application/Repositories/Reports/RetireReportRepository.cs b/BMA.EHR.Application/Repositories/Reports/RetireReportRepository.cs
index ecb65e2f..6ad5a61a 100644
--- a/BMA.EHR.Application/Repositories/Reports/RetireReportRepository.cs
+++ b/BMA.EHR.Application/Repositories/Reports/RetireReportRepository.cs
@@ -192,7 +192,7 @@ namespace BMA.EHR.Application.Repositories.Reports
}).ToList();
}
string SignDate = retireHistorys.SignDate != null ? DateTime.Parse(retireHistorys.SignDate.ToString()).ToThaiFullDate().ToString().ToThaiNumber() : "-";
- return new { SignDate, retireHistorys.Detail, retireHistorys.Id, retireHistorys.CreatedAt, Year = retireHistorys.Year.ToThaiYear().ToString().ToThaiNumber(), retireHistorys.Round, retireHistorys.Type, retireHistorys.TypeReport, Total = retireHistorys.Total.ToString().ToThaiNumber(), profiles = mapProfiles };
+ return new { SignDate, Detail = retireHistorys.Detail.ToThaiNumber(), retireHistorys.Id, retireHistorys.CreatedAt, Year = retireHistorys.Year.ToThaiYear().ToString().ToThaiNumber(), retireHistorys.Round, retireHistorys.Type, retireHistorys.TypeReport, Total = retireHistorys.Total.ToString().ToThaiNumber(), profiles = mapProfiles };
}
}
else
@@ -312,7 +312,7 @@ namespace BMA.EHR.Application.Repositories.Reports
root = (isDuplicateRoot ? "" : profile.root + "\n") +
(isDuplicateHospital || !hospital.ToObject>().Contains(profile.child1) ? "" : profile.child1 + "\n") +
(isDuplicatePosType ? "" : $"ตำแหน่งประเภท{profile.posTypeName}" + "\n") +
- (isDuplicatePosLevel ? "" : $"ระดับ{profile.posLevelName}"),
+ (isDuplicatePosLevel ? "" : $"ระดับ{profile.posLevelName}").ToThaiNumber(),
child = (profile.posExecutiveName == null ? "" : profile.posExecutiveName + "\n") +
(profile.child4 == null ? "" : profile.child4 + "\n") +
(profile.child3 == null ? "" : profile.child3 + "\n") +
@@ -326,7 +326,7 @@ namespace BMA.EHR.Application.Repositories.Reports
}).ToList();
}
string SignDate = retire.SignDate != null ? DateTime.Parse(retire.SignDate.ToString()).ToThaiFullDate().ToString().ToThaiNumber() : "-";
- return new { SignDate, retire.Detail, retire.Id, retire.CreatedAt, Year = retire.Year.ToThaiYear().ToString().ToThaiNumber(), retire.Round, retire.Type, retire.TypeReport, Total = profile_retire.Count.ToString().ToThaiNumber(), profiles = mapProfiles };
+ return new { SignDate, Detail = retire.Detail.ToThaiNumber(), retire.Id, retire.CreatedAt, Year = retire.Year.ToThaiYear().ToString().ToThaiNumber(), retire.Round, retire.Type, retire.TypeReport, Total = profile_retire.Count.ToString().ToThaiNumber(), profiles = mapProfiles };
}
}
#endregion
diff --git a/BMA.EHR.Application/Repositories/UserProfileRepository.cs b/BMA.EHR.Application/Repositories/UserProfileRepository.cs
index 4f081f77..aa89d9b9 100644
--- a/BMA.EHR.Application/Repositories/UserProfileRepository.cs
+++ b/BMA.EHR.Application/Repositories/UserProfileRepository.cs
@@ -233,6 +233,29 @@ namespace BMA.EHR.Application.Repositories
throw;
}
}
+
+ public async Task GetProfileByCheckInAsync(Guid keycloakId, string? accessToken)
+ {
+ try
+ {
+ var apiPath = $"{_configuration["API"]}/org/dotnet/check-keycloak/{keycloakId}";
+ var apiKey = _configuration["API_KEY"];
+
+ var apiResult = await GetExternalAPIAsync(apiPath, accessToken ?? "", apiKey);
+ if (apiResult != null)
+ {
+ var raw = JsonConvert.DeserializeObject(apiResult);
+ if (raw != null)
+ return raw.Result;
+ }
+
+ return null;
+ }
+ catch
+ {
+ throw;
+ }
+ }
public async Task GetProfileLeaveByKeycloakIdAsync(Guid keycloakId, string? accessToken)
@@ -1039,6 +1062,42 @@ namespace BMA.EHR.Application.Repositories
}
}
+ public async Task> GetEmployeeByAdminRolev2(string? accessToken, int? node, string? nodeId, string role, string? revisionId, int? reqNode, string? reqNodeId, DateTime? startDate, DateTime? endDate)
+ {
+ try
+ {
+ var apiPath = $"{_configuration["API"]}/org/dotnet/employee-by-admin-rolev2";
+ var apiKey = _configuration["API_KEY"];
+ var body = new
+ {
+ node = node,
+ nodeId = nodeId,
+ role = role,
+ // isRetirement
+ reqNode = reqNode,
+ reqNodeId = reqNodeId,
+ date = endDate
+ };
+ Console.WriteLine(body);
+
+ var profiles = new List();
+
+ var apiResult = await PostExternalAPIAsync(apiPath, accessToken, body, apiKey);
+ if (apiResult != null)
+ {
+ var raw = JsonConvert.DeserializeObject(apiResult);
+ if (raw != null)
+ return raw.Result;
+ }
+
+ return new List();
+ }
+ catch
+ {
+ throw;
+ }
+ }
+
public async Task SearchProfile(string? citizenId, string? firstName, string? lastName, string accessToken, int page, int pageSize, string? role, string? nodeId, int? node,string? selectedNodeId,int? selectedNode )
{
try
diff --git a/BMA.EHR.Application/Responses/Leaves/GetPermissionWithActingDto.cs b/BMA.EHR.Application/Responses/Leaves/GetPermissionWithActingDto.cs
new file mode 100644
index 00000000..083c4b20
--- /dev/null
+++ b/BMA.EHR.Application/Responses/Leaves/GetPermissionWithActingDto.cs
@@ -0,0 +1,37 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using BMA.EHR.Domain.Shared;
+using Newtonsoft.Json;
+
+namespace BMA.EHR.Application.Responses.Leaves
+{
+ public class GetPermissionWithActingDto
+ {
+ public string privilege {get; set;} = string.Empty;
+ public bool isAct {get; set;} = false;
+ public List posMasterActs {get; set;} = new();
+ }
+
+ public class ActingPermission
+ {
+ public string posNo {get; set;} = string.Empty;
+ //public string? privilege {get; set;} = "PARENT";
+ [JsonConverter(typeof(PrivilegeConverter))]
+ public string privilege {get; set;} = "CHILD";
+
+ public Guid? rootDnaId {get; set;}
+ public Guid? child1DnaId {get; set;}
+ public Guid? child2DnaId {get; set;}
+ public Guid? child3DnaId {get; set;}
+ public Guid? child4DnaId {get; set;}
+ }
+
+ public class GetPermissionWithActingResultDto
+ {
+ public int status {get; set;} = 0;
+ public string message {get; set;} = string.Empty;
+ public GetPermissionWithActingDto result {get; set;} = new();
+ }
+}
\ No newline at end of file
diff --git a/BMA.EHR.CheckInConsumer/.dockerignore b/BMA.EHR.CheckInConsumer/.dockerignore
new file mode 100644
index 00000000..741ce7d4
--- /dev/null
+++ b/BMA.EHR.CheckInConsumer/.dockerignore
@@ -0,0 +1,24 @@
+# Build artifacts
+bin/
+obj/
+
+# IDE / tooling
+Properties/
+.vs/
+.vscode/
+.idea/
+
+# Source control
+.git/
+.gitignore
+
+# Documentation
+*.md
+
+# Docker
+Dockerfile
+.dockerignore
+
+# OS files
+.DS_Store
+Thumbs.db
diff --git a/BMA.EHR.CheckInConsumer/CHANGELOG-checkin-speedup.md b/BMA.EHR.CheckInConsumer/CHANGELOG-checkin-speedup.md
new file mode 100644
index 00000000..7b20dd1b
--- /dev/null
+++ b/BMA.EHR.CheckInConsumer/CHANGELOG-checkin-speedup.md
@@ -0,0 +1,83 @@
+# สรุปการปรับปรุงระบบลงเวลา (CheckInConsumer)
+
+วันที่แก้ไข: 23 มิถุนายน 2026
+
+---
+
+## ปัญหาเดิม
+
+ตอนที่พนักงานลงเวลาพร้อมกันจำนวนมาก (ประมาณ 2,000 รายการ) ระบบประมวลผลทีละรายการ ทำให้ต้องรอคิวนานถึง **22 นาที** กว่าจะประมวลผลเสร็จทั้งหมด
+
+เปรียบเทียบเหมือน **โต๊ะบัญชี 1 คน รับคิวทีละคน** ทั้งที่มีคนรอ 2,000 คน → คิวยาวมาก
+
+---
+
+## วิธีที่แก้ (เข้าใจง่าย ๆ)
+
+### 1. เพิ่มคนช่วยประมวลผลพร้อมกัน (Concurrency)
+- **ก่อน:** ประมวลผลทีละรายการ (เหมือนมีโต๊ะบัญชี 1 โต๊ะ)
+- **หลัง:** ประมวลผลพร้อมกันได้สูงสุด **5 รายการ** (เหมือนเปิดโต๊ะบัญชี 5 โต๊ะ)
+
+> ผลที่ได้: เวลารอคิวลดลงจาก **22 นาที → ประมาณ 4–5 นาที**
+
+### 2. จัดคิวล่วงหน้าให้ RabbitMQ (Prefetch)
+- **ก่อน:** ระบบดึงข้อมูลมาทีละชิ้น ทำให้เสียเวลารอส่งต่อ
+- **หลัง:** ระบบดึงข้อมูลมาเป็นชุด ๆ ละ 20 ชิ้นไว้เตรียมพร้อม → ลดเวลารอระหว่างรายการ
+
+### 3. ลดเวลารอเมื่อ API มีปัญหา (Timeout)
+- **ก่อน:** ถ้า API ค้าง ระบบจะรอนานถึง **5 นาที** ต่อรายการ
+- **หลัง:** ลดเหลือ **1 นาที** → รายการที่มีปัญหาจะถูกปฏิเสธเร็วขึ้น ไม่ทำให้คิวค้าง
+
+### 4. ปรับปรุงการเชื่อมต่อ HTTP
+- เปลี่ยนระบบเชื่อมต่อให้รองรับการส่งคำขอหลายรายการพร้อมกันโดยไม่สะดุด
+
+---
+
+## ตัวเลขเปรียบเทียบ
+
+| รายการ | ก่อนแก้ | หลังแก้ |
+|---|---|---|
+| จำนวนรายการที่ประมวลผลพร้อมกัน | 1 | 5 |
+| เวลารอคิวสูงสุด (2,000 รายการ) | ~22 นาที | ~4–5 นาที |
+| เวลารอเมื่อ API มีปัญหา | 5 นาที | 1 นาที |
+
+---
+
+## ไฟล์ที่แก้ไข
+
+1. **`Program.cs`** — โค้ดหลักของตัวประมวลผลคิว
+2. **`appsettings.json`** — ไฟล์ตั้งค่าระบบ
+
+---
+
+## วิธีปรับความเร็วเพิ่มเติม (ไม่ต้องเขียนโค้ดใหม่)
+
+ถ้าหลังทดสอบแล้วเห็นว่าระบบรับได้ และอยากให้เร็วขึ้นอีก ให้แก้ไขไฟล์ `appsettings.json` แล้ว restart โปรแกรมได้เลย:
+
+```json
+{
+ "MaxConcurrency": 10, ← เพิ่มจาก 5 เป็น 10 (ประมวลผลพร้อมกัน 10 รายการ)
+ "PrefetchCount": 50, ← ควรตั้งเป็น ประมาณ MaxConcurrency × 2 ขึ้นไป
+ "HttpTimeoutSeconds": 60 ← เวลารอ API วินาที
+}
+```
+
+**ค่าที่ใช้และผลที่คาดการณ์:**
+- `MaxConcurrency = 5` → ใช้เวลา ~4–5 นาที (ค่าเริ่มต้นปลอดภัย)
+- `MaxConcurrency = 10` → ใช้เวลา ~2–3 นาที
+- `MaxConcurrency = 20` → ใช้เวลา ~1–2 นาที (ต้องตรวจสอบว่าระบบหลังบ้านรับไหวก่อน)
+
+---
+
+## ข้อควรระวัง / คำแนะนำ
+
+1. **ควรทดสอบในระบบทดสอบก่อน** โดยดูว่า
+ - ไม่มี error ในระบบหลัก (API)
+ - ฐานข้อมูลไม่ช้าผิดปกติ
+ - ไม่พบปัญหาลงเวลาซ้ำซ้อน
+
+2. ถ้าพบปัญหา เช่น
+ - มี error ใน API → **ลด** `MaxConcurrency` เหลือ 2 หรือ 3
+ - ลงเวลาซ้ำ → แจ้งทีมเทคนิคเพื่อแก้ฝั่ง API เพิ่มเติม
+
+3. **ค่า `MaxConcurrency = 5` เป็นค่าปลอดภัย** เพราะระบบ API ด้านหลังยังมีข้อจำกัดอยู่บางส่วน หากต้องการเพิ่มให้สูงกว่านี้ (เช่น 20–50) ควรปรึกษาทีมเทคนิคเพื่อปรับปรุงฝั่ง API ก่อน
diff --git a/BMA.EHR.CheckInConsumer/Dockerfile b/BMA.EHR.CheckInConsumer/Dockerfile
index 1c90d6f2..81c430d9 100644
--- a/BMA.EHR.CheckInConsumer/Dockerfile
+++ b/BMA.EHR.CheckInConsumer/Dockerfile
@@ -1,4 +1,4 @@
-## See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging.
+## See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging.
#
## This stage is used when running from VS in fast mode (Default for Debug configuration)
#FROM mcr.microsoft.com/dotnet/runtime:8.0 AS base
@@ -21,6 +21,7 @@
#ARG BUILD_CONFIGURATION=Release
#RUN dotnet publish "BMA.EHR.CheckInConsumer.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
#
+
## This stage is used in production or when running from VS in regular mode (Default when not using the Debug configuration)
#FROM base AS final
#WORKDIR /app
@@ -29,30 +30,25 @@
# ใช้ official .NET SDK image สำหรับการ build
+# Note: Build context = repository root (ตามที่ GitHub Actions ใช้)
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
-# กำหนด working directory ภายใน container
WORKDIR /src
-# คัดลอกไฟล์ .csproj และ restore dependencies
-# COPY *.csproj ./
-COPY . ./
-RUN dotnet restore
+# copy เฉพาะ .csproj ก่อน เพื่อใช้ layer caching (restore เร็ว เก็บ cache นาน)
+COPY BMA.EHR.CheckInConsumer/BMA.EHR.CheckInConsumer.csproj ./BMA.EHR.CheckInConsumer/
+WORKDIR /src/BMA.EHR.CheckInConsumer
+RUN dotnet restore "BMA.EHR.CheckInConsumer.csproj"
-# คัดลอกไฟล์ทั้งหมดและ build
-COPY . ./
-RUN dotnet build -c Release -o /app/build
-# WORKDIR "/src/BMA.EHR.CheckInConsumer"
-# RUN dotnet build "BMA.EHR.CheckInConsumer.csproj" -c Release -o /app/build
+# คัดลอก source ที่เหลือแล้ว publish
+COPY BMA.EHR.CheckInConsumer/ ./
+RUN dotnet publish "BMA.EHR.CheckInConsumer.csproj" -c Release -o /app/publish /p:UseAppHost=false
-# ใช้ stage ใหม่สำหรับการ runtime
+# ใช้ stage ใหม่สำหรับ runtime (image เล็กลง)
FROM mcr.microsoft.com/dotnet/runtime:8.0 AS runtime
-# กำหนด working directory สำหรับ runtime
WORKDIR /app
-# คัดลอกไฟล์จาก build stage มายัง runtime stage
-COPY --from=build /app/build .
+COPY --from=build /app/publish .
-# ระบุ entry point ของแอปพลิเคชัน
ENTRYPOINT ["dotnet", "BMA.EHR.CheckInConsumer.dll"]
diff --git a/BMA.EHR.CheckInConsumer/Program.cs b/BMA.EHR.CheckInConsumer/Program.cs
index 0ae439cf..a0ff686c 100644
--- a/BMA.EHR.CheckInConsumer/Program.cs
+++ b/BMA.EHR.CheckInConsumer/Program.cs
@@ -1,4 +1,4 @@
-using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Configuration;
using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using System.Text;
@@ -18,80 +18,133 @@ var user = configuration["Rabbit:User"] ?? "";
var pass = configuration["Rabbit:Password"] ?? "";
var queue = configuration["Rabbit:Queue"] ?? "basic-queue";
+// Concurrency & prefetch (configurable via appsettings.json)
+var maxConcurrency = int.TryParse(configuration["MaxConcurrency"], out var c) && c > 0 ? c : 5;
+var prefetchCount = ushort.TryParse(configuration["PrefetchCount"], out var p) && p > 0 ? p : (ushort)20;
+var httpTimeoutSec = int.TryParse(configuration["HttpTimeoutSeconds"], out var t) && t > 0 ? t : 60;
+
+WriteToConsole($"Config -> MaxConcurrency: {maxConcurrency}, PrefetchCount: {prefetchCount}, HttpTimeout: {httpTimeoutSec}s");
+
// create connection
var factory = new ConnectionFactory()
{
- //Uri = new Uri("amqp://admin:P@ssw0rd@192.168.4.11:5672")
- HostName = host,// หรือ hostname ของ RabbitMQ Server ที่คุณใช้
- UserName = user, // ใส่ชื่อผู้ใช้ของคุณ
- Password = pass // ใส่รหัสผ่านของคุณ
+ HostName = host,
+ UserName = user,
+ Password = pass,
+ DispatchConsumersAsync = true
};
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);
+// Prefetch: RabbitMQ จะส่ง message หลายตัวมาที่ consumer พร้อมกัน (ลด network round-trip)
+channel.BasicQos(prefetchSize: 0, prefetchCount: prefetchCount, global: false);
-consumer.Received += async (model, ea) =>
+// HttpClient แบบ SocketsHttpHandler พร้อม connection pooling รองรับ concurrent requests
+var socketsHandler = new SocketsHttpHandler
{
- var body = ea.Body.ToArray();
- var message = Encoding.UTF8.GetString(body);
- await CallRestApi(message);
+ MaxConnectionsPerServer = maxConcurrency * 2,
+ PooledConnectionLifetime = TimeSpan.FromMinutes(2),
+ PooledConnectionIdleTimeout = TimeSpan.FromSeconds(30)
+};
+using var httpClient = new HttpClient(socketsHandler);
+httpClient.Timeout = TimeSpan.FromSeconds(httpTimeoutSec);
- // 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();
+// SemaphoreSlim คุมจำนวน message ที่ประมวลผลพร้อมกัน (เนื่องจาก API มีข้อจำกัดเรื่อง concurrency)
+using var semaphore = new SemaphoreSlim(maxConcurrency, maxConcurrency);
- // WriteToConsole($"ได้รับคำขอจาก Queue: {message}");
- // WriteToConsole($"ตอบกลับจาก REST API: {JsonConvert.SerializeObject(item)}");
- //}
+var consumer = new AsyncEventingBasicConsumer(channel);
- WriteToConsole($"ได้รับคำขอจาก Queue: {message}");
- //WriteToConsole($"ตอบกลับจาก REST API: {JsonConvert.SerializeObject(item)}");
+consumer.Received += (model, ea) =>
+{
+ // รอ semaphore ก่อนเริ่มประมวลผล
+ semaphore.WaitAsync().ContinueWith(async _ =>
+ {
+ try
+ {
+ var body = ea.Body.ToArray();
+ var message = Encoding.UTF8.GetString(body);
+
+ WriteToConsole($"Received message: {message}");
+
+ var success = await CallRestApi(message, httpClient, configuration);
+
+ if (success)
+ {
+ channel.BasicAck(ea.DeliveryTag, multiple: false);
+ WriteToConsole("Message processed successfully");
+ }
+ else
+ {
+ channel.BasicNack(ea.DeliveryTag, multiple: false, requeue: false);
+ WriteToConsole("Message processing failed - message rejected");
+ }
+ }
+ catch (Exception ex)
+ {
+ WriteToConsole($"Error processing message: {ex.Message}");
+ channel.BasicNack(ea.DeliveryTag, multiple: false, requeue: false);
+ }
+ finally
+ {
+ semaphore.Release();
+ }
+ }, TaskScheduler.Default).ConfigureAwait(false);
+
+ return Task.CompletedTask;
};
-//channel.BasicConsume(queue: "bma-checkin-queue", autoAck: true, consumer: consumer);
-channel.BasicConsume(queue: queue, autoAck: true, consumer: consumer);
+channel.BasicConsume(queue: queue, autoAck: false, consumer: consumer);
-//Console.WriteLine("\nPress 'Enter' to exit the process...");
+WriteToConsole("Consumer started. Waiting for messages...");
+// Keep the application running
await Task.Delay(-1);
static void WriteToConsole(string message)
{
- Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")} : {message}");
+ Console.WriteLine($"{DateTime.Now:yyyy-MM-dd HH:mm:ss} : {message}");
}
-async Task CallRestApi(string requestData)
+static async Task CallRestApi(string requestData, HttpClient client, IConfiguration configuration)
{
- using var client = new HttpClient();
- var apiPath = $"{configuration["API"]}/leave/process-check-in";
-
- var content = new StringContent(requestData, Encoding.UTF8, "application/json");
-
- var response = await client.PostAsync(apiPath, content);
-
- if (response.IsSuccessStatusCode)
+ try
{
- var responseContent = await response.Content.ReadAsStringAsync();
- WriteToConsole(responseContent);
+ var apiPath = $"{configuration["API"]}/leave/process-check-in";
+ var content = new StringContent(requestData, Encoding.UTF8, "application/json");
+
+ var response = await client.PostAsync(apiPath, content);
+
+ if (response.IsSuccessStatusCode)
+ {
+ var responseContent = await response.Content.ReadAsStringAsync();
+ WriteToConsole($"API Success: {responseContent}");
+ return true;
+ }
+ else
+ {
+ var errorMessage = await response.Content.ReadAsStringAsync();
+ var res = JsonSerializer.Deserialize(errorMessage);
+ WriteToConsole($"API Error ({response.StatusCode}): {res?.Message ?? errorMessage}");
+ return false;
+ }
}
- else
+ catch (HttpRequestException ex)
{
- var errorMessage = await response.Content.ReadAsStringAsync();
- var res = JsonSerializer.Deserialize(errorMessage);
- WriteToConsole($"Error: {res.Message}");
+ WriteToConsole($"HTTP Error: {ex.Message}");
+ return false;
+ }
+ catch (TaskCanceledException ex)
+ {
+ WriteToConsole($"Timeout: {ex.Message}");
+ return false;
+ }
+ catch (Exception ex)
+ {
+ WriteToConsole($"Unexpected Error: {ex.Message}");
+ return false;
}
}
@@ -110,28 +163,14 @@ public class ResponseObject
public class CheckTimeDtoRB
{
public Guid? CheckInId { get; set; }
-
-
public double Lat { get; set; } = 0;
-
-
public double Lon { get; set; } = 0;
-
-
public string POI { get; set; } = string.Empty;
-
-
public bool IsLocation { get; set; } = true;
-
public string? LocationName { get; set; } = string.Empty;
-
public string? Remark { get; set; } = string.Empty;
-
public Guid? UserId { get; set; }
-
public DateTime? CurrentDate { get; set; }
-
public string? CheckInFileName { get; set; }
-
public byte[]? CheckInFileBytes { get; set; }
-}
\ No newline at end of file
+}
diff --git a/BMA.EHR.CheckInConsumer/appsettings.json b/BMA.EHR.CheckInConsumer/appsettings.json
index 76f86c86..1fbba85b 100644
--- a/BMA.EHR.CheckInConsumer/appsettings.json
+++ b/BMA.EHR.CheckInConsumer/appsettings.json
@@ -5,5 +5,8 @@
"Password": "12345678",
"Queue": "hrms-checkin-queue-dev"
},
- "API": "https://localhost:7283/api/v1"
+ "API": "https://localhost:7283/api/v1",
+ "MaxConcurrency": 5,
+ "PrefetchCount": 20,
+ "HttpTimeoutSeconds": 60
}
diff --git a/BMA.EHR.Discipline.Service/Controllers/DisciplineResultController.cs b/BMA.EHR.Discipline.Service/Controllers/DisciplineResultController.cs
index 85388209..35a9dfa2 100644
--- a/BMA.EHR.Discipline.Service/Controllers/DisciplineResultController.cs
+++ b/BMA.EHR.Discipline.Service/Controllers/DisciplineResultController.cs
@@ -968,12 +968,20 @@ namespace BMA.EHR.DisciplineResult.Service.Controllers
/// ไม่ได้ Login เข้าระบบ
/// เมื่อเกิดข้อผิดพลาดในการทำงาน
[HttpPost("command19/report")]
- public async Task> PostReportCommand19([FromBody] ReportPersonRequest req)
+ public async Task> PostReportCommand19([FromBody] ReportPersonAndCommandRequest req)
{
var data = await _context.DisciplineDisciplinary_ProfileComplaintInvestigates
.Where(x => req.refIds.Contains(x.Id.ToString()))
.ToListAsync();
- data.ForEach(profile => profile.Status = req.status.Trim().ToUpper());
+ // data.ForEach(profile => profile.Status = req.status.Trim().ToUpper());
+ data.ForEach(profile =>
+ {
+ profile.Status = !string.IsNullOrEmpty(req.status)
+ ? req.status.Trim().ToUpper() : null;
+ profile.CommandTypeId = !string.IsNullOrEmpty(req.commandTypeId) && Guid.TryParse(req.commandTypeId, out var cmdTypeId)
+ ? cmdTypeId : null;
+ profile.CommandCode = req.commandCode ?? null;
+ });
await _context.SaveChangesAsync();
return Success();
}
@@ -993,7 +1001,13 @@ namespace BMA.EHR.DisciplineResult.Service.Controllers
.Where(x => req.refIds.Contains(x.Id.ToString()))
// .Where(x => x.Status.ToUpper() == "REPORT")
.ToListAsync();
- data.ForEach(profile => profile.Status = "NEW");
+ // data.ForEach(profile => profile.Status = "NEW");
+ data.ForEach(profile =>
+ {
+ profile.Status = "NEW";
+ profile.CommandTypeId = null;
+ profile.CommandCode = null;
+ });
await _context.SaveChangesAsync();
return Success();
}
@@ -1013,6 +1027,11 @@ namespace BMA.EHR.DisciplineResult.Service.Controllers
.Include(x => x.DisciplineDisciplinary)
.Where(x => req.refIds.Select(x => x.refId).Contains(x.Id.ToString()))
.ToListAsync();
+
+ // Task #224 ปรับให้เป็น process ที่ควรบันทึกตามลำดับ
+ data.ForEach(profile => { profile.Status = "REPORTED"; profile.CommandTypeId = null; });
+ await _context.SaveChangesAsync();
+
var resultData = (from p in data
join r in req.refIds
on p.Id.ToString() equals r.refId
@@ -1061,8 +1080,8 @@ namespace BMA.EHR.DisciplineResult.Service.Controllers
var _result = await _res.Content.ReadAsStringAsync();
if (_res.IsSuccessStatusCode)
{
- // คำสั่งไล่ออก หรือ ปลดออก Status หลังออกคำสั่งใช้ "REPORTED" เพื่อไม่ให้ส่งรายชื่อไปออกคำสั่งซ้ำได้
- data.ForEach(profile => { profile.Status = "REPORTED"; profile.CommandTypeId = null; });
+ //// คำสั่งไล่ออก หรือ ปลดออก Status หลังออกคำสั่งใช้ "REPORTED" เพื่อไม่ให้ส่งรายชื่อไปออกคำสั่งซ้ำได้
+ // data.ForEach(profile => { profile.Status = "REPORTED"; profile.CommandTypeId = null; });
var _profile = new List();
DateTime _date = DateTime.Now;
foreach (var item in data)
@@ -1105,12 +1124,20 @@ namespace BMA.EHR.DisciplineResult.Service.Controllers
/// ไม่ได้ Login เข้าระบบ
/// เมื่อเกิดข้อผิดพลาดในการทำงาน
[HttpPost("command20/report")]
- public async Task> PostReportcommand20([FromBody] ReportPersonRequest req)
+ public async Task> PostReportcommand20([FromBody] ReportPersonAndCommandRequest req)
{
var data = await _context.DisciplineDisciplinary_ProfileComplaintInvestigates
.Where(x => req.refIds.Contains(x.Id.ToString()))
.ToListAsync();
- data.ForEach(profile => profile.Status = req.status.Trim().ToUpper());
+ // data.ForEach(profile => profile.Status = req.status.Trim().ToUpper());
+ data.ForEach(profile =>
+ {
+ profile.Status = !string.IsNullOrEmpty(req.status)
+ ? req.status.Trim().ToUpper() : null;
+ profile.CommandTypeId = !string.IsNullOrEmpty(req.commandTypeId) && Guid.TryParse(req.commandTypeId, out var cmdTypeId)
+ ? cmdTypeId : null;
+ profile.CommandCode = req.commandCode ?? null;
+ });
await _context.SaveChangesAsync();
return Success();
}
@@ -1130,7 +1157,13 @@ namespace BMA.EHR.DisciplineResult.Service.Controllers
.Where(x => req.refIds.Contains(x.Id.ToString()))
// .Where(x => x.Status.ToUpper() == "REPORT")
.ToListAsync();
- data.ForEach(profile => profile.Status = "NEW");
+ // data.ForEach(profile => profile.Status = "NEW");
+ data.ForEach(profile =>
+ {
+ profile.Status = "NEW";
+ profile.CommandTypeId = null;
+ profile.CommandCode = null;
+ });
await _context.SaveChangesAsync();
return Success();
}
@@ -1150,6 +1183,11 @@ namespace BMA.EHR.DisciplineResult.Service.Controllers
.Include(x => x.DisciplineDisciplinary)
.Where(x => req.refIds.Select(x => x.refId).Contains(x.Id.ToString()))
.ToListAsync();
+
+ // Task #224 ปรับให้เป็น process ที่ควรบันทึกตามลำดับ
+ data.ForEach(profile => { profile.Status = "REPORTED"; profile.CommandTypeId = null; });
+ await _context.SaveChangesAsync();
+
var resultData = (from p in data
join r in req.refIds
on p.Id.ToString() equals r.refId
@@ -1198,8 +1236,8 @@ namespace BMA.EHR.DisciplineResult.Service.Controllers
var _result = await _res.Content.ReadAsStringAsync();
if (_res.IsSuccessStatusCode)
{
- // คำสั่งไล่ออก หรือ ปลดออก Status หลังออกคำสั่งใช้ "REPORTED" เพื่อไม่ให้ส่งรายชื่อไปออกคำสั่งซ้ำได้
- data.ForEach(profile => { profile.Status = "REPORTED"; profile.CommandTypeId = null; });
+ //// คำสั่งไล่ออก หรือ ปลดออก Status หลังออกคำสั่งใช้ "REPORTED" เพื่อไม่ให้ส่งรายชื่อไปออกคำสั่งซ้ำได้
+ // data.ForEach(profile => { profile.Status = "REPORTED"; profile.CommandTypeId = null; });
var _profile = new List();
DateTime _date = DateTime.Now;
foreach (var item in data)
@@ -1379,6 +1417,11 @@ namespace BMA.EHR.DisciplineResult.Service.Controllers
// .Where(x => x.Status == "REPORT")
.Where(x => req.refIds.Select(x => x.refId).Contains(x.Id.ToString()))
.ToListAsync();
+
+ // Task #224 ปรับให้เป็น process ที่ควรบันทึกตามลำดับ
+ data.ForEach(profile => profile.Status = "DONE");
+ await _context.SaveChangesAsync();
+
var resultData = (from p in data
join r in req.refIds
on p.Id.ToString() equals r.refId
@@ -1424,12 +1467,12 @@ namespace BMA.EHR.DisciplineResult.Service.Controllers
{
data = resultData,
});
- var _result = await _res.Content.ReadAsStringAsync();
- if (_res.IsSuccessStatusCode)
- {
- data.ForEach(profile => profile.Status = "DONE");
- await _context.SaveChangesAsync();
- }
+ //// var _result = await _res.Content.ReadAsStringAsync();
+ //// if (_res.IsSuccessStatusCode)
+ //// {
+ //// data.ForEach(profile => profile.Status = "DONE");
+ //// await _context.SaveChangesAsync();
+ //// }
}
return Success();
}
@@ -1518,6 +1561,11 @@ namespace BMA.EHR.DisciplineResult.Service.Controllers
// .Where(x => x.Status == "REPORT")
.Where(x => req.refIds.Select(x => x.refId).Contains(x.Id.ToString()))
.ToListAsync();
+
+ // Task #224 ปรับให้เป็น process ที่ควรบันทึกตามลำดับ
+ data.ForEach(profile => profile.Status = "DONE");
+ await _context.SaveChangesAsync();
+
var resultData = (from p in data
join r in req.refIds
on p.Id.ToString() equals r.refId
@@ -1563,12 +1611,12 @@ namespace BMA.EHR.DisciplineResult.Service.Controllers
{
data = resultData,
});
- var _result = await _res.Content.ReadAsStringAsync();
- if (_res.IsSuccessStatusCode)
- {
- data.ForEach(profile => profile.Status = "DONE");
- await _context.SaveChangesAsync();
- }
+ //// var _result = await _res.Content.ReadAsStringAsync();
+ //// if (_res.IsSuccessStatusCode)
+ //// {
+ //// data.ForEach(profile => profile.Status = "DONE");
+ //// await _context.SaveChangesAsync();
+ //// }
}
return Success();
}
@@ -1582,14 +1630,22 @@ namespace BMA.EHR.DisciplineResult.Service.Controllers
/// ไม่ได้ Login เข้าระบบ
/// เมื่อเกิดข้อผิดพลาดในการทำงาน
[HttpPost("command27/report")]
- public async Task> PostReportCommand27([FromBody] ReportPersonRequest req)
+ public async Task> PostReportCommand27([FromBody] ReportPersonAndCommandRequest req)
{
try
{
var data = await _context.DisciplineDisciplinary_ProfileComplaintInvestigates
.Where(x => req.refIds.Contains(x.Id.ToString()))
.ToListAsync();
- data.ForEach(profile => profile.Status = req.status.Trim().ToUpper());
+ // data.ForEach(profile => profile.Status = req.status.Trim().ToUpper());
+ data.ForEach(profile =>
+ {
+ profile.Status = !string.IsNullOrEmpty(req.status)
+ ? req.status.Trim().ToUpper() : null;
+ profile.CommandTypeId = !string.IsNullOrEmpty(req.commandTypeId) && Guid.TryParse(req.commandTypeId, out var cmdTypeId)
+ ? cmdTypeId : null;
+ profile.CommandCode = req.commandCode ?? null;
+ });
await _context.SaveChangesAsync();
return Success();
}
@@ -1616,7 +1672,13 @@ namespace BMA.EHR.DisciplineResult.Service.Controllers
.Where(x => req.refIds.Contains(x.Id.ToString()))
// .Where(x => x.Status.ToUpper() == "REPORT")
.ToListAsync();
- data.ForEach(profile => profile.Status = "NEW");
+ // data.ForEach(profile => profile.Status = "NEW");
+ data.ForEach(profile =>
+ {
+ profile.Status = "NEW";
+ profile.CommandTypeId = null;
+ profile.CommandCode = null;
+ });
await _context.SaveChangesAsync();
return Success();
}
@@ -1641,6 +1703,11 @@ namespace BMA.EHR.DisciplineResult.Service.Controllers
.Include(x => x.DisciplineDisciplinary)
.Where(x => req.refIds.Select(x => x.refId).Contains(x.Id.ToString()))
.ToListAsync();
+
+ // Task #224 ปรับให้เป็น process ที่ควรบันทึกตามลำดับ
+ data.ForEach(profile => { profile.Status = "NEW"; profile.CommandTypeId = null; });
+ await _context.SaveChangesAsync();
+
string? _null = null;
var resultData = (from p in data
join r in req.refIds
@@ -1686,12 +1753,12 @@ namespace BMA.EHR.DisciplineResult.Service.Controllers
{
data = resultData,
});
- var _result = await _res.Content.ReadAsStringAsync();
- if (_res.IsSuccessStatusCode)
- {
- data.ForEach(profile => { profile.Status = "NEW"; profile.CommandTypeId = null; });
- await _context.SaveChangesAsync();
- }
+ //// var _result = await _res.Content.ReadAsStringAsync();
+ //// if (_res.IsSuccessStatusCode)
+ //// {
+ //// data.ForEach(profile => { profile.Status = "NEW"; profile.CommandTypeId = null; });
+ //// await _context.SaveChangesAsync();
+ //// }
}
return Success();
}
@@ -1705,14 +1772,22 @@ namespace BMA.EHR.DisciplineResult.Service.Controllers
/// ไม่ได้ Login เข้าระบบ
/// เมื่อเกิดข้อผิดพลาดในการทำงาน
[HttpPost("command28/report")]
- public async Task> PostReportCommand28([FromBody] ReportPersonRequest req)
+ public async Task> PostReportCommand28([FromBody] ReportPersonAndCommandRequest req)
{
try
{
var data = await _context.DisciplineDisciplinary_ProfileComplaintInvestigates
.Where(x => req.refIds.Contains(x.Id.ToString()))
.ToListAsync();
- data.ForEach(profile => profile.Status = req.status.Trim().ToUpper());
+ // data.ForEach(profile => profile.Status = req.status.Trim().ToUpper());
+ data.ForEach(profile =>
+ {
+ profile.Status = !string.IsNullOrEmpty(req.status)
+ ? req.status.Trim().ToUpper() : null;
+ profile.CommandTypeId = !string.IsNullOrEmpty(req.commandTypeId) && Guid.TryParse(req.commandTypeId, out var cmdTypeId)
+ ? cmdTypeId : null;
+ profile.CommandCode = req.commandCode ?? null;
+ });
await _context.SaveChangesAsync();
return Success();
}
@@ -1739,7 +1814,13 @@ namespace BMA.EHR.DisciplineResult.Service.Controllers
.Where(x => req.refIds.Contains(x.Id.ToString()))
// .Where(x => x.Status.ToUpper() == "REPORT")
.ToListAsync();
- data.ForEach(profile => profile.Status = "NEW");
+ // data.ForEach(profile => profile.Status = "NEW");
+ data.ForEach(profile =>
+ {
+ profile.Status = "NEW";
+ profile.CommandTypeId = null;
+ profile.CommandCode = null;
+ });
await _context.SaveChangesAsync();
return Success();
}
@@ -1764,6 +1845,11 @@ namespace BMA.EHR.DisciplineResult.Service.Controllers
.Include(x => x.DisciplineDisciplinary)
.Where(x => req.refIds.Select(x => x.refId).Contains(x.Id.ToString()))
.ToListAsync();
+
+ // Task #224 ปรับให้เป็น process ที่ควรบันทึกตามลำดับ
+ data.ForEach(profile => { profile.Status = "NEW"; profile.CommandTypeId = null; });
+ await _context.SaveChangesAsync();
+
string? _null = null;
var resultData = (from p in data
join r in req.refIds
@@ -1809,12 +1895,12 @@ namespace BMA.EHR.DisciplineResult.Service.Controllers
{
data = resultData,
});
- var _result = await _res.Content.ReadAsStringAsync();
- if (_res.IsSuccessStatusCode)
- {
- data.ForEach(profile => { profile.Status = "NEW"; profile.CommandTypeId = null; });
- await _context.SaveChangesAsync();
- }
+ //// var _result = await _res.Content.ReadAsStringAsync();
+ //// if (_res.IsSuccessStatusCode)
+ //// {
+ //// data.ForEach(profile => { profile.Status = "NEW"; profile.CommandTypeId = null; });
+ //// await _context.SaveChangesAsync();
+ //// }
}
return Success();
}
@@ -1828,14 +1914,22 @@ namespace BMA.EHR.DisciplineResult.Service.Controllers
/// ไม่ได้ Login เข้าระบบ
/// เมื่อเกิดข้อผิดพลาดในการทำงาน
[HttpPost("command29/report")]
- public async Task> PostReportCommand29([FromBody] ReportPersonRequest req)
+ public async Task> PostReportCommand29([FromBody] ReportPersonAndCommandRequest req)
{
try
{
var data = await _context.DisciplineDisciplinary_ProfileComplaintInvestigates
.Where(x => req.refIds.Contains(x.Id.ToString()))
.ToListAsync();
- data.ForEach(profile => profile.Status = req.status.Trim().ToUpper());
+ // data.ForEach(profile => profile.Status = req.status.Trim().ToUpper());
+ data.ForEach(profile =>
+ {
+ profile.Status = !string.IsNullOrEmpty(req.status)
+ ? req.status.Trim().ToUpper() : null;
+ profile.CommandTypeId = !string.IsNullOrEmpty(req.commandTypeId) && Guid.TryParse(req.commandTypeId, out var cmdTypeId)
+ ? cmdTypeId : null;
+ profile.CommandCode = req.commandCode ?? null;
+ });
await _context.SaveChangesAsync();
return Success();
}
@@ -1862,7 +1956,13 @@ namespace BMA.EHR.DisciplineResult.Service.Controllers
.Where(x => req.refIds.Contains(x.Id.ToString()))
// .Where(x => x.Status.ToUpper() == "REPORT")
.ToListAsync();
- data.ForEach(profile => profile.Status = "NEW");
+ // data.ForEach(profile => profile.Status = "NEW");
+ data.ForEach(profile =>
+ {
+ profile.Status = "NEW";
+ profile.CommandTypeId = null;
+ profile.CommandCode = null;
+ });
await _context.SaveChangesAsync();
return Success();
}
@@ -1887,6 +1987,11 @@ namespace BMA.EHR.DisciplineResult.Service.Controllers
.Include(x => x.DisciplineDisciplinary)
.Where(x => req.refIds.Select(x => x.refId).Contains(x.Id.ToString()))
.ToListAsync();
+
+ // Task #224 ปรับให้เป็น process ที่ควรบันทึกตามลำดับ
+ data.ForEach(profile => { profile.Status = "NEW"; profile.CommandTypeId = null; });
+ await _context.SaveChangesAsync();
+
string? _null = null;
var resultData = (from p in data
join r in req.refIds
@@ -1932,12 +2037,12 @@ namespace BMA.EHR.DisciplineResult.Service.Controllers
{
data = resultData,
});
- var _result = await _res.Content.ReadAsStringAsync();
- if (_res.IsSuccessStatusCode)
- {
- data.ForEach(profile => { profile.Status = "NEW"; profile.CommandTypeId = null; });
- await _context.SaveChangesAsync();
- }
+ //// var _result = await _res.Content.ReadAsStringAsync();
+ //// if (_res.IsSuccessStatusCode)
+ //// {
+ //// data.ForEach(profile => { profile.Status = "NEW"; profile.CommandTypeId = null; });
+ //// await _context.SaveChangesAsync();
+ //// }
}
return Success();
}
@@ -1951,14 +2056,22 @@ namespace BMA.EHR.DisciplineResult.Service.Controllers
/// ไม่ได้ Login เข้าระบบ
/// เมื่อเกิดข้อผิดพลาดในการทำงาน
[HttpPost("command30/report")]
- public async Task> PostReportCommand30([FromBody] ReportPersonRequest req)
+ public async Task> PostReportCommand30([FromBody] ReportPersonAndCommandRequest req)
{
try
{
var data = await _context.DisciplineDisciplinary_ProfileComplaintInvestigates
.Where(x => req.refIds.Contains(x.Id.ToString()))
.ToListAsync();
- data.ForEach(profile => profile.Status = req.status.Trim().ToUpper());
+ // data.ForEach(profile => profile.Status = req.status.Trim().ToUpper());
+ data.ForEach(profile =>
+ {
+ profile.Status = !string.IsNullOrEmpty(req.status)
+ ? req.status.Trim().ToUpper() : null;
+ profile.CommandTypeId = !string.IsNullOrEmpty(req.commandTypeId) && Guid.TryParse(req.commandTypeId, out var cmdTypeId)
+ ? cmdTypeId : null;
+ profile.CommandCode = req.commandCode ?? null;
+ });
await _context.SaveChangesAsync();
return Success();
}
@@ -1985,7 +2098,13 @@ namespace BMA.EHR.DisciplineResult.Service.Controllers
.Where(x => req.refIds.Contains(x.Id.ToString()))
// .Where(x => x.Status.ToUpper() == "REPORT")
.ToListAsync();
- data.ForEach(profile => profile.Status = "NEW");
+ // data.ForEach(profile => profile.Status = "NEW");
+ data.ForEach(profile =>
+ {
+ profile.Status = "NEW";
+ profile.CommandTypeId = null;
+ profile.CommandCode = null;
+ });
await _context.SaveChangesAsync();
return Success();
}
@@ -2010,6 +2129,11 @@ namespace BMA.EHR.DisciplineResult.Service.Controllers
.Include(x => x.DisciplineDisciplinary)
.Where(x => req.refIds.Select(x => x.refId).Contains(x.Id.ToString()))
.ToListAsync();
+
+ // Task #224 ปรับให้เป็น process ที่ควรบันทึกตามลำดับ
+ data.ForEach(profile => { profile.Status = "NEW"; profile.CommandTypeId = null; });
+ await _context.SaveChangesAsync();
+
string? _null = null;
var resultData = (from p in data
join r in req.refIds
@@ -2055,12 +2179,12 @@ namespace BMA.EHR.DisciplineResult.Service.Controllers
{
data = resultData,
});
- var _result = await _res.Content.ReadAsStringAsync();
- if (_res.IsSuccessStatusCode)
- {
- data.ForEach(profile => { profile.Status = "NEW"; profile.CommandTypeId = null; });
- await _context.SaveChangesAsync();
- }
+ //// var _result = await _res.Content.ReadAsStringAsync();
+ //// if (_res.IsSuccessStatusCode)
+ //// {
+ //// data.ForEach(profile => { profile.Status = "NEW"; profile.CommandTypeId = null; });
+ //// await _context.SaveChangesAsync();
+ //// }
}
return Success();
}
@@ -2074,14 +2198,22 @@ namespace BMA.EHR.DisciplineResult.Service.Controllers
/// ไม่ได้ Login เข้าระบบ
/// เมื่อเกิดข้อผิดพลาดในการทำงาน
[HttpPost("command31/report")]
- public async Task> PostReportCommand31([FromBody] ReportPersonRequest req)
+ public async Task> PostReportCommand31([FromBody] ReportPersonAndCommandRequest req)
{
try
{
var data = await _context.DisciplineDisciplinary_ProfileComplaintInvestigates
.Where(x => req.refIds.Contains(x.Id.ToString()))
.ToListAsync();
- data.ForEach(profile => profile.Status = req.status.Trim().ToUpper());
+ // data.ForEach(profile => profile.Status = req.status.Trim().ToUpper());
+ data.ForEach(profile =>
+ {
+ profile.Status = !string.IsNullOrEmpty(req.status)
+ ? req.status.Trim().ToUpper() : null;
+ profile.CommandTypeId = !string.IsNullOrEmpty(req.commandTypeId) && Guid.TryParse(req.commandTypeId, out var cmdTypeId)
+ ? cmdTypeId : null;
+ profile.CommandCode = req.commandCode ?? null;
+ });
await _context.SaveChangesAsync();
return Success();
}
@@ -2108,7 +2240,13 @@ namespace BMA.EHR.DisciplineResult.Service.Controllers
.Where(x => req.refIds.Contains(x.Id.ToString()))
// .Where(x => x.Status.ToUpper() == "REPORT")
.ToListAsync();
- data.ForEach(profile => profile.Status = "NEW");
+ // data.ForEach(profile => profile.Status = "NEW");
+ data.ForEach(profile =>
+ {
+ profile.Status = "NEW";
+ profile.CommandTypeId = null;
+ profile.CommandCode = null;
+ });
await _context.SaveChangesAsync();
return Success();
}
@@ -2133,6 +2271,11 @@ namespace BMA.EHR.DisciplineResult.Service.Controllers
.Include(x => x.DisciplineDisciplinary)
.Where(x => req.refIds.Select(x => x.refId).Contains(x.Id.ToString()))
.ToListAsync();
+
+ // Task #224 ปรับให้เป็น process ที่ควรบันทึกตามลำดับ
+ data.ForEach(profile => { profile.Status = "NEW"; profile.CommandTypeId = null; });
+ await _context.SaveChangesAsync();
+
string? _null = null;
var resultData = (from p in data
join r in req.refIds
@@ -2178,12 +2321,12 @@ namespace BMA.EHR.DisciplineResult.Service.Controllers
{
data = resultData,
});
- var _result = await _res.Content.ReadAsStringAsync();
- if (_res.IsSuccessStatusCode)
- {
- data.ForEach(profile => { profile.Status = "NEW"; profile.CommandTypeId = null; });
- await _context.SaveChangesAsync();
- }
+ //// var _result = await _res.Content.ReadAsStringAsync();
+ //// if (_res.IsSuccessStatusCode)
+ //// {
+ //// data.ForEach(profile => { profile.Status = "NEW"; profile.CommandTypeId = null; });
+ //// await _context.SaveChangesAsync();
+ //// }
}
return Success();
}
@@ -2197,7 +2340,7 @@ namespace BMA.EHR.DisciplineResult.Service.Controllers
/// ไม่ได้ Login เข้าระบบ
/// เมื่อเกิดข้อผิดพลาดในการทำงาน
[HttpPost("command32/report")]
- public async Task> PostReportCommand32([FromBody] ReportPersonRequest req)
+ public async Task> PostReportCommand32([FromBody] ReportPersonAndCommandRequest req)
{
try
{
@@ -2210,7 +2353,15 @@ namespace BMA.EHR.DisciplineResult.Service.Controllers
.Where(x => req.refIds.Contains(x.Id.ToString()))
.ToListAsync();
- data2.ForEach(profile => profile.IsReport = req.status.Trim().ToUpper());
+ // data2.ForEach(profile => profile.IsReport = req.status.Trim().ToUpper());
+ data2.ForEach(profile =>
+ {
+ profile.Status = !string.IsNullOrEmpty(req.status)
+ ? req.status.Trim().ToUpper() : null;
+ profile.CommandTypeId = !string.IsNullOrEmpty(req.commandTypeId) && Guid.TryParse(req.commandTypeId, out var cmdTypeId)
+ ? cmdTypeId : null;
+ profile.CommandCode = req.commandCode ?? null;
+ });
await _context.SaveChangesAsync();
return Success();
@@ -2245,7 +2396,13 @@ namespace BMA.EHR.DisciplineResult.Service.Controllers
// .Where(x => x.IsReport == "REPORT")
.ToListAsync();
- data2.ForEach(profile => profile.IsReport = "NEW");
+ // data2.ForEach(profile => profile.IsReport = "NEW");
+ data2.ForEach(profile =>
+ {
+ profile.Status = "NEW";
+ profile.CommandTypeId = null;
+ profile.CommandCode = null;
+ });
await _context.SaveChangesAsync();
return Success();
@@ -2272,6 +2429,11 @@ namespace BMA.EHR.DisciplineResult.Service.Controllers
// .Where(x => x.IsReport == "REPORT")
.Where(x => req.refIds.Select(x => x.refId).Contains(x.Id.ToString()))
.ToListAsync();
+
+ // Task #224 ปรับให้เป็น process ที่ควรบันทึกตามลำดับ
+ data.ForEach(profile => profile.IsReport = "DONE");
+ await _context.SaveChangesAsync();
+
string? _null = null;
var resultData = (from p in data
join r in req.refIds
@@ -2317,12 +2479,12 @@ namespace BMA.EHR.DisciplineResult.Service.Controllers
{
data = resultData,
});
- var _result = await _res.Content.ReadAsStringAsync();
- if (_res.IsSuccessStatusCode)
- {
- data.ForEach(profile => profile.IsReport = "DONE");
- await _context.SaveChangesAsync();
- }
+ //// var _result = await _res.Content.ReadAsStringAsync();
+ //// if (_res.IsSuccessStatusCode)
+ //// {
+ //// data.ForEach(profile => profile.IsReport = "DONE");
+ //// await _context.SaveChangesAsync();
+ //// }
}
var data1 = await _context.DisciplineDisciplinary_ProfileComplaintInvestigates
diff --git a/BMA.EHR.Domain/Common/BaseController.cs b/BMA.EHR.Domain/Common/BaseController.cs
index 26f71bf5..0b382d95 100644
--- a/BMA.EHR.Domain/Common/BaseController.cs
+++ b/BMA.EHR.Domain/Common/BaseController.cs
@@ -95,6 +95,9 @@ namespace BMA.EHR.Domain.Common
protected Guid? ProfileId => User.GetProfileId();
protected string? Prefix => User.GetPrefix();
protected string? FullNameFromClaim => User.GetName();
+
+ protected string? FirstName => User.GetFirstName();
+ protected string? LastName => User.GetLastName();
#endregion
diff --git a/BMA.EHR.Domain/Extensions/ClaimsPrincipalExtensions.cs b/BMA.EHR.Domain/Extensions/ClaimsPrincipalExtensions.cs
index 26a7c189..cc44f8a1 100644
--- a/BMA.EHR.Domain/Extensions/ClaimsPrincipalExtensions.cs
+++ b/BMA.EHR.Domain/Extensions/ClaimsPrincipalExtensions.cs
@@ -26,5 +26,7 @@ namespace BMA.EHR.Domain.Extensions
public static Guid? GetProfileId(this ClaimsPrincipal user) => user.GetGuidClaim(BmaClaimTypes.ProfileId);
public static string? GetPrefix(this ClaimsPrincipal user) => user.GetClaimValue(BmaClaimTypes.Prefix);
public static string? GetName(this ClaimsPrincipal user) => user.GetClaimValue(BmaClaimTypes.Name);
+ public static string? GetFirstName(this ClaimsPrincipal user) => user.GetClaimValue(BmaClaimTypes.GivenName);
+ public static string? GetLastName(this ClaimsPrincipal user) => user.GetClaimValue(BmaClaimTypes.FamilyName);
}
}
diff --git a/BMA.EHR.Domain/Models/Leave/Requests/LeaveBeginning.cs b/BMA.EHR.Domain/Models/Leave/Requests/LeaveBeginning.cs
index 153b7d22..0bb9a1c1 100644
--- a/BMA.EHR.Domain/Models/Leave/Requests/LeaveBeginning.cs
+++ b/BMA.EHR.Domain/Models/Leave/Requests/LeaveBeginning.cs
@@ -27,11 +27,11 @@ namespace BMA.EHR.Domain.Models.Leave.Requests
[Required, Comment("จำนวนวันลาทั้งหมด")]
public double LeaveDays { get; set; } = 0.0;
- [Required, Comment("จำนวนวันลาที่ใช้ไป")]
- public double LeaveDaysUsed { get; set; } = 0.0;
+ [Comment("จำนวนวันลาที่ใช้ไป")]
+ public double? LeaveDaysUsed { get; set; } = 0.0;
[Comment("จำนวนครั้งที่ลาสะสม")]
- public int LeaveCount { get; set; } = 0;
+ public int? LeaveCount { get; set; } = 0;
public Guid? RootDnaId { get; set; }
diff --git a/BMA.EHR.Domain/Models/Leave/Requests/LeaveRequest.cs b/BMA.EHR.Domain/Models/Leave/Requests/LeaveRequest.cs
index 69088cbb..f6c9ce2d 100644
--- a/BMA.EHR.Domain/Models/Leave/Requests/LeaveRequest.cs
+++ b/BMA.EHR.Domain/Models/Leave/Requests/LeaveRequest.cs
@@ -210,5 +210,7 @@ namespace BMA.EHR.Domain.Models.Leave.Requests
public Guid? Child4DnaId { get; set; } = Guid.Empty;
+ public DateTime? DateSendLeave { get; set; }
+
}
}
diff --git a/BMA.EHR.Domain/Models/Leave/Requests/LeaveRequestApprover.cs b/BMA.EHR.Domain/Models/Leave/Requests/LeaveRequestApprover.cs
index f974d004..0a46c199 100644
--- a/BMA.EHR.Domain/Models/Leave/Requests/LeaveRequestApprover.cs
+++ b/BMA.EHR.Domain/Models/Leave/Requests/LeaveRequestApprover.cs
@@ -38,5 +38,10 @@ namespace BMA.EHR.Domain.Models.Leave.Requests
public string Comment { get; set; } = string.Empty;
public string? ApproveType { get; set; } = string.Empty; // ผู้บังคับบัญชา = commander, ผู้มีอำนาจอนุมัติ = Approver
+
+
+ public bool IsAct { get; set; } = false;
+
+ public string KeyId { get; set; } = string.Empty;
}
}
diff --git a/BMA.EHR.Domain/Models/Placement/PlacementAppointment.cs b/BMA.EHR.Domain/Models/Placement/PlacementAppointment.cs
index 870a7817..474dcb69 100644
--- a/BMA.EHR.Domain/Models/Placement/PlacementAppointment.cs
+++ b/BMA.EHR.Domain/Models/Placement/PlacementAppointment.cs
@@ -119,6 +119,10 @@ namespace BMA.EHR.Domain.Models.Placement
public string? position { get; set; }
[Comment("ตำแหน่งทางการบริหาร")]
public string? PositionExecutive { get; set; }
+
+ [Comment("id ตำแหน่งทางการบริหาร")]
+ public string? posExecutiveId { get; set; }
+
[Comment("id ประเภทตำแหน่ง")]
public string? posTypeId { get; set; }
[Comment("ชื่อประเภทตำแหน่ง")]
diff --git a/BMA.EHR.Domain/Models/Placement/PlacementProfile.cs b/BMA.EHR.Domain/Models/Placement/PlacementProfile.cs
index 5a94e63b..c660c8ca 100644
--- a/BMA.EHR.Domain/Models/Placement/PlacementProfile.cs
+++ b/BMA.EHR.Domain/Models/Placement/PlacementProfile.cs
@@ -321,6 +321,10 @@ namespace BMA.EHR.Domain.Models.Placement
public string? positionName { get; set; }
[Comment("ตำแหน่งทางการบริหาร")]
public string? PositionExecutive { get; set; }
+
+ [Comment("id ตำแหน่งทางการบริหาร")]
+ public string? posExecutiveId { get; set; }
+
[Comment("สายงาน")]
public string? positionField { get; set; }
[Comment("id ประเภทตำแหน่ง")]
diff --git a/BMA.EHR.Domain/Models/Placement/PlacementReceive.cs b/BMA.EHR.Domain/Models/Placement/PlacementReceive.cs
index 9d366d19..3b1b625c 100644
--- a/BMA.EHR.Domain/Models/Placement/PlacementReceive.cs
+++ b/BMA.EHR.Domain/Models/Placement/PlacementReceive.cs
@@ -64,6 +64,10 @@ namespace BMA.EHR.Domain.Models.Placement
public string? profileId { get; set; }
[Comment("คำนำหน้า")]
public string? prefix { get; set; }
+
+ [Comment("ยศ")]
+ public string? rank { get; set; }
+
[Comment("ชื่อ")]
public string? firstName { get; set; }
[Comment("นามสกุล")]
@@ -128,6 +132,10 @@ namespace BMA.EHR.Domain.Models.Placement
public string? position { get; set; }
[Comment("ตำแหน่งทางการบริหาร")]
public string? PositionExecutive { get; set; }
+
+ [Comment("id ตำแหน่งทางการบริหาร")]
+ public string? posExecutiveId { get; set; }
+
[Comment("id ประเภทตำแหน่ง")]
public string? posTypeId { get; set; }
[Comment("ชื่อประเภทตำแหน่ง")]
diff --git a/BMA.EHR.Domain/Models/Retirement/RetirementOther.cs b/BMA.EHR.Domain/Models/Retirement/RetirementOther.cs
index b37e47aa..c1f69ece 100644
--- a/BMA.EHR.Domain/Models/Retirement/RetirementOther.cs
+++ b/BMA.EHR.Domain/Models/Retirement/RetirementOther.cs
@@ -173,6 +173,10 @@ namespace BMA.EHR.Domain.Models.Retirement
public string? position { get; set; }
[Comment("ตำแหน่งทางการบริหาร")]
public string? PositionExecutive { get; set; }
+
+ [Comment("id ตำแหน่งทางการบริหาร")]
+ public string? posExecutiveId { get; set; }
+
[Comment("id ประเภทตำแหน่ง")]
public string? posTypeId { get; set; }
[Comment("ชื่อประเภทตำแหน่ง")]
diff --git a/BMA.EHR.Domain/Shared/PrivilegeConverter.cs b/BMA.EHR.Domain/Shared/PrivilegeConverter.cs
new file mode 100644
index 00000000..59f8168c
--- /dev/null
+++ b/BMA.EHR.Domain/Shared/PrivilegeConverter.cs
@@ -0,0 +1,30 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Newtonsoft.Json;
+
+namespace BMA.EHR.Domain.Shared
+{
+ public class PrivilegeConverter : JsonConverter
+{
+ public override bool CanConvert(Type objectType)
+ {
+ return objectType == typeof(string);
+ }
+
+ public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
+ {
+ if (reader.TokenType == JsonToken.Null)
+ {
+ return "EMPTY";
+ }
+ return reader.Value;
+ }
+
+ public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
+ {
+ writer.WriteValue(value);
+ }
+}
+}
\ No newline at end of file
diff --git a/BMA.EHR.Infrastructure/Migrations/20260512073417_update_PlacementReceives_add_rank.Designer.cs b/BMA.EHR.Infrastructure/Migrations/20260512073417_update_PlacementReceives_add_rank.Designer.cs
new file mode 100644
index 00000000..b65f0774
--- /dev/null
+++ b/BMA.EHR.Infrastructure/Migrations/20260512073417_update_PlacementReceives_add_rank.Designer.cs
@@ -0,0 +1,21250 @@
+//
+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
+{
+ [DbContext(typeof(ApplicationDBContext))]
+ [Migration("20260512073417_update_PlacementReceives_add_rank")]
+ partial class update_PlacementReceives_add_rank
+ {
+ ///
+ 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.Commands.Core.Command", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("char(36)")
+ .HasColumnOrder(0)
+ .HasComment("PrimaryKey")
+ .HasAnnotation("Relational:JsonPropertyName", "id");
+
+ b.Property("ActEndDate")
+ .HasColumnType("datetime(6)")
+ .HasComment("วันที่สิ้นสุดการรักษาการแทน");
+
+ b.Property("ActStartDate")
+ .HasColumnType("datetime(6)")
+ .HasComment("วันที่เริ่มรักษาการแทน");
+
+ b.Property("AuthorizedPosition")
+ .IsRequired()
+ .HasColumnType("longtext")
+ .HasComment("ตำแหน่งผู้มีอำนาจลงนาม");
+
+ b.Property("AuthorizedUserFullName")
+ .IsRequired()
+ .HasColumnType("longtext")
+ .HasComment("ชื่อผู้มีอำนาจลงนาม");
+
+ b.Property("AuthorizedUserId")
+ .HasColumnType("char(36)")
+ .HasComment("รหัสอ้างอิงผู้มีอำนาจลงนาม");
+
+ b.Property("CaseFault")
+ .HasColumnType("longtext")
+ .HasComment("กรณีความผิด");
+
+ b.Property("ChairManFullName")
+ .HasColumnType("longtext")
+ .HasComment("ประธานคณะกรรมการ");
+
+ b.Property("CommandAffectDate")
+ .HasColumnType("datetime(6)")
+ .HasComment("วันที่คำสั่งมีผล");
+
+ b.Property("CommandExcecuteDate")
+ .HasColumnType("datetime(6)")
+ .HasComment("วันที่ออกคำสั่ง");
+
+ b.Property("CommandNo")
+ .IsRequired()
+ .HasMaxLength(10)
+ .HasColumnType("varchar(10)")
+ .HasComment("เลขที่คำสั่ง");
+
+ b.Property("CommandStatusId")
+ .HasColumnType("char(36)")
+ .HasComment("รหัสอ้างอิงสถานะคำสั่ง");
+
+ b.Property("CommandSubject")
+ .IsRequired()
+ .HasMaxLength(500)
+ .HasColumnType("varchar(500)")
+ .HasComment("คำสั่งเรื่อง");
+
+ b.Property("CommandTypeId")
+ .HasColumnType("char(36)")
+ .HasComment("รหัสอ้างอิงประเภทคำสั่ง");
+
+ b.Property("CommandYear")
+ .IsRequired()
+ .HasMaxLength(4)
+ .HasColumnType("varchar(4)")
+ .HasComment("ปีที่ออกคำสั่ง");
+
+ b.Property("ComplaintId")
+ .HasColumnType("char(36)")
+ .HasComment("Id เรื่องร้องเรียน");
+
+ b.Property("ConclusionFireDate")
+ .HasColumnType("datetime(6)")
+ .HasComment("ลงวันที่ (เรื่องการดำเนินการทางวินัย)");
+
+ b.Property("ConclusionFireNo")
+ .HasColumnType("longtext")
+ .HasComment("ครั้งที่ (เรื่องการดำเนินการทางวินัย)");
+
+ b.Property("ConclusionFireResolution")
+ .HasColumnType("longtext")
+ .HasComment("มติที่ประชุม (เรื่องการดำเนินการทางวินัย)");
+
+ b.Property("ConclusionMeetingDate")
+ .HasColumnType("datetime(6)")
+ .HasComment("การประชุม ลงวันที่");
+
+ b.Property("ConclusionMeetingNo")
+ .HasColumnType("longtext")
+ .HasComment("การประชุม ครั้งที่");
+
+ b.Property("ConclusionReceiveDate")
+ .HasColumnType("datetime(6)")
+ .HasComment("ลงวันที่ (การรับโอน)");
+
+ b.Property("ConclusionReceiveNo")
+ .HasColumnType("longtext")
+ .HasComment("มติ กก. ครั้งที่ (การรับโอน)");
+
+ b.Property("ConclusionRegisterDate")
+ .HasColumnType("datetime(6)")
+ .HasComment("ลงวันที่ (เรื่อง รับสมัครสอบฯ)");
+
+ b.Property("ConclusionRegisterNo")
+ .HasColumnType("longtext")
+ .HasComment("มติ กก. ครั้งที่ (เรื่อง รับสมัครสอบฯ)");
+
+ b.Property("ConclusionResultDate")
+ .HasColumnType("datetime(6)")
+ .HasComment("ลงวันที่ (เรื่อง ผลการสอบแข่งขัน)");
+
+ b.Property("ConclusionResultNo")
+ .HasColumnType("longtext")
+ .HasComment("มติ กก. ครั้งที่ (เรื่อง ผลการสอบแข่งขัน)");
+
+ b.Property("ConclusionReturnDate")
+ .HasColumnType("datetime(6)")
+ .HasComment("ลงวันที่ (เรื่อง กลับเข้ารับราชการ)");
+
+ b.Property("ConclusionReturnNo")
+ .HasColumnType("longtext")
+ .HasComment("มติ กก. ครั้งที่ (เรื่อง กลับเข้ารับราชการ)");
+
+ b.Property("ConclusionTranferDate")
+ .HasColumnType("datetime(6)")
+ .HasComment("การประชุม ลงวันที่");
+
+ b.Property("ConclusionTranferNo")
+ .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("Fault")
+ .HasColumnType("longtext")
+ .HasComment("รายละเอียดการกระทำผิด");
+
+ b.Property("FaultLevel")
+ .HasColumnType("longtext")
+ .HasComment("ระดับความผิด");
+
+ b.Property("GovAidCommandDate")
+ .HasColumnType("datetime(6)")
+ .HasComment("ลงวันที่ (คำสั่งช่วยราชการ)");
+
+ b.Property("GovAidCommandNo")
+ .HasColumnType("longtext")
+ .HasComment("คำสั่งเลขที่ (คำสั่งช่วยราชการ)");
+
+ b.Property("GuiltyBasis")
+ .HasColumnType("longtext")
+ .HasComment("ฐานความผิด");
+
+ b.Property("IssuerOrganizationId")
+ .HasColumnType("char(36)")
+ .HasComment("รหัสอ้างอิงหน่วยงานที่ออกคำสั่ง");
+
+ b.Property("IssuerOrganizationName")
+ .IsRequired()
+ .HasColumnType("longtext")
+ .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("Member1FullName")
+ .HasColumnType("longtext")
+ .HasComment("กรรมการคนที่ 1");
+
+ b.Property("Member2FullName")
+ .HasColumnType("longtext")
+ .HasComment("กรรมการคนที่ 2");
+
+ b.Property("MilitaryCommanDate")
+ .HasColumnType("datetime(6)")
+ .HasComment("ลงวันที่ (ให้เข้ารับราชการทหาร)");
+
+ b.Property("MilitaryCommandNo")
+ .HasColumnType("longtext")
+ .HasComment("คำสั่งที่ (ให้เข้ารับราชการทหาร)");
+
+ b.Property("OwnerGovId")
+ .HasColumnType("char(36)")
+ .HasComment("รหัสส่วนราชการผู้ออกคำสั่ง");
+
+ b.Property("PlacementCommandDate")
+ .HasColumnType("datetime(6)")
+ .HasComment("คำสั่งบรรจุลงวันที่");
+
+ b.Property("PlacementCommandIssuer")
+ .HasColumnType("longtext")
+ .HasComment("หน่วยงานที่ออกคำสั่งบรรจุ");
+
+ b.Property("PlacementCommandNo")
+ .HasColumnType("longtext")
+ .HasComment("เลขที่คำสั่งบรรจุ");
+
+ b.Property("PlacementId")
+ .HasColumnType("char(36)")
+ .HasComment("อ้างอิงรอบการสอบ");
+
+ b.Property("PlacementOrganizationName")
+ .HasColumnType("longtext")
+ .HasComment("สังกัดที่บรรจุ");
+
+ b.Property("PlacementPositionName")
+ .HasColumnType("longtext")
+ .HasComment("ตำแหน่งที่บรรจุ");
+
+ b.Property("PositionName")
+ .HasColumnType("longtext")
+ .HasComment("ตำแหน่งที่บรรจุ");
+
+ b.Property("ProbationEndDate")
+ .HasColumnType("datetime(6)")
+ .HasComment("วันที่สิ้นสุดการทดลองปฏิบัติราชการ");
+
+ b.Property("ProbationStartDate")
+ .HasColumnType("datetime(6)")
+ .HasComment("วันที่เริ่มทดลองปฏิบัติราชการ");
+
+ b.Property("ReceiveOrganizationName")
+ .HasColumnType("longtext")
+ .HasComment("ส่วนราชการที่รับโอน");
+
+ b.Property("RefRaw")
+ .HasColumnType("longtext")
+ .HasComment("อ้างอิงมาตราตามกฏหมาย");
+
+ b.Property("Result")
+ .HasColumnType("longtext")
+ .HasComment("ผลดำเนินการพิจารณา");
+
+ b.Property("SalaryPeriod")
+ .HasColumnType("longtext")
+ .HasComment("รอบเงินเดือน");
+
+ b.Property("SalaryPeriodId")
+ .HasColumnType("char(36)")
+ .HasComment("Id เรื่องเงินเดือน");
+
+ b.Property("SourceOrganizationName")
+ .HasColumnType("longtext")
+ .HasComment("หน่วยงานต้นสังกัด ก่อนรับราชการทหาร");
+
+ b.Property("TransferOrganizationName")
+ .HasColumnType("longtext")
+ .HasComment("ส่วนราชการที่ให้โอน");
+
+ b.Property("Year")
+ .HasMaxLength(4)
+ .HasColumnType("varchar(4)")
+ .HasComment("ปีรอบเงินเดือน");
+
+ b.HasKey("Id");
+
+ b.HasIndex("CommandStatusId");
+
+ b.HasIndex("CommandTypeId");
+
+ b.HasIndex("PlacementId");
+
+ b.ToTable("Commands");
+ });
+
+ modelBuilder.Entity("BMA.EHR.Domain.Models.Commands.Core.CommandDeployment", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("char(36)")
+ .HasColumnOrder(0)
+ .HasComment("PrimaryKey")
+ .HasAnnotation("Relational:JsonPropertyName", "id");
+
+ b.Property("CitizenId")
+ .IsRequired()
+ .HasMaxLength(13)
+ .HasColumnType("varchar(13)")
+ .HasComment("เลขประจำตัวประชาชน");
+
+ b.Property("CommandId")
+ .HasColumnType("char(36)")
+ .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("FirstName")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("varchar(100)")
+ .HasComment("ชื่อ");
+
+ b.Property("IsSendInbox")
+ .HasColumnType("tinyint(1)")
+ .HasComment("ส่งกล่องข้อความหรือไม่?");
+
+ b.Property("IsSendMail")
+ .HasColumnType("tinyint(1)")
+ .HasComment("ส่งอีเมล์หรือไม่?");
+
+ b.Property("IsSendNotification")
+ .HasColumnType("tinyint(1)")
+ .HasComment("ส่งแจ้งเตือนหรือไม่?");
+
+ b.Property("LastName")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("varchar(100)")
+ .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("OrganizationName")
+ .IsRequired()
+ .HasColumnType("longtext")
+ .HasComment("ชื่อหน่วยงานของผู้รับสำเนาคำสั่ง");
+
+ b.Property("PositionName")
+ .IsRequired()
+ .HasColumnType("longtext")
+ .HasComment("ชื่อตำแหน่งของผู้รับสำเนาคำสั่ง");
+
+ b.Property("Prefix")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("varchar(50)")
+ .HasComment("คำนำหน้านาม");
+
+ b.Property("ReceiveUserId")
+ .IsRequired()
+ .HasColumnType("longtext")
+ .HasComment("รหัสอ้างอิงผู้ใช้งานระบบ");
+
+ b.Property("Sequence")
+ .HasColumnType("int")
+ .HasComment("ลำดับ");
+
+ b.HasKey("Id");
+
+ b.HasIndex("CommandId");
+
+ b.ToTable("CommandDeployments");
+ });
+
+ modelBuilder.Entity("BMA.EHR.Domain.Models.Commands.Core.CommandDocument", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("char(36)")
+ .HasColumnOrder(0)
+ .HasComment("PrimaryKey")
+ .HasAnnotation("Relational:JsonPropertyName", "id");
+
+ b.Property("Category")
+ .IsRequired()
+ .HasColumnType("longtext")
+ .HasComment("ประเภทเอกสาร");
+
+ b.Property("CommandId")
+ .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("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.HasKey("Id");
+
+ b.HasIndex("CommandId");
+
+ b.HasIndex("DocumentId");
+
+ b.ToTable("CommandDocuments");
+ });
+
+ modelBuilder.Entity("BMA.EHR.Domain.Models.Commands.Core.CommandReceiver", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("char(36)")
+ .HasColumnOrder(0)
+ .HasComment("PrimaryKey")
+ .HasAnnotation("Relational:JsonPropertyName", "id");
+
+ b.Property("Amount")
+ .HasColumnType("double")
+ .HasComment("เงินเดือน");
+
+ b.Property("BirthDate")
+ .HasMaxLength(40)
+ .HasColumnType("datetime(6)")
+ .HasComment("วันเกิด");
+
+ b.Property("CitizenId")
+ .IsRequired()
+ .HasMaxLength(13)
+ .HasColumnType("varchar(13)")
+ .HasComment("เลขประจำตัวประชาชน");
+
+ b.Property("CommandId")
+ .HasColumnType("char(36)")
+ .HasComment("รหัสอ้างอิงคำสั่ง");
+
+ b.Property("Comment")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasComment("หมายเหตุ");
+
+ b.Property("Comment2")
+ .IsRequired()
+ .HasColumnType("text")
+ .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("FirstName")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("varchar(100)")
+ .HasComment("ชื่อ");
+
+ b.Property("LastName")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("varchar(100)")
+ .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("MouthSalaryAmount")
+ .HasColumnType("double")
+ .HasComment("เงินค่าตอบแทนรายเดือน");
+
+ b.Property("Organization")
+ .HasColumnType("longtext")
+ .HasComment("ชื่อหน่วยงาน root");
+
+ b.Property("PositionLevel")
+ .HasColumnType("longtext")
+ .HasComment("ระดับ");
+
+ b.Property("PositionName")
+ .HasColumnType("longtext")
+ .HasComment("ตำแหน่ง");
+
+ b.Property("PositionNumber")
+ .HasColumnType("longtext")
+ .HasComment("เลขที่ตำแหน่ง");
+
+ b.Property("PositionSalaryAmount")
+ .HasColumnType("double")
+ .HasComment("เงินประจำตำแหน่ง");
+
+ b.Property("PositionType")
+ .HasColumnType("longtext")
+ .HasComment("ประเภท");
+
+ b.Property("Prefix")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("varchar(50)")
+ .HasComment("คำนำหน้านาม");
+
+ b.Property("RefDisciplineId")
+ .HasColumnType("char(36)")
+ .HasComment("รหัสอ้างอิงไปยังข้อมูลวินัย");
+
+ b.Property("RefPlacementProfileId")
+ .HasColumnType("char(36)")
+ .HasComment("รหัสอ้างอิงไปยังข้อมูลผู้บรรจุ");
+
+ b.Property("Sequence")
+ .HasColumnType("int")
+ .HasComment("ลำดับในบัญชีแนบท้าย");
+
+ b.HasKey("Id");
+
+ b.HasIndex("CommandId");
+
+ b.ToTable("CommandReceivers");
+ });
+
+ modelBuilder.Entity("BMA.EHR.Domain.Models.Commands.Core.CommandStatus", 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("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("Name")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("varchar(100)")
+ .HasComment("สถานะของคำสั่ง");
+
+ b.Property("Sequence")
+ .HasColumnType("int")
+ .HasComment("ลำดับการทำงาน");
+
+ b.HasKey("Id");
+
+ b.ToTable("CommandStatuses");
+ });
+
+ modelBuilder.Entity("BMA.EHR.Domain.Models.Commands.Core.CommandType", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("char(36)")
+ .HasColumnOrder(0)
+ .HasComment("PrimaryKey")
+ .HasAnnotation("Relational:JsonPropertyName", "id");
+
+ b.Property("Category")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("varchar(100)")
+ .HasComment("ประเภทคำสั่ง");
+
+ b.Property("CommandCode")
+ .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("Name")
+ .IsRequired()
+ .HasMaxLength(200)
+ .HasColumnType("varchar(200)")
+ .HasComment("ชื่อคำสั่ง");
+
+ b.HasKey("Id");
+
+ b.ToTable("CommandTypes");
+ });
+
+ modelBuilder.Entity("BMA.EHR.Domain.Models.Commands.Core.DeploymentChannel", b =>
+ {
+ b.Property