Compare commits

..

8 commits
v1.0.4 ... dev

Author SHA1 Message Date
harid
2f8effe7e4 Merge branch 'develop' into dev
All checks were successful
Build & Deploy on Dev / build (push) Successful in 49s
2026-05-12 16:25:05 +07:00
harid
081c00b869 fix เมื่อพ้นการสอบ 15 วัน ไม่ให้คลิกดูข้อมูลได้ #2485 2026-05-12 16:24:39 +07:00
harid
f1fc324828 Merge branch 'develop' into dev
All checks were successful
Build & Deploy on Dev / build (push) Successful in 51s
2026-05-01 14:22:32 +07:00
harid
cba02a4f6e api รายงานรายชื่อผู้มีสิทธิ์สอบ, รายชื่อผู้สอบคัดเลือกได้ 2026-05-01 14:22:07 +07:00
harid
208ad63abf Merge branch 'develop' into dev
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m7s
2026-04-28 10:02:43 +07:00
harid
d2f21a9181 fix #2367 2026-04-28 10:02:17 +07:00
harid
c4a6b6d1df Merge branch 'develop' into dev
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m5s
2026-03-31 15:34:09 +07:00
harid
16015cf3ef fix สถานะผลการสอบ #2386 2026-03-31 15:33:31 +07:00
6 changed files with 221 additions and 23 deletions

View file

