diff --git a/BMA.EHR.Application/Common/Interfaces/ILeaveDbContext.cs b/BMA.EHR.Application/Common/Interfaces/ILeaveDbContext.cs new file mode 100644 index 00000000..0ec6d3f3 --- /dev/null +++ b/BMA.EHR.Application/Common/Interfaces/ILeaveDbContext.cs @@ -0,0 +1,13 @@ +using Microsoft.EntityFrameworkCore; + +namespace BMA.EHR.Application.Common.Interfaces +{ + public interface ILeaveDbContext + { + DbSet Set() where T : class; + + void Attatch(T entity) where T : class; + + Task SaveChangesAsync(); + } +} diff --git a/BMA.EHR.Application/Repositories/Leaves/GenericLeaveRepository.cs b/BMA.EHR.Application/Repositories/Leaves/GenericLeaveRepository.cs new file mode 100644 index 00000000..04e46ec1 --- /dev/null +++ b/BMA.EHR.Application/Repositories/Leaves/GenericLeaveRepository.cs @@ -0,0 +1,97 @@ +using BMA.EHR.Application.Common.Interfaces; +using BMA.EHR.Domain.Models.Base; +using Microsoft.AspNetCore.Http; +using Microsoft.EntityFrameworkCore; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; +using System.Text; +using System.Threading.Tasks; + +namespace BMA.EHR.Application.Repositories.Leaves +{ + public class GenericLeaveRepository : IGenericRepository where T : class + { + #region " Field " + + private readonly ILeaveDbContext _dbContext; + private readonly DbSet _dbSet; + private readonly IHttpContextAccessor _httpContextAccessor; + + #endregion + + #region " Constructor and Destructor " + + public GenericLeaveRepository(ILeaveDbContext dbContext, + IHttpContextAccessor httpContextAccessor) + { + _dbContext = dbContext; + _dbSet = _dbContext.Set(); + _httpContextAccessor = httpContextAccessor; + + } + + #endregion + + #region " Properties " + + protected string? UserId => _httpContextAccessor?.HttpContext?.User?.FindFirst(ClaimTypes.NameIdentifier)?.Value; + + protected string? FullName => _httpContextAccessor?.HttpContext?.User?.FindFirst("name")?.Value; + + protected bool? IsPlacementAdmin => _httpContextAccessor?.HttpContext?.User?.IsInRole("placement1"); + + #endregion + + #region " Methods " + + public virtual async Task> GetAllAsync() + { + return await _dbSet.ToListAsync(); + } + + public virtual async Task GetByIdAsync(S id) + { + return await _dbSet.FindAsync(id); + } + + public virtual async Task AddAsync(T entity) + { + if (entity is EntityBase) + { + (entity as EntityBase).CreatedUserId = UserId!; + (entity as EntityBase).CreatedFullName = FullName!; + (entity as EntityBase).CreatedAt = DateTime.Now; + } + + await _dbSet.AddAsync(entity); + await _dbContext.SaveChangesAsync(); + + return entity; + } + + public virtual async Task UpdateAsync(T entity) + { + if (entity is EntityBase) + { + (entity as EntityBase).LastUpdateUserId = UserId!; + (entity as EntityBase).LastUpdateFullName = FullName!; + (entity as EntityBase).LastUpdatedAt = DateTime.Now; + } + + _dbSet.Update(entity); + await _dbContext.SaveChangesAsync(); + + return entity; + } + + public virtual async Task DeleteAsync(T entity) + { + _dbSet.Remove(entity); + await _dbContext.SaveChangesAsync(); + } + + #endregion + } +} diff --git a/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/DutyTimeRepository.cs b/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/DutyTimeRepository.cs index 49a3d9aa..347f1a2b 100644 --- a/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/DutyTimeRepository.cs +++ b/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/DutyTimeRepository.cs @@ -7,11 +7,11 @@ using Microsoft.Extensions.Configuration; namespace BMA.EHR.Application.Repositories.Leaves.TimeAttendants { - public class DutyTimeRepository : GenericRepository + public class DutyTimeRepository : GenericLeaveRepository { #region " Fields " - private readonly IApplicationDBContext _dbContext; + private readonly ILeaveDbContext _dbContext; private readonly IHttpContextAccessor _httpContextAccessor; private readonly OrganizationCommonRepository _organizationCommonRepository; private readonly UserProfileRepository _userProfileRepository; @@ -22,7 +22,7 @@ namespace BMA.EHR.Application.Repositories.Leaves.TimeAttendants #region " Constructor and Destuctor " - public DutyTimeRepository(IApplicationDBContext dbContext, + public DutyTimeRepository(ILeaveDbContext dbContext, IHttpContextAccessor httpContextAccessor, OrganizationCommonRepository organizationCommonRepository, UserProfileRepository userProfileRepository, diff --git a/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/UserTimeStampRepository.cs b/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/UserTimeStampRepository.cs index 9d71ad97..18a1f26d 100644 --- a/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/UserTimeStampRepository.cs +++ b/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/UserTimeStampRepository.cs @@ -1,5 +1,6 @@ using BMA.EHR.Application.Common.Interfaces; using BMA.EHR.Application.Messaging; +using BMA.EHR.Domain.Extensions; using BMA.EHR.Domain.Models.Leave.TimeAttendants; using Microsoft.AspNetCore.Http; using Microsoft.EntityFrameworkCore; @@ -7,11 +8,11 @@ using Microsoft.Extensions.Configuration; namespace BMA.EHR.Application.Repositories.Leaves.TimeAttendants { - public class UserTimeStampRepository : GenericRepository + public class UserTimeStampRepository : GenericLeaveRepository { #region " Fields " - private readonly IApplicationDBContext _dbContext; + private readonly ILeaveDbContext _dbContext; private readonly IHttpContextAccessor _httpContextAccessor; private readonly OrganizationCommonRepository _organizationCommonRepository; private readonly UserProfileRepository _userProfileRepository; @@ -22,7 +23,7 @@ namespace BMA.EHR.Application.Repositories.Leaves.TimeAttendants #region " Constructor and Destuctor " - public UserTimeStampRepository(IApplicationDBContext dbContext, + public UserTimeStampRepository(ILeaveDbContext dbContext, IHttpContextAccessor httpContextAccessor, OrganizationCommonRepository organizationCommonRepository, UserProfileRepository userProfileRepository, @@ -66,6 +67,31 @@ namespace BMA.EHR.Application.Repositories.Leaves.TimeAttendants return data; } + public async Task> GetTimeStampHistoryAsync(Guid keycloakId, int year, int page = 1, int pageSize = 10, string keyword = "") + { + var data = await _dbContext.Set() + .Where(u => u.KeycloakUserId == keycloakId) + .Where(u => u.CheckIn.Year.ToCeYear() == year.ToCeYear()) + .OrderBy(u => u.CheckIn) + .Skip((page - 1) * pageSize) + .Take(pageSize) + .ToListAsync(); + + return data; + } + + public async Task> GetTimeStampHistoryForAdminAsync(DateTime startDate,DateTime endDate, int page = 1, int pageSize = 10, string keyword = "") + { + var data = await _dbContext.Set() + .Where(u => u.CheckIn >= startDate && u.CheckIn <= endDate) + .OrderBy(u => u.CheckIn) + .Skip((page - 1) * pageSize) + .Take(pageSize) + .ToListAsync(); + + return data; + } + #endregion } } diff --git a/BMA.EHR.Application/Repositories/MinIOService.cs b/BMA.EHR.Application/Repositories/MinIOService.cs index 71f6f18a..58a5542a 100644 --- a/BMA.EHR.Application/Repositories/MinIOService.cs +++ b/BMA.EHR.Application/Repositories/MinIOService.cs @@ -361,12 +361,12 @@ namespace BMA.EHR.Application.Repositories var fileExt = Path.GetExtension(fileName); var fileType = MimeTypeMap.GetMimeType(fileExt); - var file_name = Path.GetFileName(fileName); + //var file_name = Path.GetFileName(fileName); var request = new PutObjectRequest { BucketName = _bucketName, - Key = file_name, + Key = fileName, InputStream = fileStream, ContentType = fileType, CannedACL = S3CannedACL.BucketOwnerFullControl diff --git a/BMA.EHR.Application/Repositories/UserProfileRepository.cs b/BMA.EHR.Application/Repositories/UserProfileRepository.cs index 544f01b8..4484eca7 100644 --- a/BMA.EHR.Application/Repositories/UserProfileRepository.cs +++ b/BMA.EHR.Application/Repositories/UserProfileRepository.cs @@ -28,6 +28,24 @@ namespace BMA.EHR.Application.Repositories #region " Methods " + public string GetUserFullName(Guid keycloakId) + { + try + { + var data = _dbContext.Set().AsQueryable() + .Include(x => x.Prefix) + .Where(x => x.KeycloakId == keycloakId) + .Select(x => $"{x.Prefix!.Name}{x.FirstName} {x.LastName}") + .FirstOrDefault(); + + return data ?? "-"; + } + catch + { + throw; + } + } + public Guid GetUserOCId(Guid keycloakId) { try diff --git a/BMA.EHR.Infrastructure/InfrastructureServiceRegistration.cs b/BMA.EHR.Infrastructure/InfrastructureServiceRegistration.cs index ae07fa84..1d83e4db 100644 --- a/BMA.EHR.Infrastructure/InfrastructureServiceRegistration.cs +++ b/BMA.EHR.Infrastructure/InfrastructureServiceRegistration.cs @@ -27,7 +27,7 @@ namespace BMA.EHR.Infrastructure }), ServiceLifetime.Transient); - services.AddScoped(provider => provider.GetService()); + services.AddScoped(provider => provider.GetService()); return services; } diff --git a/BMA.EHR.Infrastructure/Persistence/LeaveDbContext.cs b/BMA.EHR.Infrastructure/Persistence/LeaveDbContext.cs index 5e2feddc..d1cfcd9a 100644 --- a/BMA.EHR.Infrastructure/Persistence/LeaveDbContext.cs +++ b/BMA.EHR.Infrastructure/Persistence/LeaveDbContext.cs @@ -4,7 +4,7 @@ using Microsoft.EntityFrameworkCore; namespace BMA.EHR.Infrastructure.Persistence { - public class LeaveDbContext : DbContext, IApplicationDBContext + public class LeaveDbContext : DbContext, ILeaveDbContext { #region " Check-In " diff --git a/BMA.EHR.Leave.Service/Controllers/LeaveController.cs b/BMA.EHR.Leave.Service/Controllers/LeaveController.cs index 36d4ffb0..ff1f576d 100644 --- a/BMA.EHR.Leave.Service/Controllers/LeaveController.cs +++ b/BMA.EHR.Leave.Service/Controllers/LeaveController.cs @@ -8,6 +8,7 @@ using BMA.EHR.Domain.Shared; using BMA.EHR.Infrastructure.Persistence; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using Sentry; using Swashbuckle.AspNetCore.Annotations; using System.Security.Claims; @@ -342,7 +343,7 @@ namespace BMA.EHR.Command.Service.Controllers EndTimeAfternoon = duty == null ? "00:00" : duty.EndTimeAfternoon, Description = duty == null ? "-" : duty.Description, CheckInTime = data.CheckIn, - CheckInId = data.CheckOut == null ? null : data.Id, + CheckInId = data.Id, }; } @@ -415,6 +416,75 @@ namespace BMA.EHR.Command.Service.Controllers 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 data = (await _userTimeStampRepository.GetTimeStampHistoryAsync(userId, year, page, pageSize, keyword)) + .Select(d => new CheckInHistoryDto + { + CheckInId = d.Id, + + CheckInDate = d.CheckIn.Date, + CheckInTime = d.CheckIn.ToString("HH:mm"), + CheckInLocation = d.CheckInPOI, + CheckInStatus = "NORMAL", // TODO : เอาจากที่ประมวลแล้ว + + CheckOutDate = d.CheckOut == null ? null : d.CheckOut.Value.Date, + CheckOutTime = d.CheckOut == null ? "" : d.CheckOut.Value.ToString("HH:mm"), + CheckOutLocation = d.CheckOutPOI ?? "", + CheckOutStatus = d.CheckOut == null ? "" : "NORMAL" // TODO : เอาจากที่ประมวลแล้ว + + }) + .ToList(); + + return Success(data); + } + + + [HttpGet("log-record")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status500InternalServerError)] + public async Task> LogRecordAsync(DateTime startDate, DateTime endDate, int page = 1, int pageSize = 10, string keyword = "") + { + var imgUrl = $"{_configuration["MinIO:Endpoint"]}{_configuration["MinIO:BucketName"]}"; + var data = (await _userTimeStampRepository.GetTimeStampHistoryForAdminAsync(startDate, endDate, page, pageSize, keyword)) + .Select(d => new CheckInHistoryForAdminDto + { + CheckInId = d.Id, + FullName = _userProfileRepository.GetUserFullName(d.KeycloakUserId), + + CheckInDate = d.CheckIn.Date, + CheckInTime = d.CheckIn.ToString("HH:mm"), + CheckInLocation = d.CheckInPOI, + CheckInLat = d.CheckInLat, + CheckInLon = d.CheckInLon, + 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, + CheckOutImageUrl = d.CheckOut == null ? "" : $"{imgUrl}/{d.CheckOutImageUrl}", + }) + .ToList(); + + return Success(data); + } + #endregion #endregion diff --git a/BMA.EHR.Leave.Service/DTOs/CheckIn/CheckInHistoryDto.cs b/BMA.EHR.Leave.Service/DTOs/CheckIn/CheckInHistoryDto.cs new file mode 100644 index 00000000..e6661414 --- /dev/null +++ b/BMA.EHR.Leave.Service/DTOs/CheckIn/CheckInHistoryDto.cs @@ -0,0 +1,23 @@ +namespace BMA.EHR.Command.Service.DTOs.CheckIn +{ + public class CheckInHistoryDto + { + public Guid CheckInId { get; set; } = Guid.Empty; + + public DateTime? CheckInDate { get; set; } = DateTime.MinValue; + + public string? CheckInTime { get; set; } = "00:00"; + + public string? CheckInLocation { get; set; } = string.Empty; + + public string? CheckInStatus { get; set; } = string.Empty; + + public DateTime? CheckOutDate { get; set; } = DateTime.MinValue; + + public string? CheckOutTime { get; set; } = "00:00"; + + public string? CheckOutLocation { get; set; } = string.Empty; + + public string? CheckOutStatus { get; set; } = string.Empty; + } +} diff --git a/BMA.EHR.Leave.Service/DTOs/CheckIn/CheckInHistoryForAdminDto.cs b/BMA.EHR.Leave.Service/DTOs/CheckIn/CheckInHistoryForAdminDto.cs new file mode 100644 index 00000000..d4fd1f60 --- /dev/null +++ b/BMA.EHR.Leave.Service/DTOs/CheckIn/CheckInHistoryForAdminDto.cs @@ -0,0 +1,33 @@ +namespace BMA.EHR.Command.Service.DTOs.CheckIn +{ + public class CheckInHistoryForAdminDto + { + public Guid CheckInId { get; set; } = Guid.Empty; + + public string FullName { get; set; } = string.Empty; + + public DateTime? CheckInDate { get; set; } = DateTime.MinValue; + + public string? CheckInTime { get; set; } = "00:00"; + + public string? CheckInLocation { get; set; } = string.Empty; + + public double? CheckInLat { get; set; } = 0; + + public double? CheckInLon { get; set; } = 0; + + public string? CheckInImageUrl { get; set; } = string.Empty; + + public DateTime? CheckOutDate { get; set; } = DateTime.MinValue; + + public string? CheckOutTime { get; set; } = "00:00"; + + public string? CheckOutLocation { get; set; } = string.Empty; + + public double? CheckOutLat { get; set; } = 0; + + public double? CheckOutLon { get; set; } = 0; + + public string? CheckOutImageUrl { get; set; } = string.Empty; + } +}