using BMA.EHR.Application.Repositories; using BMA.EHR.Application.Repositories.Commands; using BMA.EHR.Application.Repositories.Leaves.LeaveRequests; using BMA.EHR.Application.Repositories.Leaves.TimeAttendants; using BMA.EHR.Application.Repositories.MessageQueue; using BMA.EHR.Application.Responses.Leaves; using BMA.EHR.Application.Responses.Profiles; using BMA.EHR.Domain.Common; using BMA.EHR.Domain.Extensions; using BMA.EHR.Domain.Models.Leave.TimeAttendants; using BMA.EHR.Domain.Models.Notifications; using BMA.EHR.Domain.Shared; using BMA.EHR.Infrastructure.Persistence; using BMA.EHR.Leave.Service.DTOs.AdditionalCheck; using BMA.EHR.Leave.Service.DTOs.Calendar; using BMA.EHR.Leave.Service.DTOs.ChangeRound; using BMA.EHR.Leave.Service.DTOs.CheckIn; using BMA.EHR.Leave.Service.DTOs.DutyTime; using BMA.EHR.Leave.Service.DTOs.LeaveRequest; using iTextSharp.text; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.ObjectPool; using Nest; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using RabbitMQ.Client; using RabbitMQ.Client.Events; using Swashbuckle.AspNetCore.Annotations; using System.ComponentModel.DataAnnotations; using System.Diagnostics; using System.Security.Claims; using System.Text; using System.Threading.Tasks; using SearchProfileResultDto = BMA.EHR.Leave.Service.DTOs.ChangeRound.SearchProfileResultDto; namespace BMA.EHR.Leave.Service.Controllers { [Route("api/v{version:apiVersion}/leave")] [ApiVersion("1.0")] [ApiController] [Produces("application/json")] [Authorize] [SwaggerTag("API ระบบลงเวลาและการลา")] public class LeaveController : BaseController { #region " Fields " private readonly DutyTimeRepository _dutyTimeRepository; private readonly LeaveDbContext _context; private readonly ApplicationDBContext _appDbContext; private readonly IHttpContextAccessor _httpContextAccessor; private readonly IWebHostEnvironment _hostingEnvironment; private readonly IConfiguration _configuration; private readonly UserProfileRepository _userProfileRepository; private readonly UserTimeStampRepository _userTimeStampRepository; private readonly MinIOService _minIOService; private readonly ProcessUserTimeStampRepository _processUserTimeStampRepository; private readonly UserDutyTimeRepository _userDutyTimeRepository; private readonly AdditionalCheckRequestRepository _additionalCheckRequestRepository; private readonly LeaveRequestRepository _leaveRequestRepository; private readonly UserCalendarRepository _userCalendarRepository; private readonly PermissionRepository _permission; private readonly CheckInJobStatusRepository _checkInJobStatusRepository; private readonly CommandRepository _commandRepository; private readonly NotificationRepository _notificationRepository; private readonly string _bucketName = "check-in"; private readonly ObjectPool _objectPool; private readonly string _fakeCheckInQueue = "fake-bma-checkin-queue"; private readonly string _realCheckInQueue = "bma-checkin-queue"; private readonly HttpClient _httpClient; private readonly LeaveProcessJobStatusRepository _leaveProcessJobStatusRepository; #endregion #region " Constuctor and Destructor " public LeaveController(DutyTimeRepository dutyTimeRepository, LeaveDbContext context, IHttpContextAccessor httpContextAccessor, IWebHostEnvironment hostingEnvironment, IConfiguration configuration, UserProfileRepository userProfileRepository, UserTimeStampRepository userTimeStampRepository, MinIOService minIOService, ProcessUserTimeStampRepository processUserTimeStampRepository, UserDutyTimeRepository userDutyTimeRepository, AdditionalCheckRequestRepository additionalCheckRequestRepository, UserCalendarRepository userCalendarRepository, CommandRepository commandRepository, LeaveRequestRepository leaveRequestRepository, ObjectPool objectPool, PermissionRepository permission, NotificationRepository notificationRepository, CheckInJobStatusRepository checkInJobStatusRepository, HttpClient httpClient, ApplicationDBContext appDbContext, LeaveProcessJobStatusRepository leaveProcessJobStatusRepository) { _dutyTimeRepository = dutyTimeRepository; _context = context; _appDbContext = appDbContext; _httpContextAccessor = httpContextAccessor; _hostingEnvironment = hostingEnvironment; _configuration = configuration; _userProfileRepository = userProfileRepository; _userTimeStampRepository = userTimeStampRepository; _minIOService = minIOService; _processUserTimeStampRepository = processUserTimeStampRepository; _userDutyTimeRepository = userDutyTimeRepository; _additionalCheckRequestRepository = additionalCheckRequestRepository; _userCalendarRepository = userCalendarRepository; _commandRepository = commandRepository; _leaveRequestRepository = leaveRequestRepository; _notificationRepository = notificationRepository; _checkInJobStatusRepository = checkInJobStatusRepository; _leaveProcessJobStatusRepository = leaveProcessJobStatusRepository; _objectPool = objectPool; _permission = permission; _httpClient = httpClient; var authString = $"{_configuration["Rabbit:User"] ?? ""}:{_configuration["Rabbit:Password"] ?? ""}"; var authToken = Convert.ToBase64String(Encoding.ASCII.GetBytes(authString)); _httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", authToken); } #endregion #region " Properties " private string? UserId => _httpContextAccessor?.HttpContext?.User?.FindFirst(ClaimTypes.NameIdentifier)?.Value; private string? FullName => _httpContextAccessor?.HttpContext?.User?.FindFirst("name")?.Value; private bool? PlacementAdmin => _httpContextAccessor?.HttpContext?.User?.IsInRole("placement1"); private string? AccessToken => _httpContextAccessor?.HttpContext?.Request.Headers["Authorization"]; private Guid OcId { get { // First try to get from claims var ocIdFromClaims = OrgRootDnaId; if (ocIdFromClaims.HasValue && ocIdFromClaims.Value != Guid.Empty) return ocIdFromClaims.Value; // Fallback to API call for backward compatibility if (UserId != null && UserId != "") return _userProfileRepository.GetUserOCId(Guid.Parse(UserId!), AccessToken); else return Guid.Empty; } } #endregion #region " Methods " #region " Duty Time รอบการทำงาน " /// /// LV1_004 - ข้อมูลทั้งหมดของรอบการปฏิบัติงาน (ADMIN) /// /// /// /// เมื่อทำรายการสำเร็จ /// ไม่ได้ Login เข้าระบบ /// เมื่อเกิดข้อผิดพลาดในการทำงาน [HttpGet("duty-time")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] public async Task> GetAllAsync() { var getPermission = await _permission.GetPermissionAPIAsync("LIST", "SYS_WORK_ROUND"); var jsonData = JsonConvert.DeserializeObject(getPermission); if (jsonData["status"]?.ToString() != "200") { return Error(jsonData["message"]?.ToString(), StatusCodes.Status403Forbidden); } var data = await _dutyTimeRepository.GetAllAsync(); return Success(data); } /// /// ข้อมูลของรอบการปฏิบัติงาน (ADMIN) /// /// /// /// เมื่อทำรายการสำเร็จ /// ไม่ได้ Login เข้าระบบ /// เมื่อเกิดข้อผิดพลาดในการทำงาน [HttpGet("duty-time/{id:guid}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] public async Task> GetByIdAsync(Guid id) { var data = await _dutyTimeRepository.GetByIdAsync(id); return Success(data); } /// /// LV1_001 - สร้างรอบการปฏิบัติงาน (ADMIN) /// /// /// /// เมื่อทำรายการสำเร็จ /// ไม่ได้ Login เข้าระบบ /// เมื่อเกิดข้อผิดพลาดในการทำงาน [HttpPost("duty-time")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] public async Task> PostAsync([FromBody] CreateDutyTimeDto data) { var getPermission = await _permission.GetPermissionAPIAsync("CREATE", "SYS_WORK_ROUND"); var jsonData = JsonConvert.DeserializeObject(getPermission); if (jsonData["status"]?.ToString() != "200") { return Error(jsonData["message"]?.ToString(), StatusCodes.Status403Forbidden); } // validate var startMorning = TimeOnly.Parse(data.StartTimeMorning); var endMorning = TimeOnly.Parse(data.EndTimeMorning); var startAfternoon = TimeOnly.Parse(data.StartTimeAfternoon); var endAfternoon = TimeOnly.Parse(data.EndTimeAfternoon); if (startMorning >= endMorning) { throw new Exception(GlobalMessages.StartTimeGreaterEnd); } if (startAfternoon >= endAfternoon) { throw new Exception(GlobalMessages.StartTimeGreaterEnd); } var oldData = await _dutyTimeRepository.GetAllAsync(); if (oldData == null || oldData.Count == 0) { var inserted = new DutyTime { Id = Guid.NewGuid(), Description = data.Description ?? "", StartTimeMorning = data.StartTimeMorning, EndTimeMorning = data.EndTimeMorning, StartTimeAfternoon = data.StartTimeAfternoon, EndTimeAfternoon = data.EndTimeAfternoon, IsActive = data.IsActive, IsDefault = true, }; var ret = await _dutyTimeRepository.AddAsync(inserted); return Success(ret); } else { if (data.IsDefault) { foreach (var d in oldData) { d.IsDefault = false; await _dutyTimeRepository.UpdateAsync(d); } } var inserted = new DutyTime { Id = Guid.NewGuid(), Description = data.Description ?? "", StartTimeMorning = data.StartTimeMorning, EndTimeMorning = data.EndTimeMorning, StartTimeAfternoon = data.StartTimeAfternoon, EndTimeAfternoon = data.EndTimeAfternoon, IsActive = data.IsActive, IsDefault = data.IsDefault, }; var ret = await _dutyTimeRepository.AddAsync(inserted); return Success(ret); } } /// /// LV1_002 - แก้ไขรอบการปฏิบัติงาน (ADMIN) /// /// /// /// เมื่อทำรายการสำเร็จ /// ไม่ได้ Login เข้าระบบ /// เมื่อเกิดข้อผิดพลาดในการทำงาน [HttpPut("duty-time/{id:guid}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] public async Task> PutAsync(Guid id, [FromBody] UpdateDutyTimeDto data) { var getPermission = await _permission.GetPermissionAPIAsync("UPDATE", "SYS_WORK_ROUND"); var jsonData = JsonConvert.DeserializeObject(getPermission); if (jsonData["status"]?.ToString() != "200") { return Error(jsonData["message"]?.ToString(), StatusCodes.Status403Forbidden); } var oldData = await _dutyTimeRepository.GetByIdAsync(id); if (oldData == null) { throw new Exception(GlobalMessages.DataNotFound); } else { var oldDataList = await _dutyTimeRepository.GetAllAsync(); if (data.IsDefault) { foreach (var d in oldDataList) { d.IsDefault = false; await _dutyTimeRepository.UpdateAsync(d); } } oldData.Description = data.Description ?? ""; oldData.IsDefault = data.IsDefault; oldData.IsActive = data.IsActive; if (!data.IsActive) { // ลบรายการที่เคยผูกไว้ทั้งหมด var userDutyTimes = await _context.UserDutyTimes.Where(x => x.DutyTimeId == oldData.Id).ToListAsync(); _context.UserDutyTimes.RemoveRange(userDutyTimes); await _context.SaveChangesAsync(); } await _dutyTimeRepository.UpdateAsync(oldData); return Success(oldData); } } /// /// LV1_003 - ลบรอบการปฏิบัติงาน (ADMIN) /// /// /// /// เมื่อทำรายการสำเร็จ /// ไม่ได้ Login เข้าระบบ /// เมื่อเกิดข้อผิดพลาดในการทำงาน [HttpDelete("duty-time/{id:guid}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] public async Task> DeleteAsync(Guid id) { var getPermission = await _permission.GetPermissionAPIAsync("DELETE", "SYS_WORK_ROUND"); var jsonData = JsonConvert.DeserializeObject(getPermission); if (jsonData["status"]?.ToString() != "200") { return Error(jsonData["message"]?.ToString(), StatusCodes.Status403Forbidden); } var oldData = await _dutyTimeRepository.GetByIdAsync(id); if (oldData == null) { throw new Exception(GlobalMessages.DataNotFound); } else { var inUseRound = await _userDutyTimeRepository.GetFirstInUseRound(oldData.Id); //if (inUseRound != null || oldData.IsActive || oldData.IsDefault) if (inUseRound != null) { throw new Exception("ไม่สามารถลบรอบการปฏิบัติงานที่ยังใช้งานอยู่ได้"); } if (oldData.IsDefault) { throw new Exception("ไม่สามารถลบรอบการปฏิบัติงานที่ตั้งค่า Default ได้"); } await _dutyTimeRepository.DeleteAsync(oldData); return Success(); } } /// /// LV1_012 - ข้อมูลทั้งหมดของรอบการปฏิบัติงานที่ active (ADMIN) /// /// /// /// เมื่อทำรายการสำเร็จ /// ไม่ได้ Login เข้าระบบ /// เมื่อเกิดข้อผิดพลาดในการทำงาน [HttpGet("round")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] public async Task> GetAllActiveAsync() { var getPermission = await _permission.GetPermissionAPIAsync("GET", "SYS_WORK_ROUND_EDIT"); var jsonData = JsonConvert.DeserializeObject(getPermission); if (jsonData["status"]?.ToString() != "200") { return Error(jsonData["message"]?.ToString(), StatusCodes.Status403Forbidden); } var data = await _dutyTimeRepository.GetAllActiveAsync(); return Success(data); } #endregion #region " Check-In Check-Out ลงเวลา " /// /// LV1_006 - เช็คเวลาต้องลงเวลาเข้าหรือออกงาน (USER) /// /// /// /// เมื่อทำรายการสำเร็จ /// ไม่ได้ Login เข้าระบบ /// เมื่อเกิดข้อผิดพลาดในการทำงาน [HttpGet("check-time")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] public async Task> CheckTimeAsync(CancellationToken cancellationToken = default) { var userId = UserId == null ? Guid.Empty : Guid.Parse(UserId); // Get user's last check-in record and profile in parallel var dataTask = _userTimeStampRepository.GetLastRecord(userId); var profileTask = _userProfileRepository.GetProfileByKeycloakIdNew2Async(userId, AccessToken); var defaultRoundTask = _dutyTimeRepository.GetDefaultAsync(); await Task.WhenAll(dataTask, profileTask, defaultRoundTask); var data = await dataTask; var profile = await profileTask; var getDefaultRound = await defaultRoundTask; if (profile == null) { throw new Exception(GlobalMessages.DataNotFound); } if (getDefaultRound == null) { return Error("ไม่พบรอบลงเวลา Default", StatusCodes.Status404NotFound); } var effectiveDate = await _userDutyTimeRepository.GetLastEffectRound(profile.Id); var roundId = effectiveDate != null ? effectiveDate.DutyTimeId : Guid.Empty; var userRound = await _dutyTimeRepository.GetByIdAsync(roundId); var duty = userRound ?? getDefaultRound; // Determine check-in status and data DateTime? checkInTime = null; Guid? checkInId = null; if (data != null) { if (data.CheckOut != null) { // fix issue SIT ระบบบันทึกเวลาปฏิบัติงาน>>ลงเวลาเข้า-ออกงาน (กรณีลงเวลาออกอีกวัน) #921 var currentDate = DateTime.Now.Date; // ถ้า check-in + check-out ไปแล้วในวันเดียวกัน if (data.CheckIn.Date == currentDate && data.CheckOut.Value.Date == currentDate) { return Error("คุณได้ทำการลงเวลาเข้าและออกเรียบร้อยแล้ว คุณจะสามารถลงเวลาได้อีกครั้งในวันถัดไป"); } // ถ้า check-out คนละวัน ให้แสดงว่ายังไม่ได้ check-in วันนี้ } else { // มี check-in แต่ยังไม่ check-out checkInTime = data.CheckIn; checkInId = data.Id; } } // Create response DTO (duty is never null here due to fallback logic) var ret = new CheckInResultDto { StartTimeMorning = duty.StartTimeMorning, EndTimeMorning = duty.EndTimeMorning, StartTimeAfternoon = duty.StartTimeAfternoon, EndTimeAfternoon = duty.EndTimeAfternoon, Description = duty.Description, CheckInTime = checkInTime, CheckInId = checkInId, }; return Success(ret); } /// /// LV1_005 - ลงเวลาเข้า-ออกงาน (USER) /// /// /// /// เมื่อทำรายการสำเร็จ /// ไม่ได้ Login เข้าระบบ /// เมื่อเกิดข้อผิดพลาดในการทำงาน [HttpPost("check-in"), DisableRequestSizeLimit] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] public async Task> CheckInAsync([FromForm] CheckTimeDto data) { // prepare data and convert request body and send to queue var userId = UserId == null ? Guid.Empty : Guid.Parse(UserId); var currentDate = DateTime.Now; // ตรวจสอบว่ามีงานที่กำลัง pending หรือ processing อยู่หรือไม่ var existingJobs = await _checkInJobStatusRepository.GetPendingOrProcessingJobsAsync(userId); if (existingJobs != null && existingJobs.Count > 0) { // กรองเฉพาะงานที่เป็นประเภทเดียวกัน (CHECK_IN หรือ CHECK_OUT) var checkType = data.CheckInId == null ? "CHECK_IN" : "CHECK_OUT"; var sameTypeJob = existingJobs.FirstOrDefault(j => j.CheckType == checkType); if (sameTypeJob != null) { return Error($"มีงาน {checkType} กำลังดำเนินการอยู่", StatusCodes.Status500InternalServerError); // var timeDiff = (currentDate - sameTypeJob.CreatedDate).TotalMinutes; // if (timeDiff < 2) // { // return Error($"มีงาน {checkType} กำลังดำเนินการอยู่ กรุณารอสักครู่", StatusCodes.Status409Conflict); // } } } var checkFileBytes = new byte[0]; // fix issue : ระบบลงเวลาปฏิบัติงาน>>รูปภาพไม่แสดงในฝั่งของ Admin #804 if (data.Img != null && data.Img.Length > 0) { var formFile = data.Img; using (var memoryStream = new MemoryStream()) { await formFile.CopyToAsync(memoryStream); checkFileBytes = memoryStream.ToArray(); } } // add task id for check in queue string taskId = Guid.NewGuid().ToString(); var checkData = new CheckTimeDtoRB { UserId = userId, CurrentDate = currentDate, CheckInId = data.CheckInId, TaskId = Guid.Parse(taskId), Lat = data.Lat, Lon = data.Lon, POI = data.POI, IsLocation = data.IsLocation, LocationName = data.LocationName, Remark = data.Remark, CheckInFileName = data.Img == null ? "no-file" : data.Img.FileName, CheckInFileBytes = checkFileBytes, Token = AccessToken ?? "" }; var channel = _objectPool.Get(); CheckInJobStatus? jobStatus = null; try { var queue = _configuration["Rabbit:Queue"] ?? "basic-queue"; channel.QueueDeclare(queue: queue, durable: true, exclusive: false, autoDelete: false, arguments: null); var serializedObject = JsonConvert.SerializeObject(checkData); var body = Encoding.UTF8.GetBytes(serializedObject); var properties = channel.CreateBasicProperties(); properties.Persistent = true; properties.MessageId = taskId; // บันทึกสถานะงานก่อนส่งไป RabbitMQ jobStatus = new CheckInJobStatus { TaskId = Guid.Parse(taskId), KeycloakUserId = userId, CreatedDate = currentDate, Status = "PENDING", CheckType = data.CheckInId == null ? "CHECK_IN" : "CHECK_OUT", CheckInId = data.CheckInId, AdditionalData = JsonConvert.SerializeObject(new { IsLocation = data.IsLocation, LocationName = data.LocationName, POI = data.POI }) }; await _checkInJobStatusRepository.AddAsync(jobStatus); // ส่งไป RabbitMQ channel.BasicPublish(exchange: "", routingKey: queue, basicProperties: properties, body: body); return Success(new { date = currentDate, taskId = taskId, keycloakId = userId }); } catch (Exception ex) { // ถ้าส่งไป queue ไม่สำเร็จ ให้ลบ job status ที่สร้างไว้ออก if (jobStatus != null) { try { await _checkInJobStatusRepository.DeleteAsync(jobStatus); } catch { // Ignore delete error } } return Error($"ไม่สามารถส่งงานไปยัง Queue ได้: {ex.Message}"); //throw new Exception($"ไม่สามารถส่งงานไปยัง Queue ได้: {ex.Message}"); } finally { _objectPool.Return(channel); } } /// /// ตรวจสอบสถานะงาน check-in ด้วย Task ID /// /// Task ID ที่ได้จากการเรียก CheckInAsync /// /// /// เมื่อทำรายการสำเร็จ /// ไม่ได้ Login เข้าระบบ /// ไม่พบข้อมูลงาน /// เมื่อเกิดข้อผิดพลาดในการทำงาน [HttpGet("job-status/{taskId:guid}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] public async Task> GetJobStatusAsync(Guid taskId) { var jobStatus = await _checkInJobStatusRepository.GetByTaskIdAsync(taskId); if (jobStatus == null) { return Error("ไม่พบข้อมูลงาน", StatusCodes.Status404NotFound); } var result = new { taskId = jobStatus.TaskId, keycloakUserId = jobStatus.KeycloakUserId, status = jobStatus.Status, checkType = jobStatus.CheckType, checkInId = jobStatus.CheckInId, createdDate = jobStatus.CreatedDate, processingDate = jobStatus.ProcessingDate, completedDate = jobStatus.CompletedDate, errorMessage = jobStatus.ErrorMessage, additionalData = jobStatus.AdditionalData != null ? JsonConvert.DeserializeObject(jobStatus.AdditionalData) : null }; return Success(result); } /// /// ดึงรายการงานที่ยัง pending หรือ processing ของผู้ใช้ /// /// /// /// เมื่อทำรายการสำเร็จ /// ไม่ได้ Login เข้าระบบ /// เมื่อเกิดข้อผิดพลาดในการทำงาน [HttpGet("pending-jobs")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] public async Task> GetPendingJobsAsync() { var userId = UserId == null ? Guid.Empty : Guid.Parse(UserId); var jobs = await _checkInJobStatusRepository.GetPendingOrProcessingJobsAsync(userId); var result = jobs.Select(job => new { taskId = job.TaskId, status = job.Status, checkType = job.CheckType, checkInId = job.CheckInId, createdDate = job.CreatedDate, processingDate = job.ProcessingDate }).ToList(); return Success(new { count = result.Count, jobs = result }); } [HttpGet("check-status")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] public async Task> CheckInCheckStatus() { var userId = UserId == null ? Guid.Empty : Guid.Parse(UserId); // var currentDate = DateTime.Now; // var channel = _objectPool.Get(); try { // var _url = _configuration["Rabbit:URL"] ?? ""; // var _queue = _configuration["Rabbit:Queue"] ?? "basic-queue"; // // Step 1: ตรวจสอบจำนวน message ทั้งหมดในคิว // string queueUrl = $"{_url}{_queue}"; // var queueResponse = await _httpClient.GetAsync(queueUrl); // if (!queueResponse.IsSuccessStatusCode) // { // return Error("Error accessing RabbitMQ API", (int)queueResponse.StatusCode); // } // var queueContent = await queueResponse.Content.ReadAsStringAsync(); // var queueData = JObject.Parse(queueContent); // int totalMessages = queueData["messages"]?.Value() ?? 0; // // Step 2: วนลูปดึง message ทีละ 100 งาน // int batchSize = 100; // var allMessages = new List(); // int processedMessages = 0; // while (processedMessages < totalMessages) // { // var requestBody = new StringContent( // $"{{\"count\":{batchSize},\"requeue\":true,\"encoding\":\"auto\",\"ackmode\":\"ack_requeue_true\"}}", // Encoding.UTF8, // "application/json" // ); // string getMessagesUrl = $"{_url}{_queue}/get"; // var response = await _httpClient.PostAsync(getMessagesUrl, requestBody); // if (!response.IsSuccessStatusCode) // { // return StatusCode((int)response.StatusCode, "Error retrieving messages from RabbitMQ."); // } // var content = await response.Content.ReadAsStringAsync(); // var messages = JArray.Parse(content); // if (messages.Count == 0) // { // break; // } // processedMessages += messages.Count; // allMessages.AddRange(messages.Select(m => m["properties"].ToString())); // } var jobs = await _checkInJobStatusRepository.GetPendingOrProcessingJobsAsync(userId); // Step 3: ค้นหา taskIds ที่อยู่ใน messages ทั้งหมด //var foundTasks = allMessages.FirstOrDefault(x => x.Contains(userId.ToString("D"))); return Success(new { keycloakId = userId, InQueue = (jobs != null && jobs.Count > 0) }); } catch (Exception ex) { return Error(ex, ex.Message); } finally { //_objectPool.Return(channel); } } /// /// Fake Check in /// /// /// /// เมื่อทำรายการสำเร็จ /// ไม่ได้ Login เข้าระบบ /// เมื่อเกิดข้อผิดพลาดในการทำงาน [HttpPost("fake-check-in"), DisableRequestSizeLimit] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] [AllowAnonymous] public ActionResult FakeCheckIn([FromBody] FakeCheckTimeDto data) { var currentDate = DateTime.Now; var channel = _objectPool.Get(); try { channel.QueueDeclare(queue: _fakeCheckInQueue, durable: true, exclusive: false, autoDelete: false, arguments: null); // Create Task ID string taskId = Guid.NewGuid().ToString(); var properties = channel.CreateBasicProperties(); properties.Persistent = true; properties.MessageId = taskId; // แนบ Message ID เพื่อใช้ตรวจสอบ var serializedObject = JsonConvert.SerializeObject(data); var body = Encoding.UTF8.GetBytes(serializedObject); channel.BasicPublish(exchange: "", routingKey: _fakeCheckInQueue, basicProperties: properties, body: body); return Success(new { date = currentDate, taskId = taskId }); } finally { _objectPool.Return(channel); } } [HttpGet("fake-check-status/{id:guid}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] [AllowAnonymous] public async Task> FakeCheckInCheckStatus(Guid id) { var currentDate = DateTime.Now; var channel = _objectPool.Get(); try { var _url = _configuration["Rabbit:URL"] ?? ""; // Step 1: ตรวจสอบจำนวน message ทั้งหมดในคิว string queueUrl = $"{_url}{_fakeCheckInQueue}"; var queueResponse = await _httpClient.GetAsync(queueUrl); if (!queueResponse.IsSuccessStatusCode) { return Error("Error accessing RabbitMQ API", (int)queueResponse.StatusCode); } var queueContent = await queueResponse.Content.ReadAsStringAsync(); var queueData = JObject.Parse(queueContent); int totalMessages = queueData["messages"]?.Value() ?? 0; // Step 2: วนลูปดึง message ทีละ 100 งาน int batchSize = 100; var allMessages = new List(); int processedMessages = 0; while (processedMessages < totalMessages) { var requestBody = new StringContent( $"{{\"count\":{batchSize},\"requeue\":true,\"encoding\":\"auto\",\"ackmode\":\"ack_requeue_true\"}}", Encoding.UTF8, "application/json" ); string getMessagesUrl = $"{_url}{_fakeCheckInQueue}/get"; var response = await _httpClient.PostAsync(getMessagesUrl, requestBody); if (!response.IsSuccessStatusCode) { return StatusCode((int)response.StatusCode, "Error retrieving messages from RabbitMQ."); } var content = await response.Content.ReadAsStringAsync(); var messages = JArray.Parse(content); if (messages.Count == 0) { break; } processedMessages += messages.Count; allMessages.AddRange(messages.Select(m => m["properties"].ToString())); } // Step 3: ค้นหา taskIds ที่อยู่ใน messages ทั้งหมด var foundTasks = allMessages.FirstOrDefault(x => x.Contains(id.ToString("D"))); return Success(new { taskId = id, InQueue = foundTasks != null }); } catch (Exception ex) { return Error(ex, ex.Message); } finally { _objectPool.Return(channel); } } /// /// Check in Processing /// /// /// /// เมื่อทำรายการสำเร็จ /// ไม่ได้ Login เข้าระบบ /// เมื่อเกิดข้อผิดพลาดในการทำงาน [HttpPost("process-check-in"), DisableRequestSizeLimit] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] [AllowAnonymous] public async Task> ProcessCheckInAsync([FromBody] CheckTimeDtoRB data) { var userId = data.UserId ?? Guid.Empty; var taskId = data.TaskId ?? Guid.Empty; try { // อัปเดตสถานะเป็น PROCESSING if (taskId != Guid.Empty) { await _checkInJobStatusRepository.UpdateToProcessingAsync(taskId); } var profile = await _userProfileRepository.GetProfileByKeycloakIdNew2Async(userId, data.Token); if (profile == null) { await _checkInJobStatusRepository.UpdateToFailedAsync(taskId, "ไม่พบข้อมูลผู้ใช้"); //var staffList = await _userProfileRepository.GetOCStaffAsync(profile) return Error(GlobalMessages.DataNotFound, StatusCodes.Status404NotFound); } var currentDate = data.CurrentDate ?? DateTime.Now; if (data.CheckInFileName == "no-file") { //throw new Exception(GlobalMessages.NoFileToUpload); await _checkInJobStatusRepository.UpdateToFailedAsync(taskId, GlobalMessages.NoFileToUpload); // send notification to user var noti1 = new Notification { Body = $"ประมวลผลการลงเวลาวันที่ {currentDate.ToString("dd-MM-yyyy")} ไม่สำเร็จ \r\nเนื่องจาก {GlobalMessages.NoFileToUpload}", ReceiverUserId = profile.Id, Type = "", Payload = "", }; _appDbContext.Set().Add(noti1); await _appDbContext.SaveChangesAsync(); return Error(GlobalMessages.NoFileToUpload, StatusCodes.Status400BadRequest); } // last check-in record var lastCheckIn = await _userTimeStampRepository.GetLastRecord(userId); var check_status = data.CheckInId == null ? "check-in-picture" : "check-out-picture"; var check_out_status = "check-out-picture"; var fileName = $"{_bucketName}/{userId}/{currentDate.ToString("dd-MM-yyyy")}/{check_status}/{data.CheckInFileName}"; var fileNameCheckOut = $"{_bucketName}/{userId}/{currentDate.ToString("dd-MM-yyyy")}/{check_out_status}/{data.CheckInFileName}"; using (var ms = new MemoryStream(data.CheckInFileBytes ?? new byte[0])) { try { await _minIOService.UploadFileAsync(fileName, ms); // if (lastCheckIn != null && lastCheckIn.CheckOut == null) // { // // ยังไม่เคย check-out มาก่อน หรือ check-out เป็น null ให้ใช้ชื่อไฟล์แบบ check-out // await _minIOService.UploadFileAsync(fileNameCheckOut, ms); // } } catch (Exception ex) { await _checkInJobStatusRepository.UpdateToFailedAsync(taskId, $"ไม่สามารถอัปโหลดรูปภาพได้: {ex.Message}"); // send notification to user var noti1 = new Notification { Body = $"ประมวลผลการลงเวลาวันที่ {currentDate.ToString("dd-MM-yyyy")} ไม่สำเร็จ \r\nเนื่องจากไม่สามารถอัปโหลดรูปภาพได้ {ex.Message}", ReceiverUserId = profile.Id, Type = "", Payload = "", }; _appDbContext.Set().Add(noti1); await _appDbContext.SaveChangesAsync(); return Error($"ไม่สามารถอัปโหลดรูปภาพได้: {ex.Message}", StatusCodes.Status500InternalServerError); } } if (lastCheckIn != null && lastCheckIn.CheckOut == null) { using (var ms2 = new MemoryStream(data.CheckInFileBytes ?? new byte[0])) { try { await _minIOService.UploadFileAsync(fileNameCheckOut, ms2); // if (lastCheckIn != null && lastCheckIn.CheckOut == null) // { // // ยังไม่เคย check-out มาก่อน หรือ check-out เป็น null ให้ใช้ชื่อไฟล์แบบ check-out // await _minIOService.UploadFileAsync(fileNameCheckOut, ms); // } } catch (Exception ex) { await _checkInJobStatusRepository.UpdateToFailedAsync(taskId, $"ไม่สามารถอัปโหลดรูปภาพได้: {ex.Message}"); // send notification to user var noti1 = new Notification { Body = $"ประมวลผลการลงเวลาวันที่ {currentDate.ToString("dd-MM-yyyy")} ไม่สำเร็จ \r\nเนื่องจากไม่สามารถอัปโหลดรูปภาพได้ {ex.Message}", ReceiverUserId = profile.Id, Type = "", Payload = "", }; _appDbContext.Set().Add(noti1); await _appDbContext.SaveChangesAsync(); return Error($"ไม่สามารถอัปโหลดรูปภาพได้: {ex.Message}", StatusCodes.Status500InternalServerError); } } } var defaultRound = await _dutyTimeRepository.GetDefaultAsync(); if (defaultRound == null) { await _checkInJobStatusRepository.UpdateToFailedAsync(taskId, "ไม่พบรอบการลงเวลาทำงาน Default"); // send notification to user var noti1 = new Notification { Body = $"ประมวลผลการลงเวลาวันที่ {currentDate.ToString("dd-MM-yyyy")} ไม่สำเร็จ \r\nเนื่องจากไม่พบรอบการลงเวลาทำงาน Default", ReceiverUserId = profile.Id, Type = "", Payload = "", }; _appDbContext.Set().Add(noti1); await _appDbContext.SaveChangesAsync(); return Error("ไม่พบรอบการลงเวลาทำงาน Default", StatusCodes.Status404NotFound); } var effectiveDate = await _userDutyTimeRepository.GetLastEffectRound(profile.Id, currentDate); var roundId = effectiveDate != null ? effectiveDate.DutyTimeId : Guid.Empty; var userRound = await _dutyTimeRepository.GetByIdAsync(roundId); // TODO : รอดุึงรอบที่ผูกกับ user var duty = userRound ?? defaultRound; // create check in object if (data.CheckInId == null) { if (lastCheckIn != null && lastCheckIn.CheckOut == null) { var checkout = await _userTimeStampRepository.GetByIdAsync(lastCheckIn!.Id); if(checkout != null) { var currentCheckInProcess = await _processUserTimeStampRepository.GetTimestampByDateAsync(userId, checkout.CheckIn.Date); var checkout_process = await _processUserTimeStampRepository.GetByIdAsync(currentCheckInProcess!.Id); var endTime1 = ""; var startTime1 = ""; var endTimeMorning1 = ""; if (!data.IsLocation && data.LocationName == "ไปประชุม / อบรม / สัมมนา") { startTime1 = "13:00"; endTime1 = "14:30"; endTimeMorning1 = "12:00"; } else { endTime1 = duty.EndTimeAfternoon; startTime1 = duty.StartTimeAfternoon; endTimeMorning1 = duty.EndTimeMorning; } string checkOutStatus = "NORMAL"; var leaveReq1 = await _leaveRequestRepository.GetLeavePeriodAsync(userId, currentDate.Date); if (leaveReq1 != null) { var leaveRange = leaveReq1.LeaveRangeEnd == null ? "" : leaveReq1.LeaveRangeEnd.ToUpper(); if (leaveRange == "AFTERNOON" || leaveRange == "ALL") { if(DateTime.Parse(currentDate.ToString("yyyy-MM-dd HH:mm")) < DateTime.Parse($"{currentDate.ToString("yyyy-MM-dd")} {endTimeMorning1}")) checkOutStatus = "ABSENT"; else checkOutStatus = "NORMAL"; } else { // fix issue : SIT ระบบบันทึกเวลาปฏิบัติงาน>>ลงเวลาเข้า-ออกงาน (กรณีลงเวลาออกอีกวัน) #921 var currentDateTime = DateTime.Parse(currentDate.ToString("yyyy-MM-dd HH:mm")); var dutyEndTimeAfternoon = DateTime.Parse($"{checkout.CheckIn.ToString("yyyy-MM-dd")} {endTime1}"); var dutyEndTimeMorning = DateTime.Parse($"{checkout.CheckIn.ToString("yyyy-MM-dd")} {endTimeMorning1}"); if(currentDateTime.Date > checkout.CheckIn.Date) { // ถ้า check-out เป็นวันถัดไป สถานะปกติเสมอ checkOutStatus = "NORMAL"; } else { // ถ้า check-out เป็นวันเดียวกับ check-in // ตรวจสอบเวลาว่าสิ้นสุดก่อนบ่ายหรือไม่ if(currentDateTime < dutyEndTimeMorning) // ถ้าออกก่อนเวลาสิ้นสุดตอนเช้า ขาดราชการ { checkOutStatus = "ABSENT"; } else if(currentDateTime >= dutyEndTimeAfternoon) // ถ้าออกหลังเวลาสิ้นสุดตอนบ่าย ปกติ { checkOutStatus = "NORMAL"; } else { checkOutStatus = "ABSENT"; } } } } else { // fix issue : SIT ระบบบันทึกเวลาปฏิบัติงาน>>ลงเวลาเข้า-ออกงาน (กรณีลงเวลาออกอีกวัน) #921 var currentDateTime = DateTime.Parse(currentDate.ToString("yyyy-MM-dd HH:mm")); var dutyEndTimeAfternoon = DateTime.Parse($"{checkout.CheckIn.ToString("yyyy-MM-dd")} {endTime1}"); var dutyEndTimeMorning = DateTime.Parse($"{checkout.CheckIn.ToString("yyyy-MM-dd")} {endTimeMorning1}"); if(currentDateTime.Date > checkout.CheckIn.Date) { // ถ้า check-out เป็นวันถัดไป สถานะปกติเสมอ checkOutStatus = "NORMAL"; } else { // ถ้า check-out เป็นวันเดียวกับ check-in // ตรวจสอบเวลาว่าสิ้นสุดก่อนบ่ายหรือไม่ if(currentDateTime < dutyEndTimeMorning) // ถ้าออกก่อนเวลาสิ้นสุดตอนเช้า ขาดราชการ { checkOutStatus = "ABSENT"; } else if(currentDateTime >= dutyEndTimeAfternoon) // ถ้าออกหลังเวลาสิ้นสุดตอนบ่าย ปกติ { checkOutStatus = "NORMAL"; } else { checkOutStatus = "ABSENT"; } } } if (checkout_process != null) { checkout_process.CheckOutLat = data.Lat; checkout_process.CheckOutLon = data.Lon; checkout_process.IsLocationCheckOut = data.IsLocation; checkout_process.CheckOutLocationName = data.LocationName; checkout_process.CheckOutPOI = data.POI; checkout_process.CheckOutRemark = data.Remark; checkout_process.CheckOutImageUrl = fileNameCheckOut; checkout_process.CheckOut = currentDate; checkout_process.CheckOutStatus = checkOutStatus; await _processUserTimeStampRepository.UpdateAsync(checkout_process); } } } // validate duplicate check in var currentCheckIn = await _userTimeStampRepository.GetTimestampByDateAsync(userId, currentDate); if (currentCheckIn != null) { await _checkInJobStatusRepository.UpdateToFailedAsync(taskId, "ไม่สามารถลงเวลาได้ เนื่องจากมีการลงเวลาในวันนี้แล้ว"); // send notification to user var noti1 = new Notification { Body = $"ประมวลผลการลงเวลาวันที่ {currentDate.ToString("dd-MM-yyyy")} ไม่สำเร็จ \r\nเนื่องจากมีการลงเวลาในวันนี้แล้ว", ReceiverUserId = profile.Id, Type = "", Payload = "", }; _appDbContext.Set().Add(noti1); await _appDbContext.SaveChangesAsync(); return Error(new Exception("ไม่สามารถลงเวลาได้ เนื่องจากมีการลงเวลาในวันนี้แล้ว!"), StatusCodes.Status400BadRequest); } var checkin = new UserTimeStamp { KeycloakUserId = userId, CheckInLat = data.Lat, CheckInLon = data.Lon, IsLocationCheckIn = data.IsLocation, CheckInLocationName = data.LocationName, CheckInPOI = data.POI, CheckInRemark = data.Remark, CheckInImageUrl = fileName, CheckIn = currentDate, Prefix = profile.Prefix, FirstName = profile.FirstName, LastName = profile.LastName, CitizenId = profile.CitizenId, Root = profile.Root, Child1 = profile.Child1, Child2 = profile.Child2, Child3 = profile.Child3, Child4 = profile.Child4, RootId = profile.RootId, Child1Id = profile.Child1Id, Child2Id = profile.Child2Id, Child3Id = profile.Child3Id, Child4Id = profile.Child4Id, Gender = profile.Gender, ProfileId = profile.Id, ProfileType = profile.ProfileType, RootDnaId = profile.RootDnaId, Child1DnaId = profile.Child1DnaId, Child2DnaId = profile.Child2DnaId, Child3DnaId = profile.Child3DnaId, Child4DnaId = profile.Child4DnaId, }; var startTime = ""; var endTime = ""; if (!data.IsLocation && data.LocationName == "ไปประชุม / อบรม / สัมมนา") { //startTime = "09:30"; startTime = "10:30"; endTime = "12:00"; } else { startTime = duty.StartTimeMorning; endTime = duty.EndTimeMorning; } string checkInStatus = "NORMAL"; var leaveReq = await _leaveRequestRepository.GetLeavePeriodAsync(userId, currentDate.Date); if (leaveReq != null) { var leaveRange = leaveReq.LeaveRange == null ? "" : leaveReq.LeaveRange.ToUpper(); if (leaveRange == "MORNING" || leaveRange == "ALL") checkInStatus = "NORMAL"; else { checkInStatus = DateTime.Parse(currentDate.ToString("yyyy-MM-dd HH:mm")) > DateTime.Parse($"{currentDate.ToString("yyyy-MM-dd")} {startTime}") ? DateTime.Parse(currentDate.ToString("yyyy-MM-dd HH:mm")) > DateTime.Parse($"{currentDate.ToString("yyyy-MM-dd")} {duty.EndTimeMorning}") ? "ABSENT" : "LATE" : "NORMAL"; } } else { checkInStatus = DateTime.Parse(currentDate.ToString("yyyy-MM-dd HH:mm")) > DateTime.Parse($"{currentDate.ToString("yyyy-MM-dd")} {startTime}") ? DateTime.Parse(currentDate.ToString("yyyy-MM-dd HH:mm")) > DateTime.Parse($"{currentDate.ToString("yyyy-MM-dd")} {duty.EndTimeMorning}") ? "ABSENT" : "LATE" : "NORMAL"; } // process - รอทำใน queue var checkin_process = new ProcessUserTimeStamp { KeycloakUserId = userId, CheckInLat = data.Lat, CheckInLon = data.Lon, IsLocationCheckIn = data.IsLocation, CheckInLocationName = data.LocationName, CheckInPOI = data.POI, CheckInRemark = data.Remark, CheckInImageUrl = fileName, CheckIn = currentDate, CheckInStatus = checkInStatus, Prefix = profile.Prefix, FirstName = profile.FirstName, LastName = profile.LastName, CitizenId = profile.CitizenId, Root = profile.Root, Child1 = profile.Child1, Child2 = profile.Child2, Child3 = profile.Child3, Child4 = profile.Child4, RootId = profile.RootId, Child1Id = profile.Child1Id, Child2Id = profile.Child2Id, Child3Id = profile.Child3Id, Child4Id = profile.Child4Id, Gender = profile.Gender, ProfileId = profile.Id, ProfileType = profile.ProfileType, RootDnaId = profile.RootDnaId, Child1DnaId = profile.Child1DnaId, Child2DnaId = profile.Child2DnaId, Child3DnaId = profile.Child3DnaId, Child4DnaId = profile.Child4DnaId, }; await _userTimeStampRepository.AddAsync(checkin); await _processUserTimeStampRepository.AddAsync(checkin_process); } else { var checkout = await _userTimeStampRepository.GetByIdAsync(data.CheckInId.Value); //var currentCheckIn = await _userTimeStampRepository.GetTimestampByDateAsync(userId, currentDate); if (checkout == null) { await _checkInJobStatusRepository.UpdateToFailedAsync(taskId, "ไม่พบข้อมูลการลงเวลาทำงาน"); // send notification to user var noti1 = new Notification { Body = $"ประมวลผลการลงเวลาวันที่ {currentDate.ToString("dd-MM-yyyy")} ไม่สำเร็จ \r\nเนื่องจากไม่พบข้อมูลการลงเวลาทำงาน", ReceiverUserId = profile.Id, Type = "", Payload = "", }; _appDbContext.Set().Add(noti1); await _appDbContext.SaveChangesAsync(); return Error(new Exception(GlobalMessages.DataNotFound), StatusCodes.Status404NotFound); } var currentCheckInProcess = await _processUserTimeStampRepository.GetTimestampByDateAsync(userId, checkout.CheckIn.Date); if (currentCheckInProcess == null) { await _checkInJobStatusRepository.UpdateToFailedAsync(taskId, "ไม่พบข้อมูลการประมวลผลเวลาทำงาน (CheckIn)"); // send notification to user var noti1 = new Notification { Body = $"ประมวลผลการลงเวลาวันที่ {currentDate.ToString("dd-MM-yyyy")} ไม่สำเร็จ \r\nเนื่องจากไม่พบข้อมูลการประมวลผลเวลาทำงาน (CheckIn)", ReceiverUserId = profile.Id, Type = "", Payload = "", }; _appDbContext.Set().Add(noti1); await _appDbContext.SaveChangesAsync(); return Error(new Exception(GlobalMessages.DataNotFound), StatusCodes.Status404NotFound); } var checkout_process = await _processUserTimeStampRepository.GetByIdAsync(currentCheckInProcess.Id); // Update checkout record checkout.CheckOutLat = data.Lat; checkout.CheckOutLon = data.Lon; checkout.IsLocationCheckOut = data.IsLocation; checkout.CheckOutLocationName = data.LocationName; checkout.CheckOutPOI = data.POI; checkout.CheckOutRemark = data.Remark; checkout.CheckOutImageUrl = fileName; checkout.CheckOut = currentDate; await _userTimeStampRepository.UpdateAsync(checkout); var endTime = ""; var startTime = ""; var endTimeMorning = ""; if (!data.IsLocation && data.LocationName == "ไปประชุม / อบรม / สัมมนา") { startTime = "13:00"; endTime = "14:30"; endTimeMorning = "12:00"; } else { endTime = duty.EndTimeAfternoon; startTime = duty.StartTimeAfternoon; endTimeMorning = duty.EndTimeMorning; } string checkOutStatus = "NORMAL"; var leaveReq = await _leaveRequestRepository.GetLeavePeriodAsync(userId, currentDate.Date); if (leaveReq != null) { var leaveRange = leaveReq.LeaveRangeEnd == null ? "" : leaveReq.LeaveRangeEnd.ToUpper(); if (leaveRange == "AFTERNOON" || leaveRange == "ALL") { if (checkout.CheckIn.Date < currentDate.Date) { // ถ้า check-out เป็นวันถัดไป สถานะปกติเสมอ checkOutStatus = "NORMAL"; } else { if(DateTime.Parse(currentDate.ToString("yyyy-MM-dd HH:mm")) < DateTime.Parse($"{currentDate.ToString("yyyy-MM-dd")} {endTimeMorning}")) checkOutStatus = "ABSENT"; else checkOutStatus = "NORMAL"; } } else { // fix issue : SIT ระบบบันทึกเวลาปฏิบัติงาน>>ลงเวลาเข้า-ออกงาน (กรณีลงเวลาออกอีกวัน) #921 var currentDateTime = DateTime.Parse(currentDate.ToString("yyyy-MM-dd HH:mm")); var dutyEndTimeAfternoon = DateTime.Parse($"{checkout.CheckIn.ToString("yyyy-MM-dd")} {endTime}"); var dutyEndTimeMorning = DateTime.Parse($"{checkout.CheckIn.ToString("yyyy-MM-dd")} {endTimeMorning}"); if(currentDateTime.Date > checkout.CheckIn.Date) { // ถ้า check-out เป็นวันถัดไป สถานะปกติเสมอ checkOutStatus = "NORMAL"; } else { // ถ้า check-out เป็นวันเดียวกับ check-in // ตรวจสอบเวลาว่าสิ้นสุดก่อนบ่ายหรือไม่ if(currentDateTime < dutyEndTimeMorning) // ถ้าออกก่อนเวลาสิ้นสุดตอนเช้า ขาดราชการ { checkOutStatus = "ABSENT"; } else if(currentDateTime >= dutyEndTimeAfternoon) // ถ้าออกหลังเวลาสิ้นสุดตอนบ่าย ปกติ { checkOutStatus = "NORMAL"; } else { checkOutStatus = "ABSENT"; } } // checkOutStatus = DateTime.Parse(currentDate.ToString("yyyy-MM-dd HH:mm")) < // DateTime.Parse($"{checkout.CheckIn.ToString("yyyy-MM-dd")} {duty.EndTimeAfternoon}") ? // // "ABSENT" : // checkout.CheckIn.Date < currentDate.Date ? "NORMAL" : // DateTime.Parse(currentDate.ToString("yyyy-MM-dd HH:mm")) >= // DateTime.Parse($"{checkout.CheckIn.ToString("yyyy-MM-dd")} {endTime}") ? // "NORMAL" : // "ABSENT" : // DateTime.Parse(currentDate.ToString("yyyy-MM-dd HH:mm")) < // DateTime.Parse($"{currentDate.ToString("yyyy-MM-dd")} {endTimeMorning}") ? // "ABSENT" : // "NORMAL"; } } else { // fix issue : SIT ระบบบันทึกเวลาปฏิบัติงาน>>ลงเวลาเข้า-ออกงาน (กรณีลงเวลาออกอีกวัน) #921 var currentDateTime = DateTime.Parse(currentDate.ToString("yyyy-MM-dd HH:mm")); var dutyEndTimeAfternoon = DateTime.Parse($"{checkout.CheckIn.ToString("yyyy-MM-dd")} {endTime}"); var dutyEndTimeMorning = DateTime.Parse($"{checkout.CheckIn.ToString("yyyy-MM-dd")} {endTimeMorning}"); if(currentDateTime.Date > checkout.CheckIn.Date) { // ถ้า check-out เป็นวันถัดไป สถานะปกติเสมอ checkOutStatus = "NORMAL"; } else { // ถ้า check-out เป็นวันเดียวกับ check-in // ตรวจสอบเวลาว่าสิ้นสุดก่อนบ่ายหรือไม่ if(currentDateTime < dutyEndTimeMorning) // ถ้าออกก่อนเวลาสิ้นสุดตอนเช้า ขาดราชการ { checkOutStatus = "ABSENT"; } else if(currentDateTime >= dutyEndTimeAfternoon) // ถ้าออกหลังเวลาสิ้นสุดตอนบ่าย ปกติ { checkOutStatus = "NORMAL"; } else { checkOutStatus = "ABSENT"; } } // checkOutStatus = DateTime.Parse(currentDate.ToString("yyyy-MM-dd HH:mm")) < // DateTime.Parse($"{currentDate.ToString("yyyy-MM-dd")} {duty.EndTimeAfternoon}") ? // // "ABSENT" : // checkout.CheckIn.Date < currentDate.Date ? "NORMAL" : // DateTime.Parse(currentDate.ToString("yyyy-MM-dd HH:mm")) >= // DateTime.Parse($"{currentDate.ToString("yyyy-MM-dd")} {endTime}") ? // "NORMAL" : // "ABSENT" : // DateTime.Parse(currentDate.ToString("yyyy-MM-dd HH:mm")) < // DateTime.Parse($"{currentDate.ToString("yyyy-MM-dd")} {endTimeMorning}") ? // "ABSENT" : // "NORMAL"; } if (checkout_process != null) { checkout_process.CheckOutLat = data.Lat; checkout_process.CheckOutLon = data.Lon; checkout_process.IsLocationCheckOut = data.IsLocation; checkout_process.CheckOutLocationName = data.LocationName; checkout_process.CheckOutPOI = data.POI; checkout_process.CheckOutRemark = data.Remark; checkout_process.CheckOutImageUrl = fileName; checkout_process.CheckOut = currentDate; checkout_process.CheckOutStatus = checkOutStatus; await _processUserTimeStampRepository.UpdateAsync(checkout_process); } else { await _checkInJobStatusRepository.UpdateToFailedAsync(taskId, "ไม่พบข้อมูลการประมวลผลเวลาทำงาน"); // send notification to user var noti1 = new Notification { Body = $"ประมวลผลการลงเวลาวันที่ {currentDate.ToString("dd-MM-yyyy")} ไม่สำเร็จ \r\nเนื่องจากไม่พบข้อมูลการประมวลผลเวลาทำงาน", ReceiverUserId = profile.Id, Type = "", Payload = "", }; _appDbContext.Set().Add(noti1); await _appDbContext.SaveChangesAsync(); return Error(new Exception(GlobalMessages.DataNotFound), StatusCodes.Status404NotFound); } } // อัปเดตสถานะเป็น COMPLETED if (taskId != Guid.Empty) { var additionalData = JsonConvert.SerializeObject(new { CheckInType = data.CheckInId == null ? "check-in" : "check-out", FileName = fileName, ProcessedDate = currentDate }); await _checkInJobStatusRepository.UpdateToCompletedAsync(taskId, additionalData); } var checkInType = data.CheckInId == null ? "check-in" : "check-out"; return Success(new { user = $"{profile.FirstName} {profile.LastName}", date = currentDate, type = checkInType }); ; } catch (Exception ex) { // อัปเดตสถานะเป็น FAILED if (taskId != Guid.Empty) { await _checkInJobStatusRepository.UpdateToFailedAsync(taskId, ex.Message); } return Error(ex); } } /// /// LV1_005 - ลงเวลาเข้า-ออกงาน (USER) /// /// /// /// เมื่อทำรายการสำเร็จ /// ไม่ได้ Login เข้าระบบ /// เมื่อเกิดข้อผิดพลาดในการทำงาน [HttpPost("check-in-old"), DisableRequestSizeLimit] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] public async Task> CheckInOldAsync([FromForm] CheckTimeDto data) { var userId = UserId == null ? Guid.Empty : Guid.Parse(UserId); var profile = await _userProfileRepository.GetProfileByKeycloakIdNew2Async(userId, AccessToken); if (profile == null) return Error(GlobalMessages.DataNotFound, StatusCodes.Status404NotFound); if (data.Img == null) throw new Exception(GlobalMessages.NoFileToUpload); var currentDate = DateTime.Now; var fileName = $"{_bucketName}/{UserId}/{DateTime.Now.ToString("dd-MM-yyyy")}/{data.Img.FileName}"; using (var ms = new MemoryStream()) { data.Img.CopyTo(ms); await _minIOService.UploadFileAsync(fileName, ms); } var defaultRound = await _dutyTimeRepository.GetDefaultAsync(); if (defaultRound == null) { return Error("ไม่พบรอบการลงเวลาทำงาน Default", StatusCodes.Status404NotFound); } var effectiveDate = await _userDutyTimeRepository.GetLastEffectRound(profile.Id); var roundId = effectiveDate != null ? effectiveDate.DutyTimeId : Guid.Empty; var userRound = await _dutyTimeRepository.GetByIdAsync(roundId); // TODO : รอดุึงรอบที่ผูกกับ user var duty = userRound ?? defaultRound; // create check in object if (data.CheckInId == null) { // validate duplicate check in var currentCheckIn = await _userTimeStampRepository.GetTimestampByDateAsync(Guid.Parse(UserId), currentDate); if (currentCheckIn != null) { return Error(new Exception("ไม่สามารถลงเวลาได้ เนื่องจากมีการลงเวลาในวันนี้แล้ว!"), StatusCodes.Status400BadRequest); } var checkin = new UserTimeStamp { KeycloakUserId = UserId != null ? Guid.Parse(UserId) : Guid.Empty, CheckInLat = data.Lat, CheckInLon = data.Lon, IsLocationCheckIn = data.IsLocation, CheckInLocationName = data.LocationName, CheckInPOI = data.POI, CheckInRemark = data.Remark, CheckInImageUrl = fileName, CheckIn = currentDate, Prefix = profile.Prefix, FirstName = profile.FirstName, LastName = profile.LastName, }; var checkInStatus = DateTime.Parse(currentDate.ToString("yyyy-MM-dd HH:mm")) > DateTime.Parse($"{currentDate.ToString("yyyy-MM-dd")} {duty.StartTimeMorning}") ? DateTime.Parse(currentDate.ToString("yyyy-MM-dd HH:mm")) > DateTime.Parse($"{currentDate.ToString("yyyy-MM-dd")} {duty.EndTimeMorning}") ? "ABSENT" : "LATE" : "NORMAL"; // process - รอทำใน queue var checkin_process = new ProcessUserTimeStamp { KeycloakUserId = UserId != null ? Guid.Parse(UserId) : Guid.Empty, CheckInLat = data.Lat, CheckInLon = data.Lon, IsLocationCheckIn = data.IsLocation, CheckInLocationName = data.LocationName, CheckInPOI = data.POI, CheckInRemark = data.Remark, CheckInImageUrl = fileName, CheckIn = currentDate, CheckInStatus = checkInStatus, Prefix = profile.Prefix, FirstName = profile.FirstName, LastName = profile.LastName, }; await _userTimeStampRepository.AddAsync(checkin); await _processUserTimeStampRepository.AddAsync(checkin_process); } else { var checkout = await _userTimeStampRepository.GetByIdAsync(data.CheckInId.Value); var currentCheckInProcess = await _processUserTimeStampRepository.GetTimestampByDateAsync(Guid.Parse(UserId), checkout.CheckIn.Date); var checkout_process = await _processUserTimeStampRepository.GetByIdAsync(currentCheckInProcess.Id); if (checkout != null) { checkout.CheckOutLat = data.Lat; checkout.CheckOutLon = data.Lon; checkout.IsLocationCheckOut = data.IsLocation; checkout.CheckOutLocationName = data.LocationName; checkout.CheckOutPOI = data.POI; checkout.CheckOutRemark = data.Remark; checkout.CheckOutImageUrl = fileName; checkout.CheckOut = currentDate; await _userTimeStampRepository.UpdateAsync(checkout); } else { return Error(new Exception(GlobalMessages.DataNotFound), StatusCodes.Status404NotFound); } var checkOutStatus = DateTime.Parse(currentDate.ToString("yyyy-MM-dd HH:mm")) < DateTime.Parse($"{currentDate.ToString("yyyy-MM-dd")} {duty.EndTimeAfternoon}") ? "ABSENT" : DateTime.Parse(currentDate.ToString("yyyy-MM-dd HH:mm")) < DateTime.Parse($"{currentDate.ToString("yyyy-MM-dd")} {duty.EndTimeMorning}") ? "ABSENT" : "NORMAL"; if (checkout_process != null) { checkout_process.CheckOutLat = data.Lat; checkout_process.CheckOutLon = data.Lon; checkout_process.IsLocationCheckOut = data.IsLocation; checkout_process.CheckOutLocationName = data.LocationName; checkout_process.CheckOutPOI = data.POI; checkout_process.CheckOutRemark = data.Remark; checkout_process.CheckOutImageUrl = fileName; checkout_process.CheckOut = currentDate; checkout_process.CheckOutStatus = checkOutStatus; await _processUserTimeStampRepository.UpdateAsync(checkout_process); } else { return Error(new Exception(GlobalMessages.DataNotFound), StatusCodes.Status404NotFound); } } return Success(new { date = currentDate }); } /// /// LV1_007 - ประวัติการลงเวลา (USER) /// /// /// /// เมื่อทำรายการสำเร็จ /// ไม่ได้ Login เข้าระบบ /// เมื่อเกิดข้อผิดพลาดในการทำงาน [HttpGet("check-in/history")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] public async Task> CheckInHistoryAsync(int year, int page = 1, int pageSize = 10, string keyword = "") { var userId = UserId == null ? Guid.Empty : Guid.Parse(UserId); var profile = await _userProfileRepository.GetProfileByKeycloakIdNew2Async(userId, AccessToken); if (profile == null) { return Error(GlobalMessages.DataNotFound, StatusCodes.Status404NotFound); } var defaultRound = await _dutyTimeRepository.GetDefaultAsync(); if (defaultRound == null) { return Error("ไม่พบรอบการลงเวลาทำงาน Default", StatusCodes.Status404NotFound); } var effectiveDate = await _userDutyTimeRepository.GetLastEffectRound(profile.Id); var roundId = effectiveDate != null ? effectiveDate.DutyTimeId : Guid.Empty; var userRound = await _dutyTimeRepository.GetByIdAsync(roundId); // TODO : รอดุึงรอบที่ผูกกับ user var duty = userRound ?? defaultRound; var checkin_base = DateTime.Parse($"{DateTime.Now.ToString("yyyy-MM-dd")} {duty.StartTimeMorning}"); var checkout_base = DateTime.Parse($"{DateTime.Now.ToString("yyyy-MM-dd")} {duty.EndTimeAfternoon}"); // var test = await _processUserTimeStampRepository.GetTimeStampHistoryAsync(userId, year); // return Success(test); var data = (await _processUserTimeStampRepository.GetTimeStampHistoryAsync2(userId, year)) .Select(d => new CheckInHistoryDto { CheckInId = d.Id, CheckInDate = d.CheckIn.Date, CheckInTime = d.CheckIn.ToString("HH:mm:ss"), CheckInLocation = d.CheckInPOI, CheckInStatus = d.CheckInStatus != null || d.CheckInStatus != "" ? d.CheckInStatus : DateTime.Parse(d.CheckIn.ToString("yyyy-MM-dd HH:mm")) > DateTime.Parse($"{d.CheckIn.Date.ToString("yyyy-MM-dd")} {duty.StartTimeMorning}") ? "LATE" : "NORMAL", CheckInIsLocation = d.IsLocationCheckIn, CheckInLocationName = d.CheckInLocationName ?? "", CheckOutDate = d.CheckOut == null ? null : d.CheckOut.Value.Date, CheckOutTime = d.CheckOut == null ? "" : d.CheckOut.Value.ToString("HH:mm:ss"), CheckOutLocation = d.CheckOutPOI ?? "", CheckOutStatus = d.CheckOut == null ? null : d.CheckOutStatus != null || d.CheckOutStatus != "" ? d.CheckOutStatus : DateTime.Parse(d.CheckOut.Value.ToString("yyyy-MM-dd HH:mm")) < DateTime.Parse($"{d.CheckIn.Date.ToString("yyyy-MM-dd")} {duty.EndTimeAfternoon}") ? "LATE" : DateTime.Parse(d.CheckOut.Value.ToString("yyyy-MM-dd HH:mm")) < DateTime.Parse($"{d.CheckIn.Date.ToString("yyyy-MM-dd")} {duty.EndTimeMorning}") ? "ABSENT" : "NORMAL", CheckOutIsLocation = d.IsLocationCheckOut, CheckOutLocationName = d.CheckOutLocationName ?? "", IsEdit = _processUserTimeStampRepository.IsEditRequest(userId, d.CheckIn.Date) //IsEdit = (d.EditStatus != null && d.EditStatus != "") //EditReason = d.EditReason ?? "",ß //EditStatus = d.EditStatus ?? "" }) .ToList(); if (keyword != "") { data = data.Where(x => (x.CheckInLocationName!.Contains(keyword) || x.CheckInLocation!.Contains(keyword) || x.CheckOutLocationName!.Contains(keyword) || x.CheckOutLocation!.Contains(keyword))) .ToList(); } var pageData = data .Skip((page - 1) * pageSize) .Take(pageSize) .ToList(); return Success(new { data = pageData, total = data.Count }); } /// /// LV1_010 - รายการลงเวลาปฏิบัติงาน (ADMIN) /// /// /// /// เมื่อทำรายการสำเร็จ /// ไม่ได้ Login เข้าระบบ /// เมื่อเกิดข้อผิดพลาดในการทำงาน [HttpGet("log-record")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] public async Task> LogRecordAsync([Required] DateTime startDate, [Required] DateTime endDate, int page = 1, int pageSize = 10, string keyword = "", string profileType = "ALL", string? sortBy = "", bool? descending = false) { var getPermission = await _permission.GetPermissionAPIAsync("LIST", "SYS_CHECKIN"); var jsonData = JsonConvert.DeserializeObject(getPermission); if (jsonData["status"]?.ToString() != "200") { return Error(jsonData["message"]?.ToString(), StatusCodes.Status403Forbidden); } if (startDate.Date > endDate.Date) { return Error(new Exception("วันเริ่มต้นต้องมีค่าน้อยกว่าหรือเท่ากับวันสิ้นสุด"), StatusCodes.Status400BadRequest); } //var count = await _userTimeStampRepository.CountRecordAsync(); var imgUrl = $"{_configuration["MinIO:Endpoint"]}{_configuration["MinIO:BucketName"]}"; string role = jsonData["result"]?.ToString(); var nodeId = string.Empty; var profileAdmin = new GetUserOCAllDto(); profileAdmin = await _userProfileRepository.GetUserOCAll(Guid.Parse(UserId!), AccessToken); if (role == "NORMAL" || role == "CHILD") { nodeId = profileAdmin?.Node == 4 ? profileAdmin?.Child4DnaId : profileAdmin?.Node == 3 ? profileAdmin?.Child3DnaId : profileAdmin?.Node == 2 ? profileAdmin?.Child2DnaId : profileAdmin?.Node == 1 ? profileAdmin?.Child1DnaId : profileAdmin?.Node == 0 ? profileAdmin?.RootDnaId : ""; } else if (role == "BROHTER") { nodeId = profileAdmin?.Node == 4 ? profileAdmin?.Child3DnaId : profileAdmin?.Node == 3 ? profileAdmin?.Child2DnaId : profileAdmin?.Node == 2 ? profileAdmin?.Child1DnaId : profileAdmin?.Node == 1 ? profileAdmin?.RootDnaId : ""; } else if (role == "ROOT" /*|| role == "PARENT"*/) { nodeId = profileAdmin?.RootDnaId; } //var data = (await _userTimeStampRepository.GetTimeStampHistoryForAdminAsync(startDate, endDate)) var data = (await _userTimeStampRepository.GetTimeStampHistoryForAdminRoleAsync(startDate, endDate, role, nodeId, profileAdmin?.Node)) .Select(d => new CheckInHistoryForAdminDto { Id = d.Id, //FullName = _userProfileRepository.GetUserFullName(d.KeycloakUserId, AccessToken), FullName = $"{d.Prefix ?? ""}{d.FirstName ?? ""} {d.LastName ?? ""}", Prefix = d.Prefix ?? "", FirstName = d.FirstName ?? "", LastName = d.LastName ?? "", ProfileType = d.ProfileType ?? "", CheckInDate = d.CheckIn.Date, CheckInTime = d.CheckIn.ToString("HH:mm:ss"), CheckInLocation = d.CheckInPOI, CheckInLat = d.CheckInLat, CheckInLon = d.CheckInLon, CheckInImage = $"{imgUrl}/{d.CheckInImageUrl}", // add from new specification IsLocationCheckIn = d.IsLocationCheckIn, CheckInLocationName = d.CheckInLocationName ?? "", CheckOutDate = d.CheckOut?.Date, CheckOutTime = d.CheckOut == null ? "" : d.CheckOut.Value.ToString("HH:mm:ss"), CheckOutLocation = d.CheckOut == null ? "" : d.CheckOutPOI, CheckOutLat = d.CheckOut == null ? null : d.CheckOutLat, CheckOutLon = d.CheckOut == null ? null : d.CheckOutLon, CheckOutImage = d.CheckOut == null ? "" : $"{imgUrl}/{d.CheckOutImageUrl}", // add from new specification IsLocationCheckOut = d.IsLocationCheckOut, CheckOutLocationName = d.CheckOutLocationName ?? "" }) .ToList(); if (keyword != "") { data = data.Where(x => x.FullName.Contains(keyword)).ToList(); } if (profileType.Trim().ToUpper() != "ALL") data = data.Where(x => x.ProfileType == profileType.Trim().ToUpper()).ToList(); if (!string.IsNullOrWhiteSpace(sortBy)) { switch (sortBy.ToUpper()) { case "FULLNAME": if (descending == true) data = data.OrderByDescending(x => x.Prefix) .ThenByDescending(x => x.FirstName) .ThenByDescending(x => x.LastName) .ToList(); else data = data.OrderBy(x => x.Prefix) .ThenBy(x => x.FirstName) .ThenBy(x => x.LastName) .ToList(); break; case "CHECKINTIME": if (descending == true) data = data.OrderByDescending(x => x.CheckInTime).ToList(); else data = data.OrderBy(x => x.CheckInTime).ToList(); break; case "CHECKINLOCATION": if (descending == true) data = data.OrderByDescending(x => x.CheckInLocation) .ThenByDescending(x => x.CheckInLat) .ThenByDescending(x => x.CheckInLon) .ToList(); else data = data.OrderBy(x => x.CheckInLocation) .ThenBy(x => x.CheckInLat) .ThenBy(x => x.CheckInLon) .ToList(); break; case "CHECKOUTTIME": if (descending == true) data = data.OrderByDescending(x => x.CheckOutTime).ToList(); else data = data.OrderBy(x => x.CheckOutTime).ToList(); break; case "CHECKOUTLOCATION": if (descending == true) data = data.OrderByDescending(x => x.CheckOutLocation) .ThenByDescending(x => x.CheckOutLat) .ThenByDescending(x => x.CheckOutLon) .ToList(); else data = data.OrderBy(x => x.CheckOutLocation) .ThenBy(x => x.CheckOutLat) .ThenBy(x => x.CheckOutLon) .ToList(); break; default: break; } } var pageData = data .Skip((page - 1) * pageSize) .Take(pageSize) .ToList(); return Success(new { data = pageData, total = data.Count }); } /// /// LV1_011 - รายละเอียดการลงเวลาปฎิบัติงานรายบุคคล Tabรายากรลงเวลาที่ประมวลผลแล้ว (ADMIN) /// /// /// /// เมื่อทำรายการสำเร็จ /// ไม่ได้ Login เข้าระบบ /// เมื่อเกิดข้อผิดพลาดในการทำงาน [HttpGet("time-record/{id:guid}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] public async Task> GetTimeRecordAsync([Required] Guid id) { var getWorkflow = await _permission.GetPermissionAPIWorkflowAsync(id.ToString(), "SYS_CHECKIN"); if (getWorkflow == false) { var getPermission = await _permission.GetPermissionAPIAsync("GET", "SYS_CHECKIN"); var jsonData = JsonConvert.DeserializeObject(getPermission); if (jsonData["status"]?.ToString() != "200") { return Error(jsonData["message"]?.ToString(), StatusCodes.Status403Forbidden); } } var imgUrl = $"{_configuration["MinIO:Endpoint"]}{_configuration["MinIO:BucketName"]}"; var d = (await _processUserTimeStampRepository.GetTimeStampById(id)); if (d == null) { throw new Exception(GlobalMessages.DataNotFound); } else { var profile = await _userProfileRepository.GetProfileByKeycloakIdNew2Async(d.KeycloakUserId, AccessToken); if (profile == null) { return Error(GlobalMessages.DataNotFound, StatusCodes.Status404NotFound); } var defaultRound = await _dutyTimeRepository.GetDefaultAsync(); if (defaultRound == null) { return Error("ไม่พบรอบการลงเวลา Default", StatusCodes.Status404NotFound); } //var userRound = await _dutyTimeRepository.GetByIdAsync(profile.DutyTimeId ?? Guid.Empty); var effectiveDate = await _userDutyTimeRepository.GetLastEffectRound(profile.Id); var roundId = effectiveDate != null ? effectiveDate.DutyTimeId : Guid.Empty; var userRound = await _dutyTimeRepository.GetByIdAsync(roundId); var duty = userRound ?? defaultRound; var result = new CheckInDetailForAdminDto { Id = d.Id, FullName = $"{d.Prefix}{d.FirstName} {d.LastName}", ProfileType = (d.ProfileType != "" || d.ProfileType != null) ? d.ProfileType : (profile.ProfileType ?? ""), //FullName = $"{profile.Prefix}{profile.FirstName} {profile.LastName}", // _userProfileRepository.GetUserFullName(d.KeycloakUserId, AccessToken), CheckInDate = d.CheckIn.Date, CheckInTime = d.CheckIn.ToString("HH:mm"), CheckInPOI = d.CheckInPOI, CheckInLat = d.CheckInLat, CheckInLon = d.CheckInLon, //CheckInImg = $"{imgUrl}/{d.CheckInImageUrl}", CheckInImg = await _minIOService.ImagesPathByName(d.CheckInImageUrl), CheckInStatus = d.CheckInStatus != null || d.CheckInStatus != "" ? d.CheckInStatus : DateTime.Parse(d.CheckIn.ToString("yyyy-MM-dd HH:mm")) > DateTime.Parse($"{d.CheckIn.Date.ToString("yyyy-MM-dd")} {duty.StartTimeMorning}") ? DateTime.Parse(d.CheckIn.ToString("yyyy-MM-dd HH:mm")) > DateTime.Parse($"{d.CheckIn.ToString("yyyy-MM-dd")} {duty.EndTimeMorning}") ? "ABSENT" : "LATE" : "NORMAL", CheckInDescription = d.CheckInRemark ?? "", IsLocationCheckIn = d.IsLocationCheckIn, CheckInLocationName = d.CheckInLocationName ?? "", CheckOutDate = d.CheckOut == null ? null : d.CheckOut.Value.Date, CheckOutTime = d.CheckOut == null ? "" : d.CheckOut.Value.ToString("HH:mm"), CheckOutPOI = d.CheckOut == null ? "" : d.CheckOutPOI, CheckOutLat = d.CheckOut == null ? null : d.CheckOutLat, CheckOutLon = d.CheckOut == null ? null : d.CheckOutLon, CheckOutImg = d.CheckOut == null ? "" : await _minIOService.ImagesPathByName(d.CheckOutImageUrl), CheckOutStatus = d.CheckOut == null ? null : d.CheckOutStatus != null || d.CheckOutStatus != "" ? d.CheckOutStatus : DateTime.Parse(d.CheckOut.Value.ToString("yyyy-MM-dd HH:mm")) < DateTime.Parse($"{d.CheckIn.Date.ToString("yyyy-MM-dd")} {duty.EndTimeAfternoon}") ? "ABSENT" : DateTime.Parse(d.CheckOut.Value.ToString("yyyy-MM-dd HH:mm")) < DateTime.Parse($"{d.CheckIn.Date.ToString("yyyy-MM-dd")} {duty.EndTimeMorning}") ? "ABSENT" : "NORMAL", CheckOutDescription = d.CheckOutRemark ?? "", IsLocationCheckOut = d.IsLocationCheckOut, CheckOutLocationName = d.CheckOutLocationName ?? "" }; return Success(result); } } /// /// LV1_009 - รายการลงเวลาปฏิบัติงานที่ประมวลผลแล้ว (ADMIN) /// /// /// /// เมื่อทำรายการสำเร็จ /// ไม่ได้ Login เข้าระบบ /// เมื่อเกิดข้อผิดพลาดในการทำงาน [HttpGet("time-record")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] [AllowAnonymous] public async Task> GetTimeRecordAsync([Required] DateTime startDate, [Required] DateTime endDate, int page = 1, int pageSize = 10, string status = "NORMAL", string keyword = "", string profileType = "ALL", string? sortBy = "", bool? descending = false) { var getPermission = await _permission.GetPermissionAPIAsync("LIST", "SYS_CHECKIN"); var jsonData = JsonConvert.DeserializeObject(getPermission); if (jsonData["status"]?.ToString() != "200") { return Error(jsonData["message"]?.ToString(), StatusCodes.Status403Forbidden); } if (startDate.Date > endDate.Date) { return Error(new Exception("วันเริ่มต้นต้องมีค่าน้อยกว่าหรือเท่ากับวันสิ้นสุด"), StatusCodes.Status400BadRequest); } var profiles = new List(); //var userId = UserId == null ? Guid.Empty : Guid.Parse(UserId); //var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(userId, AccessToken); //if (profile == null) //{ // return Error(GlobalMessages.DataNotFound, StatusCodes.Status404NotFound); //} var defaultRound = await _dutyTimeRepository.GetDefaultAsync(); if (defaultRound == null) { return Error("ไม่พบรอบการลงเวลา Default", StatusCodes.Status404NotFound); } //var userRound = await _dutyTimeRepository.GetByIdAsync(profile.DutyTimeId ?? Guid.Empty); //var effectiveDate = await _userDutyTimeRepository.GetLastEffectRound(profile.Id); //var roundId = effectiveDate != null ? effectiveDate.DutyTimeId : Guid.Empty; //var userRound = await _dutyTimeRepository.GetByIdAsync(roundId); //var duty = userRound ?? defaultRound; var duty = defaultRound; var checkin_base = DateTime.Parse($"{DateTime.Now.ToString("yyyy-MM-dd")} {duty.StartTimeMorning}"); var checkout_base = DateTime.Parse($"{DateTime.Now.ToString("yyyy-MM-dd")} {duty.EndTimeAfternoon}"); //var count = await _processUserTimeStampRepository.GetTimeStampHistoryForAdminCountAsync(startDate, endDate); var imgUrl = $"{_configuration["MinIO:Endpoint"]}{_configuration["MinIO:BucketName"]}"; string role = jsonData["result"]?.ToString(); var nodeId = string.Empty; var profileAdmin = new GetUserOCAllDto(); profileAdmin = await _userProfileRepository.GetUserOCAll(Guid.Parse(UserId!), AccessToken); if (role == "NORMAL" || role == "CHILD") { nodeId = profileAdmin?.Node == 4 ? profileAdmin?.Child4DnaId : profileAdmin?.Node == 3 ? profileAdmin?.Child3DnaId : profileAdmin?.Node == 2 ? profileAdmin?.Child2DnaId : profileAdmin?.Node == 1 ? profileAdmin?.Child1DnaId : profileAdmin?.Node == 0 ? profileAdmin?.RootDnaId : ""; } else if (role == "BROTHER") { nodeId = profileAdmin?.Node == 4 ? profileAdmin?.Child3DnaId : profileAdmin?.Node == 3 ? profileAdmin?.Child2DnaId : profileAdmin?.Node == 2 ? profileAdmin?.Child1DnaId : profileAdmin?.Node == 1 || profileAdmin?.Node == 0 ? profileAdmin?.RootDnaId : ""; } else if (role == "ROOT" /*|| role == "PARENT"*/) { nodeId = profileAdmin?.RootDnaId; } //var resultData = await _processUserTimeStampRepository.GetTimeStampHistoryForAdminAsync(startDate, endDate); var resultData = await _processUserTimeStampRepository.GetTimeStampHistoryForAdminRoleAsync(startDate, endDate, role, nodeId, profileAdmin?.Node); var data = new List(); foreach (var d in resultData) { //var pf = profiles.FirstOrDefault(x => x.Keycloak == d.KeycloakUserId); //if (pf == null) //{ // pf = await _userProfileRepository.GetProfileByKeycloakIdAsync(d.KeycloakUserId, AccessToken); // if (pf == null) // continue; // else // profiles.Add(pf); //} //var pf = await _userProfileRepository.GetProfileByKeycloakIdAsync(d.KeycloakUserId, AccessToken); //if (pf == null) continue; data.Add(new CheckInProcessHistoryForAdminDto { Id = d.Id, FullName = $"{d.Prefix ?? ""}{d.FirstName ?? ""} {d.LastName ?? ""}", Prefix = d.Prefix ?? "", FirstName = d.FirstName ?? "", LastName = d.LastName ?? "", ProfileType = d.ProfileType ?? "", CheckInDate = d.CheckIn.Date, CheckInTime = d.CheckIn.ToString("HH:mm"), CheckInLocation = d.CheckInPOI, CheckInLat = d.CheckInLat, CheckInLon = d.CheckInLon, CheckInStatus = d.CheckInStatus != "" ? d.CheckInStatus : DateTime.Parse(d.CheckIn.ToString("yyyy-MM-dd HH:mm")) > DateTime.Parse($"{d.CheckIn.Date.ToString("yyyy-MM-dd")} {duty.StartTimeMorning}") ? DateTime.Parse(d.CheckIn.ToString("yyyy-MM-dd HH:mm")) > DateTime.Parse($"{d.CheckIn.ToString("yyyy-MM-dd")} {duty.EndTimeMorning}") ? "ABSENT" : "LATE" : "NORMAL", CheckInIsLocation = d.IsLocationCheckIn, CheckInLocationName = d.CheckInLocationName ?? "", //CheckInImageUrl = $"{imgUrl}/{d.CheckInImageUrl}", CheckOutDate = d.CheckOut?.Date, CheckOutTime = d.CheckOut == null ? "" : d.CheckOut.Value.ToString("HH:mm"), CheckOutLocation = d.CheckOut == null ? "" : d.CheckOutPOI, CheckOutLat = d.CheckOut == null ? null : d.CheckOutLat, CheckOutLon = d.CheckOut == null ? null : d.CheckOutLon, CheckOutStatus = d.CheckOutStatus != "" ? d.CheckOutStatus : d.CheckOut == null ? null : DateTime.Parse(d.CheckOut.Value.ToString("yyyy-MM-dd HH:mm")) < DateTime.Parse($"{d.CheckIn.Date.ToString("yyyy-MM-dd")} {duty.EndTimeAfternoon}") ? "ABSENT" : DateTime.Parse(d.CheckOut.Value.ToString("yyyy-MM-dd HH:mm")) < DateTime.Parse($"{d.CheckIn.Date.ToString("yyyy-MM-dd")} {duty.EndTimeMorning}") ? "ABSENT" : "NORMAL", CheckOutIsLocation = d.IsLocationCheckOut, CheckOutLocationName = d.CheckOutLocationName ?? "" //CheckOutImageUrl = d.CheckOut == null ? "" : $"{imgUrl}/{d.CheckOutImageUrl}", }); } // var data = (await _processUserTimeStampRepository.GetTimeStampHistoryForAdminAsync(startDate, endDate)) // .Select(d => new CheckInProcessHistoryForAdminDto // { // Id = d.Id, // FullName = _userProfileRepository.GetUserFullName(d.KeycloakUserId, AccessToken), // CheckInDate = d.CheckIn.Date, // CheckInTime = d.CheckIn.ToString("HH:mm"), // CheckInLocation = d.CheckInPOI, // CheckInLat = d.CheckInLat, // CheckInLon = d.CheckInLon, // CheckInStatus = d.CheckInStatus != "" ? d.CheckInStatus : // DateTime.Parse(d.CheckIn.ToString("yyyy-MM-dd HH:mm")) > // DateTime.Parse($"{d.CheckIn.Date.ToString("yyyy-MM-dd")} {duty.StartTimeMorning}") ? // DateTime.Parse(d.CheckIn.ToString("yyyy-MM-dd HH:mm")) > // DateTime.Parse($"{d.CheckIn.ToString("yyyy-MM-dd")} {duty.EndTimeMorning}") ? // "ABSENT" : // "LATE" : // "NORMAL", // CheckInIsLocation = d.IsLocationCheckIn, // CheckInLocationName = d.CheckInLocationName, // //CheckInImageUrl = $"{imgUrl}/{d.CheckInImageUrl}", // CheckOutDate = d.CheckOut == null ? null : d.CheckOut.Value.Date, // CheckOutTime = d.CheckOut == null ? "" : d.CheckOut.Value.ToString("HH:mm"), // CheckOutLocation = d.CheckOut == null ? "" : d.CheckOutPOI, // CheckOutLat = d.CheckOut == null ? null : d.CheckOutLat, // CheckOutLon = d.CheckOut == null ? null : d.CheckOutLon, // CheckOutStatus = d.CheckOutStatus != "" ? d.CheckOutStatus : // d.CheckOut == null ? null : DateTime.Parse(d.CheckOut.Value.ToString("yyyy-MM-dd HH:mm")) < // DateTime.Parse($"{d.CheckIn.Date.ToString("yyyy-MM-dd")} {duty.EndTimeAfternoon}") ? // "ABSENT" : // DateTime.Parse(d.CheckOut.Value.ToString("yyyy-MM-dd HH:mm")) < // DateTime.Parse($"{d.CheckIn.Date.ToString("yyyy-MM-dd")} {duty.EndTimeMorning}") ? // "ABSENT" : // "NORMAL", // CheckOutIsLocation = d.IsLocationCheckOut, // CheckOutLocationName = d.CheckOutLocationName // //CheckOutImageUrl = d.CheckOut == null ? "" : $"{imgUrl}/{d.CheckOutImageUrl}", // }) // .ToList(); if (keyword != "") { data = data.Where(x => x.FullName.Contains(keyword)).ToList(); } if (status.Trim().ToUpper() != "ALL") { data = data.Where(x => x.CheckInStatus == status || x.CheckOutStatus == status).ToList(); } if (profileType.Trim().ToUpper() != "ALL") data = data.Where(x => x.ProfileType == profileType.Trim().ToUpper()).ToList(); if (!string.IsNullOrWhiteSpace(sortBy)) { switch (sortBy.ToUpper()) { case "FULLNAME": if (descending == true) data = data.OrderByDescending(x => x.Prefix) .ThenByDescending(x => x.FirstName) .ThenByDescending(x => x.LastName) .ToList(); else data = data.OrderBy(x => x.Prefix) .ThenBy(x => x.FirstName) .ThenBy(x => x.LastName) .ToList(); break; case "CHECKINTIME": if (descending == true) data = data.OrderByDescending(x => x.CheckInTime).ToList(); else data = data.OrderBy(x => x.CheckInTime).ToList(); break; case "CHECKINLOCATION": if (descending == true) data = data.OrderByDescending(x => x.CheckInLocation) .ThenByDescending(x => x.CheckInLat) .ThenByDescending(x => x.CheckInLon) .ToList(); else data = data.OrderBy(x => x.CheckInLocation) .ThenBy(x => x.CheckInLat) .ThenBy(x => x.CheckInLon) .ToList(); break; case "CHECKOUTTIME": if (descending == true) data = data.OrderByDescending(x => x.CheckOutTime).ToList(); else data = data.OrderBy(x => x.CheckOutTime).ToList(); break; case "CHECKOUTLOCATION": if (descending == true) data = data.OrderByDescending(x => x.CheckOutLocation) .ThenByDescending(x => x.CheckOutLat) .ThenByDescending(x => x.CheckOutLon) .ToList(); else data = data.OrderBy(x => x.CheckOutLocation) .ThenBy(x => x.CheckOutLat) .ThenBy(x => x.CheckOutLon) .ToList(); break; default: break; } } var pageData = data .Skip((page - 1) * pageSize) .Take(pageSize) .ToList(); return Success(new { data = pageData, total = data.Count }); } #endregion #region " เปลี่ยนรอบการทำงาน " /// /// LV1_006 - เช็คเวลาต้องลงเวลาเข้าหรือออกงาน (USER) /// /// /// /// เมื่อทำรายการสำเร็จ /// ไม่ได้ Login เข้าระบบ /// เมื่อเกิดข้อผิดพลาดในการทำงาน [HttpPost("search")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] public async Task> SearchProfileAsync([FromBody] DTOs.ChangeRound.SearchProfileDto req) { var getPermission = await _permission.GetPermissionAPIAsync("LIST", "SYS_WORK_ROUND_EDIT"); var jsonData = JsonConvert.DeserializeObject(getPermission); if (jsonData["status"]?.ToString() != "200") { return Error(jsonData["message"]?.ToString(), StatusCodes.Status403Forbidden); } string role = jsonData["result"]?.ToString(); var nodeId = string.Empty; var profileAdmin = new GetUserOCAllDto(); profileAdmin = await _userProfileRepository.GetUserOCAll(Guid.Parse(UserId!), AccessToken); if (role == "NORMAL" || role == "CHILD") { nodeId = profileAdmin?.Node == 4 ? profileAdmin?.Child4DnaId : profileAdmin?.Node == 3 ? profileAdmin?.Child3DnaId : profileAdmin?.Node == 2 ? profileAdmin?.Child2DnaId : profileAdmin?.Node == 1 ? profileAdmin?.Child1DnaId : profileAdmin?.Node == 0 ? profileAdmin?.RootDnaId : ""; } else if (role == "BROTHER") { nodeId = profileAdmin?.Node == 4 ? profileAdmin?.Child3DnaId : profileAdmin?.Node == 3 ? profileAdmin?.Child2DnaId : profileAdmin?.Node == 2 ? profileAdmin?.Child1DnaId : profileAdmin?.Node == 1 || profileAdmin?.Node == 0 ? profileAdmin?.RootDnaId : ""; } else if (role == "ROOT" /*|| role == "PARENT"*/) { nodeId = profileAdmin?.RootDnaId; } var profile = await _userProfileRepository.SearchProfile(req.CitizenId, req.FirstName, req.LastName, AccessToken ?? "", req.Page, req.PageSize, role, nodeId, profileAdmin?.Node, req.SelectedNodeId == null ? null : req.SelectedNodeId.Value.ToString("D"), req.SelectedNode); // Get default round once var getDefaultRound = await _dutyTimeRepository.GetDefaultAsync(); var resultSet = new List(); // Create dictionaries to cache results and avoid duplicate queries var effectiveDateCache = new Dictionary(); var dutyTimeCache = new Dictionary(); foreach (var p in profile.Data) { // Use cache for effective date if (!effectiveDateCache.ContainsKey(p.Id)) { effectiveDateCache[p.Id] = await _userDutyTimeRepository.GetLastEffectRound(p.Id); } var effectiveDate = effectiveDateCache[p.Id]; var roundId = effectiveDate != null ? effectiveDate.DutyTimeId : Guid.Empty; // Use cache for duty time DutyTime? userRound = null; if (roundId != Guid.Empty) { if (!dutyTimeCache.ContainsKey(roundId)) { dutyTimeCache[roundId] = await _dutyTimeRepository.GetByIdAsync(roundId); } userRound = dutyTimeCache[roundId]; } var duty = userRound ?? getDefaultRound; if (duty == null) continue; // Skip if no duty time found var res = new SearchProfileResultDto { ProfileId = p.Id, CitizenId = p.CitizenId ?? "", FullName = $"{p.Prefix ?? ""}{p.FirstName ?? ""} {p.LastName ?? ""}", StartTimeMorning = duty.StartTimeMorning, LeaveTimeAfterNoon = duty.EndTimeAfternoon, EffectiveDate = effectiveDate?.EffectiveDate?.Date, Prefix = p.Prefix ?? "", FirstName = p.FirstName ?? "", LastName = p.LastName ?? "", RootDnaId = p.RootDnaId, Child1DnaId = p.Child1DnaId, Child2DnaId = p.Child2DnaId, Child3DnaId = p.Child3DnaId, Child4DnaId = p.Child4DnaId }; resultSet.Add(res); } return Success(new { data = resultSet, total = profile.Total }); } /// /// LV1_014 - เปลี่ยนรอบการลงเวลา (ADMIN) /// /// /// /// เมื่อทำรายการสำเร็จ /// ไม่ได้ Login เข้าระบบ /// เมื่อเกิดข้อผิดพลาดในการทำงาน [HttpPost("round")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] public async Task> CreateChangeRoundAsync([FromBody] CreateChangeRoundDto req) { var getPermission = await _permission.GetPermissionAPIAsync("UPDATE", "SYS_WORK_ROUND_EDIT"); var jsonData = JsonConvert.DeserializeObject(getPermission); if (jsonData["status"]?.ToString() != "200") { return Error(jsonData["message"]?.ToString(), StatusCodes.Status403Forbidden); } var currentDate = DateTime.Now.Date; if (req.EffectiveDate.Date < currentDate) { return Error(new Exception($"วันที่มีผลต้องมากกว่าหรือเท่ากับวันที่ปัจจุบัน({currentDate.ToString("yyyy-MM-dd")})"), StatusCodes.Status400BadRequest); } var old = await _userDutyTimeRepository.GetExist(req.ProfileId, req.EffectiveDate); var profile = await _userProfileRepository.GetProfileByProfileIdAsync(req.ProfileId, AccessToken); if (profile == null) { return Error(GlobalMessages.DataNotFound, StatusCodes.Status404NotFound); } if (old != null) { return Error(new Exception("ไม่สามารถทำรายการได้ เนื่องจากมีการกำหนดรอบการทำงานในวันที่นี้ไว้แล้ว"), StatusCodes.Status400BadRequest); } var data = new UserDutyTime { ProfileId = req.ProfileId, DutyTimeId = req.RoundId, EffectiveDate = req.EffectiveDate, Remark = req.Remark, RootDnaId = profile.RootDnaId, Child1DnaId = profile.Child1DnaId, Child2DnaId = profile.Child2DnaId, Child3DnaId = profile.Child3DnaId, Child4DnaId = profile.Child4DnaId, }; await _userDutyTimeRepository.AddAsync(data); return Success(); } [HttpPost("round/multiple")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] public async Task> CreateChangeRoundMultipleAsync([FromBody] List reqs) { var getPermission = await _permission.GetPermissionAPIAsync("UPDATE", "SYS_WORK_ROUND_EDIT"); var jsonData = JsonConvert.DeserializeObject(getPermission); if (jsonData["status"]?.ToString() != "200") { return Error(jsonData["message"]?.ToString(), StatusCodes.Status403Forbidden); } var currentDate = DateTime.Now.Date; List dataList = new List(); foreach(var req in reqs) { // var profile = await _userProfileRepository.GetProfileByProfileIdAsync(req.ProfileId, AccessToken); // if (profile == null) // { // return Error(GlobalMessages.DataNotFound, StatusCodes.Status404NotFound); // } if (req.EffectiveDate.Date < currentDate) { continue; // move to next item if effective date is in the past, not return error // return Error(new Exception($"กำหนดรอบลงเวลาของ {req.FirstName} {req.LastName} ผิดพลาด เนื่องจากวันที่มีผลต้องมากกว่าหรือเท่ากับวันที่ปัจจุบัน({currentDate.ToString("yyyy-MM-dd")})"), StatusCodes.Status400BadRequest); } var old = await _userDutyTimeRepository.GetExist(req.ProfileId, req.EffectiveDate); if (old != null) { continue; // move to next item if already exist, not return error //return Error(new Exception($"กำหนดรอบลงเวลาของ {req.FirstName} {req.LastName} ผิดพลาด เนื่องจากมีการกำหนดรอบการทำงานในวันที่นี้ไว้แล้ว"), StatusCodes.Status400BadRequest); } var data = new UserDutyTime { ProfileId = req.ProfileId, DutyTimeId = req.RoundId, EffectiveDate = req.EffectiveDate, Remark = req.Remark, RootDnaId = req.RootDnaId, Child1DnaId = req.Child1DnaId, Child2DnaId = req.Child2DnaId, Child3DnaId = req.Child3DnaId, Child4DnaId = req.Child4DnaId, }; dataList.Add(data); } await _userDutyTimeRepository.AddRangeAsync(dataList); return Success(); } /// /// LV1_015 - ประวัติการเปลี่ยนรอบการลงเวลา (ADMIN) /// /// /// /// เมื่อทำรายการสำเร็จ /// ไม่ได้ Login เข้าระบบ /// เมื่อเกิดข้อผิดพลาดในการทำงาน [HttpGet("round/{id:guid}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] public async Task> GetChangeRoundHistoryByProfileIdAsync(Guid id, int page = 1, int pageSize = 10, string keyword = "", string? sortBy = "", bool? descending = false) { var getWorkflow = await _permission.GetPermissionAPIWorkflowAsync(id.ToString(), "SYS_WORK_ROUND_EDIT"); if (getWorkflow == false) { var getPermission = await _permission.GetPermissionAPIAsync("GET", "SYS_WORK_ROUND_EDIT"); var jsonData = JsonConvert.DeserializeObject(getPermission); if (jsonData["status"]?.ToString() != "200") { return Error(jsonData["message"]?.ToString(), StatusCodes.Status403Forbidden); } } var data = await _userDutyTimeRepository.GetListByProfileIdAsync(id); var resultSet = new List(); if (data != null) { resultSet = data .GroupBy(item => item.ProfileId) .SelectMany(group => group //.OrderBy(item => item.EffectiveDate) // เรียงลำดับตาม property ที่คุณต้องการ .Select((item, index) => new ChangeRoundHistoryDto { Round = index + 1, StartTimeMorning = item.DutyTime.StartTimeMorning, LeaveTimeAfternoon = item.DutyTime.EndTimeAfternoon, EffectiveDate = item.EffectiveDate.Value, Remark = item.Remark })) //.Skip((page - 1) * pageSize) //.Take(pageSize) .ToList(); if (!string.IsNullOrWhiteSpace(sortBy)) { switch (sortBy.ToUpper()) { case "ROUNT": if (descending == true) resultSet = resultSet.OrderByDescending(x => x.Round).ToList(); else resultSet = resultSet.OrderBy(x => x.Round).ToList(); break; case "STARTTIMEMORNIONG": if (descending == true) resultSet = resultSet.OrderByDescending(x => x.StartTimeMorning).ToList(); else resultSet = resultSet.OrderBy(x => x.StartTimeMorning).ToList(); break; case "EFFECTIVEDATE": if (descending == true) resultSet = resultSet.OrderByDescending(x => x.EffectiveDate).ToList(); else resultSet = resultSet.OrderBy(x => x.EffectiveDate).ToList(); break; case "REMARK": if (descending == true) resultSet = resultSet.OrderByDescending(x => x.Remark).ToList(); else resultSet = resultSet.OrderBy(x => x.Remark).ToList(); break; default: break; } } resultSet = resultSet .Skip((page - 1) * pageSize) .Take(pageSize) .ToList(); } return Success(new { data = resultSet, total = data.Count }); } #endregion #region " เปลี่ยนรอบการทำงาน ลจ. " /// /// LV1_006 - เช็คเวลาต้องลงเวลาเข้าหรือออกงาน (USER) ลจ. /// /// /// /// เมื่อทำรายการสำเร็จ /// ไม่ได้ Login เข้าระบบ /// เมื่อเกิดข้อผิดพลาดในการทำงาน [HttpPost("emp/search")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] public async Task> SearchEmpProfileAsync([FromBody] DTOs.ChangeRound.SearchProfileDto req) { var getPermission = await _permission.GetPermissionAPIAsync("LIST", "SYS_WORK_ROUND_EDIT_EMP"); var jsonData = JsonConvert.DeserializeObject(getPermission); if (jsonData["status"]?.ToString() != "200") { return Error(jsonData["message"]?.ToString(), StatusCodes.Status403Forbidden); } string role = jsonData["result"]?.ToString(); var nodeId = string.Empty; var profileAdmin = new GetUserOCAllDto(); profileAdmin = await _userProfileRepository.GetUserOCAll(Guid.Parse(UserId!), AccessToken); if (role == "NORMAL" || role == "CHILD") { nodeId = profileAdmin?.Node == 4 ? profileAdmin?.Child4DnaId : profileAdmin?.Node == 3 ? profileAdmin?.Child3DnaId : profileAdmin?.Node == 2 ? profileAdmin?.Child2DnaId : profileAdmin?.Node == 1 ? profileAdmin?.Child1DnaId : profileAdmin?.Node == 0 ? profileAdmin?.RootDnaId : ""; } else if (role == "BROTHER") { nodeId = profileAdmin?.Node == 4 ? profileAdmin?.Child3DnaId : profileAdmin?.Node == 3 ? profileAdmin?.Child2DnaId : profileAdmin?.Node == 2 ? profileAdmin?.Child1DnaId : profileAdmin?.Node == 1 || profileAdmin?.Node == 0 ? profileAdmin?.RootDnaId : ""; } else if (role == "ROOT" /*|| role == "PARENT"*/) { nodeId = profileAdmin?.RootDnaId; } var profile = await _userProfileRepository.SearchProfileEmployee(req.CitizenId, req.FirstName, req.LastName, AccessToken ?? "", req.Page, req.PageSize, role, nodeId, profileAdmin?.Node); // Get default round once var getDefaultRound = await _dutyTimeRepository.GetDefaultAsync(); var resultSet = new List(); // Create dictionaries to cache results and avoid duplicate queries var effectiveDateCache = new Dictionary(); var dutyTimeCache = new Dictionary(); foreach (var p in profile.Data) { // Use cache for effective date if (!effectiveDateCache.ContainsKey(p.Id)) { effectiveDateCache[p.Id] = await _userDutyTimeRepository.GetLastEffectRound(p.Id); } var effectiveDate = effectiveDateCache[p.Id]; var roundId = effectiveDate != null ? effectiveDate.DutyTimeId : Guid.Empty; // Use cache for duty time DutyTime? userRound = null; if (roundId != Guid.Empty) { if (!dutyTimeCache.ContainsKey(roundId)) { dutyTimeCache[roundId] = await _dutyTimeRepository.GetByIdAsync(roundId); } userRound = dutyTimeCache[roundId]; } var duty = userRound ?? getDefaultRound; if (duty == null) continue; // Skip if no duty time found var res = new SearchProfileResultDto { ProfileId = p.Id, CitizenId = p.CitizenId ?? "", FullName = $"{p.Prefix ?? ""}{p.FirstName ?? ""} {p.LastName ?? ""}", StartTimeMorning = duty.StartTimeMorning, LeaveTimeAfterNoon = duty.EndTimeAfternoon, EffectiveDate = effectiveDate?.EffectiveDate?.Date }; resultSet.Add(res); } return Success(new { data = resultSet, total = profile.Total }); } /// /// LV1_014 - เปลี่ยนรอบการลงเวลา (ADMIN) Employee /// /// /// /// เมื่อทำรายการสำเร็จ /// ไม่ได้ Login เข้าระบบ /// เมื่อเกิดข้อผิดพลาดในการทำงาน [HttpPost("emp/round")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] public async Task> CreateChangeEmpRoundAsync([FromBody] CreateChangeRoundDto req) { var getPermission = await _permission.GetPermissionAPIAsync("UPDATE", "SYS_WORK_ROUND_EDIT"); var jsonData = JsonConvert.DeserializeObject(getPermission); if (jsonData["status"]?.ToString() != "200") { return Error(jsonData["message"]?.ToString(), StatusCodes.Status403Forbidden); } var currentDate = DateTime.Now.Date; if (req.EffectiveDate.Date < currentDate) { return Error(new Exception($"วันที่มีผลต้องมากกว่าหรือเท่ากับวันที่ปัจจุบัน({currentDate.ToString("yyyy-MM-dd")})"), StatusCodes.Status400BadRequest); } var old = await _userDutyTimeRepository.GetExist(req.ProfileId, req.EffectiveDate); var profile = await _userProfileRepository.GetProfileByProfileIdAsync(req.ProfileId, AccessToken); if (profile == null) { return Error(GlobalMessages.DataNotFound, StatusCodes.Status404NotFound); } if (old != null) { return Error(new Exception("ไม่สามารถทำรายการได้ เนื่องจากมีการกำหนดรอบการทำงานในวันที่นี้ไว้แล้ว"), StatusCodes.Status400BadRequest); } var data = new UserDutyTime { ProfileId = req.ProfileId, DutyTimeId = req.RoundId, EffectiveDate = req.EffectiveDate, Remark = req.Remark, RootDnaId = profile.RootDnaId, Child1DnaId = profile.Child1DnaId, Child2DnaId = profile.Child2DnaId, Child3DnaId = profile.Child3DnaId, Child4DnaId = profile.Child4DnaId, }; await _userDutyTimeRepository.AddAsync(data); return Success(); } /// /// LV1_015 - ประวัติการเปลี่ยนรอบการลงเวลา (ADMIN) Employee /// /// /// /// เมื่อทำรายการสำเร็จ /// ไม่ได้ Login เข้าระบบ /// เมื่อเกิดข้อผิดพลาดในการทำงาน [HttpGet("emp/round/{id:guid}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] public async Task> GetChangeEmpRoundHistoryByProfileIdAsync(Guid id, int page = 1, int pageSize = 10, string keyword = "", string? sortBy = "", bool? descending = false) { var getWorkflow = await _permission.GetPermissionAPIWorkflowAsync(id.ToString(), "SYS_WORK_ROUND_EDIT"); if (getWorkflow == false) { var getPermission = await _permission.GetPermissionAPIAsync("GET", "SYS_WORK_ROUND_EDIT"); var jsonData = JsonConvert.DeserializeObject(getPermission); if (jsonData["status"]?.ToString() != "200") { return Error(jsonData["message"]?.ToString(), StatusCodes.Status403Forbidden); } } var data = await _userDutyTimeRepository.GetListByProfileIdAsync(id); var resultSet = new List(); if (data != null) { resultSet = data .GroupBy(item => item.ProfileId) .SelectMany(group => group //.OrderBy(item => item.EffectiveDate) // เรียงลำดับตาม property ที่คุณต้องการ .Select((item, index) => new ChangeRoundHistoryDto { Round = index + 1, StartTimeMorning = item.DutyTime.StartTimeMorning, LeaveTimeAfternoon = item.DutyTime.EndTimeAfternoon, EffectiveDate = item.EffectiveDate.Value, Remark = item.Remark })) //.Skip((page - 1) * pageSize) //.Take(pageSize) .ToList(); if (!string.IsNullOrWhiteSpace(sortBy)) { switch (sortBy.ToUpper()) { case "ROUNT": if (descending == true) resultSet = resultSet.OrderByDescending(x => x.Round).ToList(); else resultSet = resultSet.OrderBy(x => x.Round).ToList(); break; case "STARTTIMEMORNIONG": if (descending == true) resultSet = resultSet.OrderByDescending(x => x.StartTimeMorning).ToList(); else resultSet = resultSet.OrderBy(x => x.StartTimeMorning).ToList(); break; case "EFFECTIVEDATE": if (descending == true) resultSet = resultSet.OrderByDescending(x => x.EffectiveDate).ToList(); else resultSet = resultSet.OrderBy(x => x.EffectiveDate).ToList(); break; case "REMARK": if (descending == true) resultSet = resultSet.OrderByDescending(x => x.Remark).ToList(); else resultSet = resultSet.OrderBy(x => x.Remark).ToList(); break; default: break; } } resultSet = resultSet .Skip((page - 1) * pageSize) .Take(pageSize) .ToList(); } return Success(new { data = resultSet, total = data.Count }); } #endregion #region " Check Checkout Time " /// /// ตรวจสอบว่าเวลาปัจจุบัน ถ้า checkout จะขาดราชการหรือไม่? /// /// /// /// เมื่อทำรายการสำเร็จ /// ไม่ได้ Login เข้าระบบ /// เมื่อเกิดข้อผิดพลาดในการทำงาน [HttpGet("user/checkout-check/{isSeminar}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] public async Task> CheckoutCheckAsync(string isSeminar = "N") { var time = DateTime.Now; var userId = UserId != null ? Guid.Parse(UserId) : Guid.Empty; var profile = await _userProfileRepository.GetProfileByKeycloakIdNew2Async(userId, AccessToken); if (profile == null) { throw new Exception(GlobalMessages.DataNotFound); } var getDefaultRound = await _dutyTimeRepository.GetDefaultAsync(); if (getDefaultRound == null) { return Error("ไม่พบรอบลงเวลา Default", StatusCodes.Status404NotFound); } var effectiveDate = await _userDutyTimeRepository.GetLastEffectRound(profile.Id); var roundId = effectiveDate != null ? effectiveDate.DutyTimeId : Guid.Empty; var userRound = await _dutyTimeRepository.GetByIdAsync(roundId); var duty = userRound ?? getDefaultRound; var lastCheckIn = await _context.Set() .Where(u => u.KeycloakUserId == userId) .Where(d => d.CheckOut == null) .OrderByDescending(u => u.CheckIn) .FirstOrDefaultAsync(); //var endTime = DateTimeOffset.Parse($"{DateTime.Now.Date.ToString("yyyy-MM-dd")}T{duty.EndTimeAfternoon}:00.0000000+07:00").ToLocalTime().DateTime;  //var endTime = DateTime.Parse($"{DateTime.Now.Date.ToString("yyyy-MM-dd")}T{duty.EndTimeAfternoon}:00.0000000+07:00"); var endTime = isSeminar.Trim().ToUpper() == "Y" ? DateTime.Parse($"{DateTime.Now.Date.ToString("yyyy-MM-dd")} 14:30") : DateTime.Parse($"{DateTime.Now.Date.ToString("yyyy-MM-dd")} {duty.EndTimeAfternoon}"); var endTimeMorning = DateTime.Parse($"{DateTime.Now.Date.ToString("yyyy-MM-dd")} {duty.EndTimeMorning}"); var endTimeDisplay = endTime; var status = string.Empty; if(lastCheckIn == null) { status = "ABSENT"; } else if (lastCheckIn.CheckIn.Date < DateTime.Now.Date) { status = "NORMAL"; } else { if (time < endTime) { //string checkOutStatus = "NORMAL"; var leaveReq = await _leaveRequestRepository.GetLeavePeriodAsync(userId, time.Date); if (leaveReq != null) { var leaveRange = leaveReq.LeaveRangeEnd == null ? "" : leaveReq.LeaveRangeEnd.ToUpper(); if (leaveRange == "AFTERNOON" || leaveRange == "ALL") { if(time < endTimeMorning) { status = "ABSENT"; endTimeDisplay = endTimeMorning; } else { status = "NORMAL"; } } else { status = "ABSENT"; } } else { status = "ABSENT"; } } else { status = "NORMAL"; } } //var status = lastCheckIn == null ? "ABSENT" : lastCheckIn.CheckIn.Date < DateTime.Now.Date ? "NORMAL" : time < endTime ? "ABSENT" : "NORMAL"; return Success(new { Status = status, StatusText = status == "ABSENT" ? "ขาดราชการ" : "ปกติ", ServerTime = time, EndTime = endTimeDisplay }); } #endregion #region " ขอลงเวลาเป็นกรณีพิเศษ " /// /// LV1_017 - เพิ่มลงเวลากรณีพิเศษ (USER) /// /// /// /// เมื่อทำรายการสำเร็จ /// ไม่ได้ Login เข้าระบบ /// เมื่อเกิดข้อผิดพลาดในการทำงาน [HttpPost("user/edit")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] public async Task> CreateAdditionalCheckRequestAsync([FromBody] CreateAdditionalCheckRequestDto req) { if (req.CheckDate.Date > DateTime.Now.Date) { return Error("ไม่สามารถขอลงเวลากรณีพิเศษในวันที่มากกว่าวันที่ปัจจุบันได้", StatusCodes.Status400BadRequest); } var userId = UserId != null ? Guid.Parse(UserId) : Guid.Empty; var profile = await _userProfileRepository.GetProfileByKeycloakIdNew2Async(userId, AccessToken); if (profile == null) { throw new Exception(GlobalMessages.DataNotFound); } var request = new AdditionalCheckRequest { KeycloakUserId = UserId != null ? Guid.Parse(UserId) : Guid.Empty, CheckDate = req.CheckDate, CheckInEdit = req.CheckInEdit, CheckOutEdit = req.CheckOutEdit, Description = req.Description, Prefix = profile.Prefix, FirstName = profile.FirstName, LastName = profile.LastName, // fix issue #1547 POI = req.POI, Latitude = req.Latitude, Longitude = req.Longitude, // add all Dna Id RootDnaId = profile.RootDnaId ?? Guid.Empty, Child1DnaId = profile.Child1DnaId ?? Guid.Empty, Child2DnaId = profile.Child2DnaId ?? Guid.Empty, Child3DnaId = profile.Child3DnaId ?? Guid.Empty, Child4DnaId = profile.Child4DnaId ?? Guid.Empty, }; await _additionalCheckRequestRepository.AddAsync(request); return Success(); } /// /// LV1_018 - รายการลงเวลากรณีพิเศษ (ADMIN) /// /// /// /// เมื่อทำรายการสำเร็จ /// ไม่ได้ Login เข้าระบบ /// เมื่อเกิดข้อผิดพลาดในการทำงาน [HttpGet("admin/edit")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] public async Task> GetAdditionalCheckRequestAsync([Required] int year, [Required] int month, [Required] int page = 1, [Required] int pageSize = 10, string keyword = "", string? sortBy = "", bool? descending = false) { var jsonData = await _permission.GetPermissionWithActingAPIAsync("LIST", "SYS_CHECKIN_SPECIAL"); //var jsonData = JsonConvert.DeserializeObject(getPermission); if (jsonData!.status != 200) { return Error(jsonData.message, StatusCodes.Status403Forbidden); } //string role = jsonData["result"]?.ToString(); string role = jsonData.result.privilege; var nodeId = string.Empty; var profileAdmin = new GetUserOCAllDto(); profileAdmin = await _userProfileRepository.GetUserOCAll(Guid.Parse(UserId!), AccessToken); if (role == "NORMAL" || role == "CHILD") { nodeId = profileAdmin?.Node == 4 ? profileAdmin?.Child4DnaId : profileAdmin?.Node == 3 ? profileAdmin?.Child3DnaId : profileAdmin?.Node == 2 ? profileAdmin?.Child2DnaId : profileAdmin?.Node == 1 ? profileAdmin?.Child1DnaId : profileAdmin?.Node == 0 ? profileAdmin?.RootDnaId : ""; } else if (role == "BROTHER") { nodeId = profileAdmin?.Node == 4 ? profileAdmin?.Child3DnaId : profileAdmin?.Node == 3 ? profileAdmin?.Child2DnaId : profileAdmin?.Node == 2 ? profileAdmin?.Child1DnaId : profileAdmin?.Node == 1 || profileAdmin?.Node == 0 ? profileAdmin?.RootDnaId : ""; } else if (role == "ROOT" /*|| role == "PARENT"*/) { nodeId = profileAdmin?.RootDnaId; } //var rawData = await _additionalCheckRequestRepository.GetAdditionalCheckRequests(year, month); var rawData = await _additionalCheckRequestRepository.GetAdditionalCheckRequestsByAdminRole(year, month, role, nodeId, profileAdmin?.Node, keyword); // ถ้ามีการรักษาการ if (jsonData.result.isAct) { var posActs = jsonData.result.posMasterActs; foreach(var act in posActs) { var actRole = act.privilege; string actNodeId = string.Empty; int? actNode; if (role == "NORMAL" || role == "CHILD") { actNodeId = act.child4DnaId != null ? act.child4DnaId.Value.ToString("D") : act.child3DnaId != null ? act.child3DnaId.Value.ToString("D") : act.child2DnaId != null ? act.child2DnaId.Value.ToString("D") : act.child1DnaId != null ? act.child1DnaId.Value.ToString("D") : act.rootDnaId != null ? act.rootDnaId.Value.ToString("D") : ""; actNode = act.child4DnaId != null ? 4 : act.child3DnaId != null ? 3 : act.child2DnaId != null ? 2 : act.child1DnaId != null ? 1 : act.rootDnaId != null ? 0 : null; } else if (role == "BROTHER") { actNodeId = act.child3DnaId != null ? act.child3DnaId.Value.ToString("D") : act.child2DnaId != null ? act.child2DnaId.Value.ToString("D") : act.child1DnaId != null ? act.rootDnaId!.Value.ToString("D") : act.rootDnaId != null ? act.rootDnaId.Value.ToString("D") : ""; actNode = act.child4DnaId != null ? 4 : act.child3DnaId != null ? 4 : act.child2DnaId != null ? 3 : act.child1DnaId != null ? 2 : act.rootDnaId != null ? 0 : null; } else if (role == "ROOT" /*|| role == "PARENT"*/) { actNodeId = act.rootDnaId!.Value.ToString("D"); actNode = 0; } var rawDataAct = await _additionalCheckRequestRepository.GetAdditionalCheckRequestsByAdminRole(year, month, actRole, actNodeId, profileAdmin?.Node, keyword); if (rawDataAct != null) { if (rawData != null) rawData = rawData.Union(rawDataAct).ToList(); else rawData = rawDataAct; } } } var total = rawData.Count; var getDefaultRound = await _dutyTimeRepository.GetDefaultAsync(); if (getDefaultRound == null) { return Error("ไม่พบรอบลงเวลา Default", StatusCodes.Status404NotFound); } var result = new List(); if (!string.IsNullOrWhiteSpace(sortBy)) { switch (sortBy.ToUpper()) { case "FULLNAME": if (descending == true) rawData = rawData.OrderByDescending(x => x.Prefix) .ThenByDescending(x => x.FirstName) .ThenByDescending(x => x.LastName) .ToList(); else rawData = rawData.OrderBy(x => x.Prefix) .ThenBy(x => x.FirstName) .ThenBy(x => x.LastName) .ToList(); break; case "CREATEDAT": if (descending == true) rawData = rawData.OrderByDescending(x => x.CreatedAt).ToList(); else rawData = rawData.OrderBy(x => x.CreatedAt).ToList(); break; case "CHECKDATE": if (descending == true) rawData = rawData.OrderByDescending(x => x.CheckDate).ToList(); else rawData = rawData.OrderBy(x => x.CheckDate).ToList(); break; // case "STARTTIMEMORNING": // if (descending == true) // rawData = rawData.OrderByDescending(x => x.StartTimeMorning).ToList(); // else // result = result.OrderBy(x => x.StartTimeMorning).ToList(); // break; // case "STARTTIMEAFTERNOON": // if (descending == true) // result = result.OrderByDescending(x => x.StartTimeAfternoon).ToList(); // else // result = result.OrderBy(x => x.StartTimeAfternoon).ToList(); // break; case "DESCRIPTION": if (descending == true) rawData = rawData.OrderByDescending(x => x.Description).ToList(); else rawData = rawData.OrderBy(x => x.Description).ToList(); break; default: rawData = rawData.OrderBy(x => x.Status.Trim().ToLower() == "pending" ? 1 : x.Status.Trim().ToLower() == "approve" ? 2 : 3).ToList(); break; } } var rawDataPaged = rawData.Skip((page - 1) * pageSize).Take(pageSize) .ToList(); foreach (var data in rawDataPaged) { var profile = await _userProfileRepository.GetProfileByKeycloakIdNew2Async(data.KeycloakUserId, AccessToken); UserDutyTime? effectiveDate = null; if (profile != null) { effectiveDate = await _userDutyTimeRepository.GetLastEffectRound(profile.Id); //return Error($"{data.Id} PF{data.FirstName} {data.LastName} : {GlobalMessages.DataNotFound}", StatusCodes.Status404NotFound); } //var userRound = await _dutyTimeRepository.GetByIdAsync(profile.DutyTimeId ?? Guid.Empty); var roundId = effectiveDate != null ? effectiveDate.DutyTimeId : Guid.Empty; var userRound = await _dutyTimeRepository.GetByIdAsync(roundId); var checkInData = await _userTimeStampRepository.GetTimestampByDateAsync(data.KeycloakUserId, data.CheckDate); var duty = userRound ?? getDefaultRound; //var duty = getDefaultRound; // create result object to return var resObj = new GetAdditionalCheckRequestDto { Id = data.Id, FullName = $"{data.Prefix}{data.FirstName} {data.LastName}", Prefix = data.Prefix ?? "", FirstName = data.FirstName ?? "", LastName = data.LastName ?? "", //FullName = $"{profile.Prefix}{profile.FirstName} {profile.LastName}", CreatedAt = data.CreatedAt, CheckDate = data.CheckDate, CheckInEdit = data.CheckInEdit, CheckOutEdit = data.CheckOutEdit, CheckInTime = checkInData == null ? "00:00" : checkInData.CheckIn.ToString("HH:mm"), CheckOutTime = checkInData == null ? "00:00" : checkInData.CheckOut == null ? "00:00" : checkInData.CheckOut.Value.ToString("HH:mm"), CheckInStatus = checkInData == null ? null : DateTime.Parse(checkInData.CheckIn.ToString("yyyy-MM-dd HH:mm")) > DateTime.Parse($"{checkInData.CheckIn.Date.ToString("yyyy-MM-dd")} {duty.StartTimeMorning}") ? DateTime.Parse(checkInData.CheckIn.ToString("yyyy-MM-dd HH:mm")) > DateTime.Parse($"{checkInData.CheckIn.ToString("yyyy-MM-dd")} {duty.EndTimeMorning}") ? "ABSENT" : "LATE" : "NORMAL", CheckOutStatus = checkInData == null ? null : checkInData.CheckOut == null ? null : DateTime.Parse(checkInData.CheckOut.Value.ToString("yyyy-MM-dd HH:mm")) < DateTime.Parse($"{checkInData.CheckIn.Date.ToString("yyyy-MM-dd")} {duty.EndTimeAfternoon}") ? "ABSENT" : DateTime.Parse(checkInData.CheckOut.Value.ToString("yyyy-MM-dd HH:mm")) < DateTime.Parse($"{checkInData.CheckIn.Date.ToString("yyyy-MM-dd")} {duty.EndTimeMorning}") ? "ABSENT" : "NORMAL", StartTimeMorning = duty.StartTimeMorning, EndTimeMorning = duty.EndTimeMorning, StartTimeAfternoon = duty.StartTimeAfternoon, EndTimeAfternoon = duty.EndTimeAfternoon, Reason = data.Comment ?? "", Status = data.Status, Description = data.Description, StatusSort = data.Status.Trim().ToLower() == "pending" ? 1 : data.Status.Trim().ToLower() == "approve" ? 2 : 3, POI = data.POI, Latitude = data.Latitude, Longitude = data.Longitude, }; result.Add(resObj); } // if (keyword != "") // { // result = result.Where(x => x.FullName.Contains(keyword)).ToList(); // } // if (string.IsNullOrWhiteSpace(sortBy)) // { // sortBy = "default"; // } // if (!string.IsNullOrWhiteSpace(sortBy)) // { // switch (sortBy.ToUpper()) // { // case "FULLNAME": // if (descending == true) // result = result.OrderByDescending(x => x.Prefix) // .ThenByDescending(x => x.FirstName) // .ThenByDescending(x => x.LastName) // .ToList(); // else // result = result.OrderBy(x => x.Prefix) // .ThenBy(x => x.FirstName) // .ThenBy(x => x.LastName) // .ToList(); // break; // case "CREATEDAT": // if (descending == true) // result = result.OrderByDescending(x => x.CreatedAt).ToList(); // else // result = result.OrderBy(x => x.CreatedAt).ToList(); // break; // case "CHECKDATE": // if (descending == true) // result = result.OrderByDescending(x => x.CheckDate).ToList(); // else // result = result.OrderBy(x => x.CheckDate).ToList(); // break; // case "STARTTIMEMORNING": // if (descending == true) // result = result.OrderByDescending(x => x.StartTimeMorning).ToList(); // else // result = result.OrderBy(x => x.StartTimeMorning).ToList(); // break; // case "STARTTIMEAFTERNOON": // if (descending == true) // result = result.OrderByDescending(x => x.StartTimeAfternoon).ToList(); // else // result = result.OrderBy(x => x.StartTimeAfternoon).ToList(); // break; // case "DESCRIPTION": // if (descending == true) // result = result.OrderByDescending(x => x.Description).ToList(); // else // result = result.OrderBy(x => x.Description).ToList(); // break; // default: // result = result.OrderBy(x => x.StatusSort).ToList(); // break; // } // } // var pageResult = result.Skip((page - 1) * pageSize).Take(pageSize) // .ToList(); return Success(new { data = result, total = total }); } /// /// LV1_019 - อนุมัติลงเวลากรณีพิเศษ (ADMIN) /// /// /// /// เมื่อทำรายการสำเร็จ /// ไม่ได้ Login เข้าระบบ /// เมื่อเกิดข้อผิดพลาดในการทำงาน [HttpPut("admin/edit/approve/{id:guid}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] public async Task> ApproveRequestAsync(Guid id, [FromBody] ApproveRequestDto req) { var getPermission = await _permission.GetPermissionAPIAsync("UPDATE", "SYS_CHECKIN_SPECIAL"); var jsonData = JsonConvert.DeserializeObject(getPermission); if (jsonData["status"]?.ToString() != "200") { return Error(jsonData["message"]?.ToString(), StatusCodes.Status403Forbidden); } if (req.Reason == null || req.Reason == string.Empty) { return Error("กรุณากรอกเหตุผล", StatusCodes.Status400BadRequest); } var requestData = await _additionalCheckRequestRepository.GetByIdAsync(id); if (requestData == null) { return Error(GlobalMessages.DataNotFound, StatusCodes.Status404NotFound); } requestData.Status = "APPROVE"; requestData.Comment = req.Reason; await _additionalCheckRequestRepository.UpdateAsync(requestData); // change user timestamp var processTimeStamp = await _processUserTimeStampRepository.GetTimestampByDateAsync(requestData.KeycloakUserId, requestData.CheckDate.Date); var profile = await _userProfileRepository.GetProfileByKeycloakIdNew2Async(requestData.KeycloakUserId, AccessToken); if (processTimeStamp == null) { processTimeStamp = new ProcessUserTimeStamp { KeycloakUserId = requestData.KeycloakUserId, CheckIn = DateTime.Parse($"{requestData.CheckDate.Date.ToString("yyyy-MM-dd")} {req.CheckInTime}"), CheckOut = DateTime.Parse($"{requestData.CheckDate.Date.ToString("yyyy-MM-dd")} {req.CheckOutTime}"), CheckInRemark = req.Reason, CheckOutRemark = req.Reason, CheckInLat = 0, CheckInLon = 0, CheckOutLat = 0, CheckOutLon = 0, CheckInPOI = "", CheckOutPOI = "", CheckInStatus = req.CheckInStatus, CheckOutStatus = req.CheckOutStatus, Prefix = profile.Prefix, FirstName = profile.FirstName, LastName = profile.LastName, // Add ข้อมูลจาก profile CitizenId = profile.CitizenId, ProfileType = profile.ProfileType, Root = profile.Root, RootId = profile.RootId, Child1 = profile.Child1, Child1Id = profile.Child1Id, Child2 = profile.Child2, Child2Id = profile.Child2Id, Child3 = profile.Child3, Child3Id = profile.Child3Id, Child4 = profile.Child4, Child4Id = profile.Child4Id, Gender = profile.Gender, ProfileId = profile.Id, }; processTimeStamp.EditStatus = "APPROVE"; processTimeStamp.EditReason = req.Reason; if (requestData.CheckInEdit) { processTimeStamp.CheckInPOI = requestData.POI ?? ""; processTimeStamp.CheckInLat = requestData.Latitude ?? 0; processTimeStamp.CheckInLon = requestData.Longitude ?? 0; } if (requestData.CheckOutEdit) { processTimeStamp.CheckOutPOI = requestData.POI ?? ""; processTimeStamp.CheckOutLat = requestData.Latitude ?? 0; processTimeStamp.CheckOutLon = requestData.Longitude ?? 0; } await _processUserTimeStampRepository.AddAsync(processTimeStamp); } else { if (requestData.CheckInEdit) { processTimeStamp.CheckIn = DateTime.Parse($"{requestData.CheckDate.Date.ToString("yyyy-MM-dd")} {req.CheckInTime}"); processTimeStamp.CheckInRemark = req.Reason; //processTimeStamp.CheckInLat = 0; //processTimeStamp.CheckInLon = 0; //processTimeStamp.CheckInPOI = "ลงเวลากรณีพิเศษ"; processTimeStamp.CheckInStatus = req.CheckInStatus; processTimeStamp.CheckInPOI = requestData.POI ?? ""; processTimeStamp.CheckInLat = requestData.Latitude ?? 0; processTimeStamp.CheckInLon = requestData.Longitude ?? 0; } if (requestData.CheckOutEdit) { processTimeStamp.CheckOut = DateTime.Parse($"{requestData.CheckDate.Date.ToString("yyyy-MM-dd")} {req.CheckOutTime}"); processTimeStamp.CheckOutRemark = req.Reason; //processTimeStamp.CheckOutLat = 0; //processTimeStamp.CheckOutLon = 0; //processTimeStamp.CheckOutPOI = "ลงเวลากรณีพิเศษ"; processTimeStamp.CheckOutStatus = req.CheckOutStatus; processTimeStamp.CheckOutPOI = requestData.POI ?? ""; processTimeStamp.CheckOutLat = requestData.Latitude ?? 0; processTimeStamp.CheckOutLon = requestData.Longitude ?? 0; } processTimeStamp.EditStatus = "APPROVE"; processTimeStamp.EditReason = req.Reason; await _processUserTimeStampRepository.UpdateAsync(processTimeStamp); } var recvId = new List { profile.Id }; await _notificationRepository.PushNotificationsAsync(recvId.ToArray(), "ลงเวลากรณีพิเศษ", "การขอลงเวลากรณีพิเศษของคุณได้รับการอนุมัติ", "", "", true, false); return Success(); } /// /// LV1_020 - ไม่อนุมัติลงเวลากรณีพิเศษ (ADMIN) /// /// /// /// เมื่อทำรายการสำเร็จ /// ไม่ได้ Login เข้าระบบ /// เมื่อเกิดข้อผิดพลาดในการทำงาน [HttpPut("admin/edit/reject/{id:guid}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] public async Task> RejectRequestAsync(Guid id, [FromBody] RejectRequestDto req) { var getPermission = await _permission.GetPermissionAPIAsync("UPDATE", "SYS_CHECKIN_SPECIAL"); var jsonData = JsonConvert.DeserializeObject(getPermission); if (jsonData["status"]?.ToString() != "200") { return Error(jsonData["message"]?.ToString(), StatusCodes.Status403Forbidden); } if (req.Reason == null || req.Reason == string.Empty) { return Error("กรุณากรอกเหตุผล", StatusCodes.Status400BadRequest); } var requestData = await _additionalCheckRequestRepository.GetByIdAsync(id); if (requestData == null) { return Error(GlobalMessages.DataNotFound, StatusCodes.Status404NotFound); } // change user timestamp var processTimeStamp = await _processUserTimeStampRepository.GetTimestampByDateAsync(requestData.KeycloakUserId, requestData.CheckDate.Date); if (processTimeStamp != null) { processTimeStamp.EditStatus = "REJECT"; processTimeStamp.EditReason = req.Reason; await _processUserTimeStampRepository.UpdateAsync(processTimeStamp); } requestData.Status = "REJECT"; requestData.Comment = req.Reason; await _additionalCheckRequestRepository.UpdateAsync(requestData); var profile = await _userProfileRepository.GetProfileByKeycloakIdNew2Async(requestData.KeycloakUserId, AccessToken); var recvId = new List { profile.Id }; await _notificationRepository.PushNotificationsAsync(recvId.ToArray(), "ลงเวลากรณีพิเศษ", "การขอลงเวลากรณีพิเศษของคุณไม่ได้รับการอนุมัติ", "", "", true, false); return Success(); } /// /// LV1_021 - รายละเอียดการลงเวลาปฎิบัติงานรายบุคคล Tabรายการลงเวลา (ADMIN) /// /// /// /// เมื่อทำรายการสำเร็จ /// ไม่ได้ Login เข้าระบบ /// เมื่อเกิดข้อผิดพลาดในการทำงาน [HttpGet("log-record/{id:guid}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] public async Task> GetLogRecordAsync([Required] Guid id) { var getWorkflow = await _permission.GetPermissionAPIWorkflowAsync(id.ToString(), "SYS_CHECKIN"); if (getWorkflow == false) { var getPermission = await _permission.GetPermissionAPIAsync("GET", "SYS_CHECKIN"); var jsonData = JsonConvert.DeserializeObject(getPermission); if (jsonData["status"]?.ToString() != "200") { return Error(jsonData["message"]?.ToString(), StatusCodes.Status403Forbidden); } } var imgUrl = $"{_configuration["MinIO:Endpoint"]}{_configuration["MinIO:BucketName"]}"; var d = (await _userTimeStampRepository.GetTimeStampById(id)); if (d == null) { throw new Exception(GlobalMessages.DataNotFound); } else { var profile = await _userProfileRepository.GetProfileByKeycloakIdNew2Async(d.KeycloakUserId, AccessToken); if (profile == null) { return Error(GlobalMessages.DataNotFound, StatusCodes.Status404NotFound); } var defaultRound = await _dutyTimeRepository.GetDefaultAsync(); if (defaultRound == null) { return Error("ไม่พบรอบการลงเวลา Default", StatusCodes.Status404NotFound); } //var userRound = await _dutyTimeRepository.GetByIdAsync(profile.DutyTimeId ?? Guid.Empty); var effectiveDate = await _userDutyTimeRepository.GetLastEffectRound(profile.Id); var roundId = effectiveDate != null ? effectiveDate.DutyTimeId : Guid.Empty; var userRound = await _dutyTimeRepository.GetByIdAsync(roundId); var duty = userRound ?? defaultRound; var result = new CheckInDetailForAdminDto { Id = d.Id, FullName = $"{d.Prefix}{d.FirstName} {d.LastName}", //FullName = _userProfileRepository.GetUserFullName(d.KeycloakUserId, AccessToken), CheckInDate = d.CheckIn.Date, CheckInTime = d.CheckIn.ToString("HH:mm"), CheckInPOI = d.CheckInPOI, CheckInLat = d.CheckInLat, CheckInLon = d.CheckInLon, // CheckInImg = $"{imgUrl}/{d.CheckInImageUrl}", CheckInImg = await _minIOService.ImagesPathByName(d.CheckInImageUrl), CheckInStatus = DateTime.Parse(d.CheckIn.ToString("yyyy-MM-dd HH:mm")) > DateTime.Parse($"{d.CheckIn.Date.ToString("yyyy-MM-dd")} {duty.StartTimeMorning}") ? DateTime.Parse(d.CheckIn.ToString("yyyy-MM-dd HH:mm")) > DateTime.Parse($"{d.CheckIn.ToString("yyyy-MM-dd")} {duty.EndTimeMorning}") ? "ABSENT" : "LATE" : "NORMAL", CheckInDescription = d.CheckInRemark ?? "", CheckOutDate = d.CheckOut == null ? null : d.CheckOut.Value.Date, CheckOutTime = d.CheckOut == null ? "" : d.CheckOut.Value.ToString("HH:mm"), CheckOutPOI = d.CheckOut == null ? "" : d.CheckOutPOI, CheckOutLat = d.CheckOut == null ? null : d.CheckOutLat, CheckOutLon = d.CheckOut == null ? null : d.CheckOutLon, CheckOutImg = d.CheckOut == null ? "" : await _minIOService.ImagesPathByName(d.CheckOutImageUrl), CheckOutStatus = d.CheckOut == null ? null : DateTime.Parse(d.CheckOut.Value.ToString("yyyy-MM-dd HH:mm")) < DateTime.Parse($"{d.CheckIn.Date.ToString("yyyy-MM-dd")} {duty.EndTimeAfternoon}") ? "ABSENT" : DateTime.Parse(d.CheckOut.Value.ToString("yyyy-MM-dd HH:mm")) < DateTime.Parse($"{d.CheckIn.Date.ToString("yyyy-MM-dd")} {duty.EndTimeMorning}") ? "ABSENT" : "NORMAL", CheckOutDescription = d.CheckOutRemark ?? "", }; return Success(result); } } /// /// LV1_022 - ประวัติการยื่นขอลงเวลาพิเศษ (USER) /// /// /// /// เมื่อทำรายการสำเร็จ /// ไม่ได้ Login เข้าระบบ /// เมื่อเกิดข้อผิดพลาดในการทำงาน [HttpGet("edit/history")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] public async Task> GetAdditionalCheckRequestHistoryAsync([Required] int year, [Required] int month, [Required] int page = 1, [Required] int pageSize = 10, string keyword = "") { var userId = UserId == null ? Guid.Empty : Guid.Parse(UserId); var rawData = await _additionalCheckRequestRepository.GetAdditionalCheckRequestsByUserId(userId, year, month); var getDefaultRound = await _dutyTimeRepository.GetDefaultAsync(); if (getDefaultRound == null) { return Error("ไม่พบรอบลงเวลา Default", StatusCodes.Status404NotFound); } var result = new List(); foreach (var data in rawData) { var profile = await _userProfileRepository.GetProfileByKeycloakIdNew2Async(data.KeycloakUserId, AccessToken); if (profile == null) { return Error(GlobalMessages.DataNotFound, StatusCodes.Status404NotFound); } //var userRound = await _dutyTimeRepository.GetByIdAsync(profile.DutyTimeId ?? Guid.Empty); var effectiveDate = await _userDutyTimeRepository.GetLastEffectRound(profile.Id); var roundId = effectiveDate != null ? effectiveDate.DutyTimeId : Guid.Empty; var userRound = await _dutyTimeRepository.GetByIdAsync(roundId); var checkInData = await _userTimeStampRepository.GetTimestampByDateAsync(data.KeycloakUserId, data.CheckDate); //var checkInData = await _processUserTimeStampRepository.GetTimestampByDateAsync(data.KeycloakUserId, data.CheckDate); var duty = userRound ?? getDefaultRound; DateTime? resultCheckInDate, resultCheckOutDate, resultCheckInDateAndTime, resultCheckOutDateAndTime; string resultCheckInTime, resultCheckOutTime; string resultCheckInLocation = "", resultCheckOutLocation = ""; if (data.CheckInEdit) { resultCheckInDate = data.CheckDate.Date; resultCheckInTime = duty.StartTimeMorning; resultCheckInLocation = data.POI ?? ""; } else { resultCheckInDate = checkInData == null ? null : checkInData.CheckIn; resultCheckInTime = checkInData == null ? "00:00" : checkInData.CheckIn.ToString("HH:mm"); } if (data.CheckOutEdit) { resultCheckOutDate = data.CheckDate.Date; resultCheckOutTime = duty.EndTimeAfternoon; resultCheckOutLocation = data.POI ?? ""; } else { resultCheckOutDate = checkInData == null ? null : checkInData.CheckOut == null ? null : checkInData.CheckOut.Value.Date; resultCheckOutTime = checkInData == null ? "00:00" : checkInData.CheckOut == null ? "00:00" : checkInData.CheckOut.Value.ToString("HH:mm"); } resultCheckInDateAndTime = resultCheckInDate is null ? null : DateTime.Parse($"{resultCheckInDate.Value.Date.ToString("yyyy-MM-dd")} {resultCheckInTime}"); resultCheckOutDateAndTime = resultCheckOutDate is null ? null : DateTime.Parse($"{resultCheckOutDate.Value.Date.ToString("yyyy-MM-dd")} {resultCheckOutTime}"); // create result object to return var resObj = new GetAdditionalCheckRequestHistoryDto { Id = data.Id, CheckInDate = resultCheckInDate, CheckOutDate = resultCheckOutDate, CheckInTime = resultCheckInTime, CheckOutTime = resultCheckOutTime, CheckInStatus = resultCheckInDateAndTime == null ? null : DateTime.Parse(resultCheckInDateAndTime.Value.ToString("yyyy-MM-dd HH:mm")) > DateTime.Parse($"{resultCheckInDate.Value.Date.ToString("yyyy-MM-dd")} {duty.StartTimeMorning}") ? DateTime.Parse(resultCheckInDateAndTime.Value.ToString("yyyy-MM-dd HH:mm")) > DateTime.Parse($"{resultCheckInDate.Value.ToString("yyyy-MM-dd")} {duty.EndTimeMorning}") ? "ABSENT" : "LATE" : "NORMAL", CheckOutStatus = resultCheckInDateAndTime == null ? null : resultCheckOutDateAndTime == null ? null : DateTime.Parse(resultCheckOutDateAndTime.Value.ToString("yyyy-MM-dd HH:mm")) < DateTime.Parse($"{resultCheckInDate.Value.Date.ToString("yyyy-MM-dd")} {duty.EndTimeAfternoon}") ? "ABSENT" : DateTime.Parse(resultCheckOutDateAndTime.Value.ToString("yyyy-MM-dd HH:mm")) < DateTime.Parse($"{resultCheckInDate.Value.Date.ToString("yyyy-MM-dd")} {duty.EndTimeMorning}") ? "ABSENT" : "NORMAL", //CheckInLocation = checkInData == null ? "" : checkInData.CheckInPOI, //CheckOutLocation = checkInData == null ? "" : checkInData.CheckOutPOI ?? "", CheckInLocation = resultCheckInLocation, CheckOutLocation = resultCheckOutLocation, EditReason = data.Comment ?? "", EditStatus = data.Status, }; result.Add(resObj); } var pageResult = result.Skip((page - 1) * pageSize).Take(pageSize) .ToList(); return Success(new { data = pageResult, total = result.Count }); } #endregion #region " ปฏิทินการทำงานของ ขรก. " /// /// LV1_023 - แสดงปฏิทินวันทำงานรายคน (ADMIN) /// /// /// /// เมื่อทำรายการสำเร็จ /// ไม่ได้ Login เข้าระบบ /// เมื่อเกิดข้อผิดพลาดในการทำงาน [HttpGet("admin/work/{id:guid}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] public async Task> GetCalendarByProfileAsync(Guid id) { var getWorkflow = await _permission.GetPermissionAPIWorkflowAsync(id.ToString(), "SYS_WORK_ROUND_EDIT"); if (getWorkflow == false) { var getPermission = await _permission.GetPermissionAPIAsync("GET", "SYS_WORK_ROUND_EDIT"); var jsonData = JsonConvert.DeserializeObject(getPermission); if (jsonData["status"]?.ToString() != "200") { return Error(jsonData["message"]?.ToString(), StatusCodes.Status403Forbidden); } } var data = await _userCalendarRepository.GetExist(id); if (data == null) return Success(new { Work = "NORMAL" }); else return Success(new { Work = data.Calendar }); } /// /// LV1_024 - บันทึกแก้ไขปฏิทินวันทำงาน (ADMIN) /// /// /// /// เมื่อทำรายการสำเร็จ /// ไม่ได้ Login เข้าระบบ /// เมื่อเกิดข้อผิดพลาดในการทำงาน [HttpPut("admin/work/{id:guid}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] public async Task> UpdateCalendarByProfileAsync(Guid id, [FromBody] UpdateCalendarDto req) { var getPermission = await _permission.GetPermissionAPIAsync("UPDATE", "SYS_WORK_ROUND_EDIT"); var jsonData = JsonConvert.DeserializeObject(getPermission); if (jsonData["status"]?.ToString() != "200") { return Error(jsonData["message"]?.ToString(), StatusCodes.Status403Forbidden); } var data = await _userCalendarRepository.GetExist(id); if (data != null) { data.Calendar = req.Work; await _userCalendarRepository.UpdateAsync(data); return Success(); } else { data = new UserCalendar { ProfileId = id, Calendar = req.Work }; await _userCalendarRepository.AddAsync(data); return Success(); } } #endregion #region " ปฏิทินการทำงานของ ลจ. " /// /// LV1_023 - แสดงปฏิทินวันทำงานรายคน (ADMIN) Employee /// /// /// /// เมื่อทำรายการสำเร็จ /// ไม่ได้ Login เข้าระบบ /// เมื่อเกิดข้อผิดพลาดในการทำงาน [HttpGet("admin/emp/work/{id:guid}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] public async Task> GetCalendarEmpByProfileAsync(Guid id) { var getWorkflow = await _permission.GetPermissionAPIWorkflowAsync(id.ToString(), "SYS_WORK_ROUND_EDIT"); if (getWorkflow == false) { var getPermission = await _permission.GetPermissionAPIAsync("GET", "SYS_WORK_ROUND_EDIT"); var jsonData = JsonConvert.DeserializeObject(getPermission); if (jsonData["status"]?.ToString() != "200") { return Error(jsonData["message"]?.ToString(), StatusCodes.Status403Forbidden); } } var data = await _userCalendarRepository.GetExist(id); if (data == null) return Success(new { Work = "NORMAL" }); else return Success(new { Work = data.Calendar }); } /// /// LV1_024 - บันทึกแก้ไขปฏิทินวันทำงาน (ADMIN) /// /// /// /// เมื่อทำรายการสำเร็จ /// ไม่ได้ Login เข้าระบบ /// เมื่อเกิดข้อผิดพลาดในการทำงาน [HttpPut("admin/emp/work/{id:guid}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] public async Task> UpdateEmpCalendarByProfileAsync(Guid id, [FromBody] UpdateCalendarDto req) { var getPermission = await _permission.GetPermissionAPIAsync("UPDATE", "SYS_WORK_ROUND_EDIT"); var jsonData = JsonConvert.DeserializeObject(getPermission); if (jsonData["status"]?.ToString() != "200") { return Error(jsonData["message"]?.ToString(), StatusCodes.Status403Forbidden); } var data = await _userCalendarRepository.GetExist(id); if (data != null) { data.Calendar = req.Work; await _userCalendarRepository.UpdateAsync(data); return Success(); } else { data = new UserCalendar { ProfileId = id, Calendar = req.Work }; await _userCalendarRepository.AddAsync(data); return Success(); } } #endregion #region " แก้ไขสถานะการลงเวลา " /// /// LV1_025 - บันทึกแก้ไขสถานะการเข้า-ออกงาน (ADMIN) /// /// /// /// เมื่อทำรายการสำเร็จ /// ไม่ได้ Login เข้าระบบ /// เมื่อเกิดข้อผิดพลาดในการทำงาน [HttpPut("admin/edit/checkin/{id:guid}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] public async Task> EditCheckInStatusAsync(Guid id, [FromBody] EditCheckInStatusDto req) { var getPermission = await _permission.GetPermissionAPIAsync("UPDATE", "SYS_CHECKIN"); var jsonData = JsonConvert.DeserializeObject(getPermission); if (jsonData["status"]?.ToString() != "200") { return Error(jsonData["message"]?.ToString(), StatusCodes.Status403Forbidden); } var data = await _processUserTimeStampRepository.GetByIdAsync(id); if (data == null) return Error(GlobalMessages.DataNotFound);  //if (data.CheckInStatus == "NORMAL" || data.CheckOutStatus == "NORMAL") //var userId = UserId == null ? Guid.Empty : Guid.Parse(UserId); // แก้เป็นมาใช้งาน KeycloakUserId แทน var profile = await _userProfileRepository.GetProfileByKeycloakIdNew2Async(data.KeycloakUserId, AccessToken); var defaultRound = await _dutyTimeRepository.GetDefaultAsync(); if (defaultRound == null) { return Error("ไม่พบรอบการลงเวลา Default", StatusCodes.Status404NotFound); } var effectiveDate = await _userDutyTimeRepository.GetLastEffectRound(profile!.Id); var roundId = effectiveDate != null ? effectiveDate.DutyTimeId : Guid.Empty; var userRound = await _dutyTimeRepository.GetByIdAsync(roundId); var duty = userRound ?? defaultRound; if (req.CheckInStatus == "NORMAL") { if(data.CheckInLocationName == "ไปประชุม / อบรม / สัมมนา") { data.CheckIn = DateTime.Parse($"{data.CheckIn.Date.ToString("yyyy-MM-dd")} 10:30"); } else { data.CheckIn = DateTime.Parse($"{data.CheckIn.Date.ToString("yyyy-MM-dd")} {duty.StartTimeMorning}"); } } if (req.CheckOutStatus == "NORMAL" ) { var checkOutTime = data.CheckOut != null ? data.CheckOut.Value : data.CheckIn; var oldCheckOutTime = data.CheckOut != null ? data.CheckOut.Value : DateTime.Now; var roundCheckOutTime = DateTime.Now; if(data.CheckOutLocationName == "ไปประชุม / อบรม / สัมมนา") { roundCheckOutTime = DateTime.Parse($"{checkOutTime.Date.ToString("yyyy-MM-dd")} 14:30"); } else { roundCheckOutTime = DateTime.Parse($"{checkOutTime.Date.ToString("yyyy-MM-dd")} {duty.EndTimeAfternoon}"); } if (oldCheckOutTime < roundCheckOutTime) { data.CheckOut = roundCheckOutTime; } } data.CheckInStatus = req.CheckInStatus; data.CheckOutStatus = req.CheckOutStatus; data.EditReason = req.Reason; await _processUserTimeStampRepository.UpdateAsync(data); return Success(); } #endregion #region " รายการลารายบุคคล " /// /// LV1_026 - รายการลารายบุคคล (ADMIN) /// /// /// /// เมื่อทำรายการสำเร็จ /// ไม่ได้ Login เข้าระบบ /// เมื่อเกิดข้อผิดพลาดในการทำงาน [HttpPut("admin/summary/keycloak/{id:guid}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] public async Task> GetLeaveSummaryByProfileAsync(Guid id, [FromBody] GetLeaveSummaryDto req) { var profile = await _userProfileRepository.GetProfileByKeycloakIdNew2Async(id, AccessToken); var thisYear = DateTime.Now.Year; var startDate = req.StartDate; var endDate = req.EndDate; var leaveDayCount = await _leaveRequestRepository.GetSumApproveLeaveByRangeForUser(id, startDate, endDate); var timeStamps = await _processUserTimeStampRepository.GetTimeStampHistoryByRangeForUserAsync(id, startDate, endDate); var defaultRound = await _dutyTimeRepository.GetDefaultAsync(); if (defaultRound == null) { return Error("ไม่พบรอบการลงเวลา Default", StatusCodes.Status404NotFound); } var effectiveDate = await _userDutyTimeRepository.GetLastEffectRound(profile.Id); var roundId = effectiveDate != null ? effectiveDate.DutyTimeId : Guid.Empty; var userRound = await _dutyTimeRepository.GetByIdAsync(roundId); var duty = userRound ?? defaultRound; var processTimeStamps = timeStamps .Select(d => new { d.Id, CheckInStatus = DateTime.Parse(d.CheckIn.ToString("yyyy-MM-dd HH:mm")) > DateTime.Parse($"{d.CheckIn.Date.ToString("yyyy-MM-dd")} {duty.StartTimeMorning}") ? "LATE" : "NORMAL", CheckOutStatus = d.CheckOut == null ? "" : DateTime.Parse(d.CheckOut.Value.ToString("yyyy-MM-dd HH:mm")) < DateTime.Parse($"{d.CheckIn.Date.ToString("yyyy-MM-dd")} {duty.EndTimeAfternoon}") ? "LATE" : DateTime.Parse(d.CheckOut.Value.ToString("yyyy-MM-dd HH:mm")) < DateTime.Parse($"{d.CheckIn.Date.ToString("yyyy-MM-dd")} {duty.EndTimeMorning}") ? "ABSENT" : "NORMAL", }); var absentCount = processTimeStamps.Count(x => x.CheckOutStatus == "ABSENT"); var lateCount = processTimeStamps.Count(x => x.CheckInStatus == "LATE"); return Success(new { leave = leaveDayCount, late = lateCount }); } #endregion #region " Process - Leave and Absence " /// /// สร้าง Task สำหรับ Process ข้อมูลวันลาและขาดราชการ (ADMIN) /// /// /// /// เมื่อทำรายการสำเร็จ /// ไม่ได้ Login เข้าระบบ /// เมื่อเกิดข้อผิดพลาดในการทำงาน [HttpPost("admin/leave-task/process")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] public async Task> CreateProcessTaskAsync([FromBody] CreateLeaveProcessJobDto req) { var userId = UserId == null ? Guid.Empty : Guid.Parse(UserId); var profile = await _userProfileRepository.GetProfileByKeycloakIdNew2Async(userId, AccessToken); if (profile == null) { return Error(GlobalMessages.DataNotFound, StatusCodes.Status404NotFound); } var task = new LeaveProcessJobStatus { RootDnaId = profile.RootDnaId ?? Guid.Empty, CreatedUserId = profile.Keycloak?.ToString("D") ?? "", CreatedFullName = profile.FirstName + " " + profile.LastName, CreatedAt = DateTime.Now, Status = "PENDING", StartDate = req.StartDate, EndDate = req.EndDate }; await _leaveProcessJobStatusRepository.AddAsync(task); return Success(); } /// /// แสดงรายการ Task สำหรับ Process ข้อมูลวันลาและขาดราชการ (ADMIN) /// /// /// /// เมื่อทำรายการสำเร็จ /// ไม่ได้ Login เข้าระบบ /// เมื่อเกิดข้อผิดพลาดในการทำงาน [HttpGet("admin/leave-task/process")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] public async Task> GetProcessTaskAsync() { var userId = UserId == null ? Guid.Empty : Guid.Parse(UserId); var tasks = await _leaveProcessJobStatusRepository.GetByUserIdAsync(userId); var result = tasks.Select(t => new { t.Id, t.CreatedFullName, t.CreatedAt, t.Status, t.StartDate, t.EndDate, t.ProcessingDate, t.CompletedDate, t.ErrorMessage }); return Success(result); } /// /// แสดงรายการ Task สำหรับ Process ข้อมูลวันลาและขาดราชการ (ADMIN) /// /// /// /// เมื่อทำรายการสำเร็จ /// ไม่ได้ Login เข้าระบบ /// เมื่อเกิดข้อผิดพลาดในการทำงาน [HttpGet("admin/leave-task/process/{id:guid}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] public async Task> GetProcessTaskByIdAsync(Guid id) { var task = await _leaveProcessJobStatusRepository.GetByTaskIdAsync(id); if (task == null) { return Error(GlobalMessages.DataNotFound, StatusCodes.Status404NotFound); } var result = new { task.Id, task.CreatedFullName, task.CreatedAt, task.Status, task.StartDate, task.EndDate, task.ProcessingDate, task.CompletedDate, task.ErrorMessage }; return Success(result); } /// /// ลบ Task สำหรับ Process ข้อมูลวันลาและขาดราชการ (ADMIN) /// /// /// /// เมื่อทำรายการสำเร็จ /// ไม่ได้ Login เข้าระบบ /// เมื่อเกิดข้อผิดพลาดในการทำงาน [HttpDelete("admin/leave-task/process/{id:guid}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] public async Task> DeleteProcessTaskByIdAsync(Guid id) { var task = await _leaveProcessJobStatusRepository.GetByTaskIdAsync(id); if (task == null) { return Error(GlobalMessages.DataNotFound, StatusCodes.Status404NotFound); } await _leaveProcessJobStatusRepository.DeleteAsync(task); return Success(); } /// /// อัปเดต Task สำหรับ Process ข้อมูลวันลาและขาดราชการ (ADMIN) /// /// /// /// เมื่อทำรายการสำเร็จ /// ไม่ได้ Login เข้าระบบ /// เมื่อเกิดข้อผิดพลาดในการทำงาน [HttpPut("admin/leave-task/process/{id:guid}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] public async Task> UpdateProcessTaskByIdAsync(Guid id,[FromBody] CreateLeaveProcessJobDto req) { var task = await _leaveProcessJobStatusRepository.GetByTaskIdAsync(id); if (task == null) { return Error(GlobalMessages.DataNotFound, StatusCodes.Status404NotFound); } if(task.Status != "PENDING") { return Error("ไม่สามารถแก้ไขได้เนื่องจาก Task อยู่ในสถานะกำลังดำเนินการหรือดำเนินการเสร็จสิ้นแล้ว"); } task.StartDate = req.StartDate; task.EndDate = req.EndDate; await _leaveProcessJobStatusRepository.UpdateAsync(task); return Success(); } #endregion #endregion } }