@ -2187,8 +2187,8 @@ namespace BMA.EHR.Recurit.Exam.Service.Controllers
.Where(x => x.ExamId == examId)
.Join(_context.DisableScores.AsQueryable()
.Include(x => x.ScoreImport),
rc => new { /*rc.PeriodExam.Year,*/ rc.ExamId },
sc => new { /*sc.ScoreImport.Year,*/ sc.ExamId },
rc => new { PeriodExamId = rc.PeriodExam.Id, rc.ExamId },
sc => new { PeriodExamId = sc.ScoreImport.PeriodExamId, sc.ExamId },
(p, sr) => new
{
ExamID = p.ExamId,

View file

@ -749,6 +749,56 @@ namespace BMA.EHR.Recurit.Exam.Service.Controllers
}
}
/// <summary>
/// ดาวน์โหลดรายชื่อผู้มีสิทธิ์สอบ
/// </summary>
/// <param name="examId">รหัสรอบสมัคร</param>
/// <returns></returns>
/// <response code="200">เมื่อทำการอ่านโหลดผู้สมัครสอบสำเร็จ</response>
/// <response code="401">ไม่ได้ Login เข้าระบบ</response>
/// <response code="500">เมื่อเกิดข้อผิดพลาดในการทำงาน</response>
[HttpGet("download/candidate-exam/{examId:length(36)}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
public async Task<ActionResult<ResponseObject>> DownloadCandidateExamAsync(string examId)
{
try
{
var data = await _periodExamService.DownloadCandidateExamAsync(examId);
return Success(data);
}
catch (Exception ex)
{
return Error(ex);
}
}
/// <summary>
/// ดาวน์โหลดรายชื่อผู้สอบคัดเลือกได้
/// </summary>
/// <param name="examId">รหัสรอบสมัคร</param>
/// <returns></returns>
/// <response code="200">เมื่อทำการอ่านโหลดผู้สมัครสอบสำเร็จ</response>
/// <response code="401">ไม่ได้ Login เข้าระบบ</response>
/// <response code="500">เมื่อเกิดข้อผิดพลาดในการทำงาน</response>
[HttpGet("download/pass-exam/{examId:length(36)}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
public async Task<ActionResult<ResponseObject>> DownloadPassExamAsync(string examId)
{
try
{
var data = await _periodExamService.DownloadPassExamAsync(examId);
return Success(data);
}
catch (Exception ex)
{
return Error(ex, "เกิดข้อผิดพลาดในการแสดงรายงาน");
}
}
/// <summary>
/// โหลดผู้สมัครสอบ
/// </summary>

View file

@ -2,6 +2,7 @@
using Microsoft.Extensions.Configuration;
using Nest;
using System.Diagnostics;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text.Encodings.Web;
using System.Text.Json;
@ -29,6 +30,49 @@ namespace BMA.EHR.Recurit.Exam.Service.Core
SystemName = "recruiting";
}
/// <summary>
/// แกะ JWT token เพื่อดึง claims ต่างๆ
/// </summary>
private JwtSecurityToken? ParseToken(string token)
{
try
{
var tokenHandler = new JwtSecurityTokenHandler();
var jwtToken = tokenHandler.ReadJwtToken(token.Replace("Bearer ", ""));
return jwtToken;
}
catch
{
return null;
}
}
/// <summary>
/// ดึงค่า claim จาก token โดยลองชื่อหลายแบบ
/// </summary>
private string? GetClaimValue(JwtSecurityToken? token, params string[] claimNames)
{
if (token == null) return null;
foreach (var name in claimNames)
{
var claim = token.Claims.FirstOrDefault(c => c.Type == name);
if (claim != null)
return claim.Value;
}
return null;
}
/// <summary>
/// ดึงค่า Guid claim จาก token
/// </summary>
private Guid? GetGuidClaim(JwtSecurityToken? token, params string[] claimNames)
{
var value = GetClaimValue(token, claimNames);
if (Guid.TryParse(value, out var guid))
return guid;
return null;
}
public async Task Invoke(HttpContext context)
{
@ -92,28 +136,22 @@ namespace BMA.EHR.Recurit.Exam.Service.Core
// เปลี่ยน stream ของ Response เพื่อให้สามารถอ่านได้
context.Response.Body = memoryStream;
// Extract all required data from JWT token claims
var keycloakId = context.User?.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? Guid.Empty.ToString("D");
var tokenHeader = context.Request.Headers["Authorization"].ToString();
var prefix = context.User?.FindFirst("prefix")?.Value;
var firstName = context.User?.FindFirst("given_name")?.Value;
var lastName = context.User?.FindFirst("family_name")?.Value;
var preferredUsername = context.User?.FindFirst("preferred_username")?.Value;
// แกะ JWT token เพื่อดึง claims ต่างๆ
var jwtToken = ParseToken(tokenHeader);
var orgRootDnaId = context.User?.FindFirst("orgRootDnaId")?.Value;
var orgChild1DnaId = context.User?.FindFirst("orgChild1DnaId")?.Value;
var orgChild2DnaId = context.User?.FindFirst("orgChild2DnaId")?.Value;
var orgChild3DnaId = context.User?.FindFirst("orgChild3DnaId")?.Value;
var orgChild4DnaId = context.User?.FindFirst("orgChild4DnaId")?.Value;
// Parse Guid values safely
Guid? rootDnaId = Guid.TryParse(orgRootDnaId, out var rid) ? rid : null;
Guid? child1DnaId = Guid.TryParse(orgChild1DnaId, out var c1) ? c1 : null;
Guid? child2DnaId = Guid.TryParse(orgChild2DnaId, out var c2) ? c2 : null;
Guid? child3DnaId = Guid.TryParse(orgChild3DnaId, out var c3) ? c3 : null;
Guid? child4DnaId = Guid.TryParse(orgChild4DnaId, out var c4) ? c4 : null;
// ดึงข้อมูลจาก claims โดยลองชื่อหลายแบบ (camelCase, snake_case, ฯลฯ)
var prefix = GetClaimValue(jwtToken, "prefix", "Prefix", "PREFIX");
var firstName = GetClaimValue(jwtToken, "given_name", "firstname", "firstName", "FirstName", "FIRSTNAME");
var lastName = GetClaimValue(jwtToken, "family_name", "lastname", "lastName", "LastName", "LASTNAME");
var preferredUsername = GetClaimValue(jwtToken, "preferred_username", "preferred_username", "PreferredUsername");
var orgRootDnaId = GetGuidClaim(jwtToken, "orgRootDnaId", "org_root_dna_id", "OrgRootDnaId", "rootDnaId");
var orgChild1DnaId = GetGuidClaim(jwtToken, "orgChild1DnaId", "org_child1_dna", "OrgChild1Dna", "child1DnaId");
var orgChild2DnaId = GetGuidClaim(jwtToken, "orgChild2DnaId", "org_child2_dna", "OrgChild2Dna", "child2DnaId");
var orgChild3DnaId = GetGuidClaim(jwtToken, "orgChild3DnaId", "org_child3_dna", "OrgChild3Dna", "child3DnaId");
var orgChild4DnaId = GetGuidClaim(jwtToken, "orgChild4DnaId", "org_child4_dna", "OrgChild4Dna", "child4DnaId");
await _next(context); // ดำเนินการต่อไปยัง Middleware อื่น ๆ
@ -200,7 +238,7 @@ namespace BMA.EHR.Recurit.Exam.Service.Core
{
logType = logType,
ip = context.Connection.RemoteIpAddress?.ToString(),
rootId = rootDnaId,
rootId = orgRootDnaId?.ToString("D"),
systemName = SystemName,
startTimeStamp = startTime.ToString("o"),
endTimeStamp = endTime.ToString("o"),

View file

@ -9,5 +9,8 @@ namespace BMA.EHR.Recurit.Exam.Service.Response
public string? RejectDetail { get; set; }
public bool? IsShowExamInfo { get; set; }
public bool? IsShowResult { get; set; }
}
}

View file

@ -1978,7 +1978,19 @@ namespace BMA.EHR.Recurit.Exam.Service.Services
if (candidate == null)
throw new Exception(GlobalMessages.CandidateNotFound);
return new CandidateStatusResponse { Status = candidate.Status, RejectDetail = candidate.RejectDetail, IsShowExamInfo = candidate.IsShowExamInfo };
bool IsShowResult = true;
if (exam.AnnouncementDate != null)
{
var showResultEndDate = exam.AnnouncementDate.Value.Date.AddDays(15);
IsShowResult = DateTime.Now.Date <= showResultEndDate;
}
return new CandidateStatusResponse {
Status = candidate.Status,
RejectDetail = candidate.RejectDetail,
IsShowExamInfo = candidate.IsShowExamInfo,
IsShowResult = IsShowResult,
};
}
public async Task UserCheckCandidateService(string examId, string positionId, string status)

View file

@ -1625,6 +1625,101 @@ namespace BMA.EHR.Recurit.Exam.Service.Services
};
}
public async Task<dynamic> DownloadCandidateExamAsync(string examId)
{
var periodExam = await _context.PeriodExams.AsQueryable()
.Where(x => x.CheckDisability == false)
.FirstOrDefaultAsync(x => x.Id == Guid.Parse(examId));
if (periodExam == null)
throw new Exception(GlobalMessages.ExamNotFound);
var data = await _context.Candidates.AsQueryable()
.Include(x => x.PeriodExam)
.Where(x => x.PeriodExam == periodExam)
.Where(x => x.Status != "register")
.Where(x => x.ExamIdenNumber != null && x.ExamIdenNumber != "")
.OrderBy(x => x.ExamIdenNumber)
.Select(p => new
{
ExamId = p.ExamIdenNumber == null ? null : (p.ExamIdenNumber.ToThaiNumber()),
FullName = $"{p.PrefixName}{p.FirstName} {p.LastName}",
PositionName = "",
ExamName =
($"{p.PeriodExam.Name} ครั้งที่ {p.PeriodExam.Round}/{p.PeriodExam.Year.Value.ToThaiYear()}").ToThaiNumber(),
}).ToListAsync();
if (data.Count == 0)
throw new Exception("ไม่พบข้อมูลในระบบ");
var examName = data[0].ExamName;
return new
{
template = "rptCandidateList",
reportName = $"รายชื่อผู้มีสิทธิ์สอบ_{data.First().ExamName}",
data = new
{
examName = examName,
data = data
}
};
}
public async Task<dynamic> DownloadPassExamAsync(string examId)
{
var periodExam = await _context.PeriodExams.AsQueryable()
.Where(x => x.CheckDisability == false)
.FirstOrDefaultAsync(x => x.Id == Guid.Parse(examId));
if (periodExam == null)
throw new Exception(GlobalMessages.ExamNotFound);
var candidates = await _context.Candidates.AsQueryable()
.Include(x => x.PeriodExam)
.ThenInclude(x => x.ScoreImport)
.Where(x => x.PeriodExam == periodExam)
.Where(x => x.Status != "register")
.ToListAsync();
var data = candidates.Select((p, idx) => new
{
SeatNumber = p.SeatNumber == null ? "-" : (p.SeatNumber.ToThaiNumber()),
CitizenId = p.CitizenId == null ? "-" : (p.CitizenId.ToThaiNumber()),
FullName = $"{p.PrefixName}{p.FirstName} {p.LastName}",
DateOfBirth = p.DateOfBirth == null ? "-" : (p.DateOfBirth.Value.ToThaiShortDate()),
ExamName = ($"{p.PeriodExam.Name} ครั้งที่ {p.PeriodExam.Round}/{p.PeriodExam.Year.Value.ToThaiYear()}").ToThaiNumber(),
Number = p.Number == null ? (idx + 1).ToString().ToThaiNumber() : p.Number.ToThaiNumber(),
FullA = "",
SumA = "",
FullB = p.PointTotalB == null ? "-" : p.PointTotalB.ToString(),
SumB = p.PointB == null ? "-" : p.PointB.ToString(),
FullC = p.PointTotalC == null ? "-" : p.PointTotalC.ToString(),
SumC = p.PointC == null ? "-" : p.PointC.ToString(),
SumScore = ((Convert.ToInt32(p.PointB ?? "0") + Convert.ToInt32(p.PointC ?? "0")).ToString()).ToThaiNumber(),
ExamResult = p.Pass,
ExamThaiId = p.ExamIdenNumber == null ? "-" : p.ExamIdenNumber.ToThaiNumber(),
ExamId = p.ExamIdenNumber,
})
.OrderBy(x => x.ExamId)
.Where(x => x.ExamResult?.Trim() == "ได้")
.ToList();
if (data.Count == 0)
throw new Exception("ไม่พบข้อมูลในระบบ");
var examName = data[0].ExamName;
return new
{
template = "rptPassExamList",
reportName = $"รายชื่อผู้สอบแข่งขันได้_{periodExam.Name} ครั้งที่ {periodExam.Round}/{periodExam.Year.Value.ToThaiYear()}",
data = new
{
examName = examName,
data = data
}
};
}
public async Task<PeriodExam> GetsPaymentExamAsync(string examId)
{
var periodExam = await _context.PeriodExams.AsQueryable()