From f1a495de6a34882c8dd389a385daf417add42a8f Mon Sep 17 00:00:00 2001 From: harid Date: Wed, 8 Oct 2025 21:07:25 +0700 Subject: [PATCH 01/18] raw query to EF query #1833 --- Controllers/RecruitController.cs | 267 ++++++++++++++++++++----------- 1 file changed, 174 insertions(+), 93 deletions(-) diff --git a/Controllers/RecruitController.cs b/Controllers/RecruitController.cs index 31b22e2..9005a5a 100644 --- a/Controllers/RecruitController.cs +++ b/Controllers/RecruitController.cs @@ -1959,103 +1959,184 @@ namespace BMA.EHR.Recruit.Service.Controllers { try { - var data = new List(); - var p_Id = new MySqlParameter("@id", id); - int total = 0; + //var data = new List(); + //var p_Id = new MySqlParameter("@id", id); + //int total = 0; - // --------------------------- - // 1️. ดึงรายละเอียดสอบ (exam_info) - // --------------------------- - using (var cmd = _context.Database.GetDbConnection().CreateCommand()) + //// --------------------------- + //// 1️. ดึงรายละเอียดสอบ (exam_info) + //// --------------------------- + //using (var cmd = _context.Database.GetDbConnection().CreateCommand()) + //{ + // cmd.CommandTimeout = 0; + + // var sb = new StringBuilder(); + // sb.Append(@" + // SELECT + // examID, profileID, prefix, fullName, dateofbirth, gender, degree, major, majorgroup, + // certificateno, certificateIssueDate, score, result, examAttribute, remark, isspecial, + // applydate, university, position_name, hddPosition, typeTest, position_level, position_type, + // exam_name, exam_order, score_year, + // COUNT(*) OVER() AS total_count + // FROM exam_info + // WHERE recruit_import_id = @id + // "); + + // cmd.Parameters.Clear(); + // cmd.Parameters.Add(p_Id); + + // var keywordParam = req.keyword?.Trim(); + // if (!string.IsNullOrWhiteSpace(keywordParam)) + // { + // sb.Append(@" + // AND ( + // examID LIKE @kw + // OR profileID LIKE @kw + // OR prefix LIKE @kw + // OR fullName LIKE @kw + // OR hddPosition LIKE @kw + // OR position_name LIKE @kw + // ) + // "); + // cmd.Parameters.Add(new MySqlParameter("@kw", $"%{keywordParam}%")); + // } + + // // --------------------------- + // // Paging + Sorting + // // --------------------------- + // sb.Append(" ORDER BY examID "); + // sb.Append(" LIMIT @PageSize OFFSET @Offset "); + // cmd.Parameters.Add(new MySqlParameter("@PageSize", req.PageSize)); + // cmd.Parameters.Add(new MySqlParameter("@Offset", (req.Page - 1) * req.PageSize)); + + // cmd.CommandText = sb.ToString(); + + // _context.Database.OpenConnection(); + + // // --------------------------- + // // ดึงข้อมูล + total + // // --------------------------- + // using (var reader = cmd.ExecuteReader()) + // { + // while (reader.Read()) + // { + // if (total == 0) + // total = Convert.ToInt32(reader["total_count"]); + + // data.Add(new + // { + // examID = reader["examID"].ToString(), + // profileID = reader["profileID"].ToString(), + // prefix = reader["prefix"].ToString(), + // fullName = reader["fullName"].ToString(), + // dateOfBirth = reader["dateofbirth"] == DBNull.Value ? "" : Convert.ToDateTime(reader["dateofbirth"]).ToThaiShortDate(), + // gender = reader["gender"].ToString(), + // degree = reader["degree"].ToString(), + // major = reader["major"].ToString(), + // majorgroup = reader["majorgroup"].ToString(), + // certificateNo = reader["certificateno"].ToString(), + // certificateIssueDate = reader["certificateIssueDate"] == DBNull.Value ? "" : Convert.ToDateTime(reader["certificateIssueDate"]).ToThaiShortDate(), + // ExamScore = reader["score"] == DBNull.Value ? 0 : Convert.ToDecimal(reader["score"]), + // ExamResult = reader["result"].ToString(), + // ExamAttribute = reader["examAttribute"].ToString(), + // Remark = reader["remark"].ToString(), + // IsSpecial = reader["isspecial"].ToString(), + // applyDate = reader["applydate"] == DBNull.Value ? "" : Convert.ToDateTime(reader["applydate"]).ToThaiShortDate(), + // university = reader["university"].ToString(), + // position_name = reader["position_name"].ToString(), + // hddPosition = reader["hddPosition"].ToString(), + // typeTest = reader["typeTest"].ToString(), + // position_level = reader["position_level"].ToString(), + // position_type = reader["position_type"].ToString(), + // exam_name = reader["exam_name"].ToString(), + // exam_order = reader["exam_order"].ToString(), + // score_year = Convert.ToInt32(reader["score_year"]).ToThaiYear().ToString() + // }); + // } + // } + //} + + var query = _context.Recruits + .Include(x => x.RecruitImport) + .Include(x => x.Educations) + .Include(x => x.Certificates) + .OrderBy(x => x.ExamId) + .Where(x => x.RecruitImport != null && x.RecruitImport.Id == id); + + var keywordParam = req.keyword?.Trim(); + if (!string.IsNullOrWhiteSpace(keywordParam)) { - cmd.CommandTimeout = 0; - - var sb = new StringBuilder(); - sb.Append(@" - SELECT - examID, profileID, prefix, fullName, dateofbirth, gender, degree, major, majorgroup, - certificateno, certificateIssueDate, score, result, examAttribute, remark, isspecial, - applydate, university, position_name, hddPosition, typeTest, position_level, position_type, - exam_name, exam_order, score_year, - COUNT(*) OVER() AS total_count - FROM exam_info - WHERE recruit_import_id = @id - "); - - cmd.Parameters.Clear(); - cmd.Parameters.Add(p_Id); - - var keywordParam = req.keyword?.Trim(); - if (!string.IsNullOrWhiteSpace(keywordParam)) - { - sb.Append(@" - AND ( - examID LIKE @kw - OR profileID LIKE @kw - OR prefix LIKE @kw - OR fullName LIKE @kw - OR hddPosition LIKE @kw - OR position_name LIKE @kw - ) - "); - cmd.Parameters.Add(new MySqlParameter("@kw", $"%{keywordParam}%")); - } - - // --------------------------- - // Paging + Sorting - // --------------------------- - sb.Append(" ORDER BY examID "); - sb.Append(" LIMIT @PageSize OFFSET @Offset "); - cmd.Parameters.Add(new MySqlParameter("@PageSize", req.PageSize)); - cmd.Parameters.Add(new MySqlParameter("@Offset", (req.Page - 1) * req.PageSize)); - - cmd.CommandText = sb.ToString(); - - _context.Database.OpenConnection(); - - // --------------------------- - // ดึงข้อมูล + total - // --------------------------- - using (var reader = cmd.ExecuteReader()) - { - while (reader.Read()) - { - if (total == 0) - total = Convert.ToInt32(reader["total_count"]); - - data.Add(new - { - examID = reader["examID"].ToString(), - profileID = reader["profileID"].ToString(), - prefix = reader["prefix"].ToString(), - fullName = reader["fullName"].ToString(), - dateOfBirth = reader["dateofbirth"] == DBNull.Value ? "" : Convert.ToDateTime(reader["dateofbirth"]).ToThaiShortDate(), - gender = reader["gender"].ToString(), - degree = reader["degree"].ToString(), - major = reader["major"].ToString(), - majorgroup = reader["majorgroup"].ToString(), - certificateNo = reader["certificateno"].ToString(), - certificateIssueDate = reader["certificateIssueDate"] == DBNull.Value ? "" : Convert.ToDateTime(reader["certificateIssueDate"]).ToThaiShortDate(), - ExamScore = reader["score"] == DBNull.Value ? 0 : Convert.ToDecimal(reader["score"]), - ExamResult = reader["result"].ToString(), - ExamAttribute = reader["examAttribute"].ToString(), - Remark = reader["remark"].ToString(), - IsSpecial = reader["isspecial"].ToString(), - applyDate = reader["applydate"] == DBNull.Value ? "" : Convert.ToDateTime(reader["applydate"]).ToThaiShortDate(), - university = reader["university"].ToString(), - position_name = reader["position_name"].ToString(), - hddPosition = reader["hddPosition"].ToString(), - typeTest = reader["typeTest"].ToString(), - position_level = reader["position_level"].ToString(), - position_type = reader["position_type"].ToString(), - exam_name = reader["exam_name"].ToString(), - exam_order = reader["exam_order"].ToString(), - score_year = Convert.ToInt32(reader["score_year"]).ToThaiYear().ToString() - }); - } - } + query = query.Where(x => + x.ExamId.Contains(keywordParam) || + x.CitizenId.Contains(keywordParam) || + x.Prefix.Contains(keywordParam) || + x.FirstName.Contains(keywordParam) || + x.LastName.Contains(keywordParam) || + x.HddPosition.Contains(keywordParam) || + x.PositionName.Contains(keywordParam) + ); } + int total = await query.CountAsync(); + + query = query + .Skip((req.Page - 1) * req.PageSize) + .Take(req.PageSize); + + var data = await query + .GroupJoin( + _context.RecruitScores.Include(x => x.ScoreImport), + rc => new { rc.RecruitImport!.Id, rc.ExamId }, + sc => new { Id = sc.ScoreImport!.RecruitImportId, sc.ExamId }, + (recruit, scores) => new { recruit, scores } + ) + .SelectMany( + x => x.scores.DefaultIfEmpty(), + (x, sr) => new + { + examID = x.recruit.ExamId, + profileID = x.recruit.CitizenId, + prefix = x.recruit.Prefix, + fullName = $"{x.recruit.FirstName} {x.recruit.LastName}", + dateOfBirth = x.recruit.DateOfBirth != null && x.recruit.DateOfBirth != DateTime.MinValue + ? x.recruit.DateOfBirth.ToThaiShortDate() + : "", + gender = x.recruit.Gendor, + degree = x.recruit.Educations.Any() ? x.recruit.Educations.First().Degree : "", + major = x.recruit.Educations.Any() ? x.recruit.Educations.First().Major : "", + certificateNo = x.recruit.Certificates.Any() + ? x.recruit.Certificates.First().CertificateNo ?? "" + : "", + certificateIssueDate = x.recruit.Certificates.Any() && x.recruit.Certificates.First().IssueDate != null && x.recruit.Certificates.First().IssueDate != DateTime.MinValue + ? x.recruit.Certificates.First().IssueDate.ToThaiShortDate() + : "", + examScore = sr == null ? 0.0 : sr.TotalScore, + examResult = sr == null ? "" : sr.ExamStatus, + examAttribute = x.recruit.Certificates.Any() && x.recruit.Certificates.First().IssueDate != null + ? _recruitService.CheckValidCertificate(x.recruit.Certificates.First().IssueDate, 5) + ? "มีคุณสมบัติ" : "ไม่มีคุณสมบัติ" + : "ไม่มีคุณสมบัติ", + remark = x.recruit.Remark, + isSpecial = x.recruit.Isspecial == "Y" ? x.recruit.Isspecial : "", + applyDate = x.recruit.ApplyDate != null && x.recruit.ApplyDate != DateTime.MinValue + ? x.recruit.ApplyDate.ToThaiShortDate() + : "", + university = x.recruit.Educations.Any() ? x.recruit.Educations.First().University : "", + position_name = x.recruit.PositionName, + hddPosition = x.recruit.HddPosition, + typeTest = x.recruit.typeTest, + position_level = x.recruit.PositionLevel, + position_type = x.recruit.PositionType, + exam_name = x.recruit.RecruitImport!.Name, + exam_order = x.recruit.RecruitImport != null && x.recruit.RecruitImport.Order != null + ? x.recruit.RecruitImport.Order.ToString() + : "", + score_year = x.recruit.RecruitImport != null && x.recruit.RecruitImport.Year != null + ? x.recruit.RecruitImport.Year.ToThaiYear().ToString() + : "", + }) + .ToListAsync(); + // --------------------------- // 3️. ดึงสรุปคะแนน // --------------------------- From db6ce8d28dcaf5f7fa310d791f0a9ae8615871bc Mon Sep 17 00:00:00 2001 From: kittapath <> Date: Wed, 8 Oct 2025 23:25:58 +0700 Subject: [PATCH 02/18] =?UTF-8?q?=E0=B9=81=E0=B8=81=E0=B9=89=20total?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/discord-notify.yml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 .github/workflows/discord-notify.yml diff --git a/.github/workflows/discord-notify.yml b/.github/workflows/discord-notify.yml new file mode 100644 index 0000000..ce4ee51 --- /dev/null +++ b/.github/workflows/discord-notify.yml @@ -0,0 +1,22 @@ +name: Discord PR Notify + +on: + pull_request: + types: [opened] + +jobs: + discord: + runs-on: ubuntu-latest + steps: + - name: Send Discord + run: | + curl -X POST "${{ secrets.DISCORD_WEBHOOK_PULLREQUEST }}" \ + -H "Content-Type: application/json" \ + -d '{ + "embeds": [{ + "title": "🔔 **Service:** ${{ github.repository }}", + "description": "👤 **Author:** ${{ github.event.pull_request.user.login }}\n🌿 **Branch:** ${{ github.event.pull_request.head.ref }} → ${{ github.event.pull_request.base.ref }}\n📦 **Pull Request:** [#${{ github.event.pull_request.number }} - ${{ github.event.pull_request.title }}](${{ github.event.pull_request.html_url }})", + "color": 5814783, + "timestamp": "${{ github.event.pull_request.created_at }}" + }] + }' From ea54763fb61a1c64177b6d8c97c2f30956a48b98 Mon Sep 17 00:00:00 2001 From: harid Date: Thu, 9 Oct 2025 10:25:59 +0700 Subject: [PATCH 03/18] comment --- Controllers/RecruitController.cs | 101 +------------------------------ 1 file changed, 2 insertions(+), 99 deletions(-) diff --git a/Controllers/RecruitController.cs b/Controllers/RecruitController.cs index 9005a5a..728c5b4 100644 --- a/Controllers/RecruitController.cs +++ b/Controllers/RecruitController.cs @@ -1959,103 +1959,6 @@ namespace BMA.EHR.Recruit.Service.Controllers { try { - //var data = new List(); - //var p_Id = new MySqlParameter("@id", id); - //int total = 0; - - //// --------------------------- - //// 1️. ดึงรายละเอียดสอบ (exam_info) - //// --------------------------- - //using (var cmd = _context.Database.GetDbConnection().CreateCommand()) - //{ - // cmd.CommandTimeout = 0; - - // var sb = new StringBuilder(); - // sb.Append(@" - // SELECT - // examID, profileID, prefix, fullName, dateofbirth, gender, degree, major, majorgroup, - // certificateno, certificateIssueDate, score, result, examAttribute, remark, isspecial, - // applydate, university, position_name, hddPosition, typeTest, position_level, position_type, - // exam_name, exam_order, score_year, - // COUNT(*) OVER() AS total_count - // FROM exam_info - // WHERE recruit_import_id = @id - // "); - - // cmd.Parameters.Clear(); - // cmd.Parameters.Add(p_Id); - - // var keywordParam = req.keyword?.Trim(); - // if (!string.IsNullOrWhiteSpace(keywordParam)) - // { - // sb.Append(@" - // AND ( - // examID LIKE @kw - // OR profileID LIKE @kw - // OR prefix LIKE @kw - // OR fullName LIKE @kw - // OR hddPosition LIKE @kw - // OR position_name LIKE @kw - // ) - // "); - // cmd.Parameters.Add(new MySqlParameter("@kw", $"%{keywordParam}%")); - // } - - // // --------------------------- - // // Paging + Sorting - // // --------------------------- - // sb.Append(" ORDER BY examID "); - // sb.Append(" LIMIT @PageSize OFFSET @Offset "); - // cmd.Parameters.Add(new MySqlParameter("@PageSize", req.PageSize)); - // cmd.Parameters.Add(new MySqlParameter("@Offset", (req.Page - 1) * req.PageSize)); - - // cmd.CommandText = sb.ToString(); - - // _context.Database.OpenConnection(); - - // // --------------------------- - // // ดึงข้อมูล + total - // // --------------------------- - // using (var reader = cmd.ExecuteReader()) - // { - // while (reader.Read()) - // { - // if (total == 0) - // total = Convert.ToInt32(reader["total_count"]); - - // data.Add(new - // { - // examID = reader["examID"].ToString(), - // profileID = reader["profileID"].ToString(), - // prefix = reader["prefix"].ToString(), - // fullName = reader["fullName"].ToString(), - // dateOfBirth = reader["dateofbirth"] == DBNull.Value ? "" : Convert.ToDateTime(reader["dateofbirth"]).ToThaiShortDate(), - // gender = reader["gender"].ToString(), - // degree = reader["degree"].ToString(), - // major = reader["major"].ToString(), - // majorgroup = reader["majorgroup"].ToString(), - // certificateNo = reader["certificateno"].ToString(), - // certificateIssueDate = reader["certificateIssueDate"] == DBNull.Value ? "" : Convert.ToDateTime(reader["certificateIssueDate"]).ToThaiShortDate(), - // ExamScore = reader["score"] == DBNull.Value ? 0 : Convert.ToDecimal(reader["score"]), - // ExamResult = reader["result"].ToString(), - // ExamAttribute = reader["examAttribute"].ToString(), - // Remark = reader["remark"].ToString(), - // IsSpecial = reader["isspecial"].ToString(), - // applyDate = reader["applydate"] == DBNull.Value ? "" : Convert.ToDateTime(reader["applydate"]).ToThaiShortDate(), - // university = reader["university"].ToString(), - // position_name = reader["position_name"].ToString(), - // hddPosition = reader["hddPosition"].ToString(), - // typeTest = reader["typeTest"].ToString(), - // position_level = reader["position_level"].ToString(), - // position_type = reader["position_type"].ToString(), - // exam_name = reader["exam_name"].ToString(), - // exam_order = reader["exam_order"].ToString(), - // score_year = Convert.ToInt32(reader["score_year"]).ToThaiYear().ToString() - // }); - // } - // } - //} - var query = _context.Recruits .Include(x => x.RecruitImport) .Include(x => x.Educations) @@ -2128,8 +2031,8 @@ namespace BMA.EHR.Recruit.Service.Controllers position_level = x.recruit.PositionLevel, position_type = x.recruit.PositionType, exam_name = x.recruit.RecruitImport!.Name, - exam_order = x.recruit.RecruitImport != null && x.recruit.RecruitImport.Order != null - ? x.recruit.RecruitImport.Order.ToString() + exam_order = x.recruit.RecruitImport != null && x.recruit.RecruitImport.Order != null + ? x.recruit.RecruitImport.Order.ToString() : "", score_year = x.recruit.RecruitImport != null && x.recruit.RecruitImport.Year != null ? x.recruit.RecruitImport.Year.ToThaiYear().ToString() From fb726ca789b61548262b3929aedc9ab8759b290f Mon Sep 17 00:00:00 2001 From: kittapath <> Date: Sat, 11 Oct 2025 18:37:46 +0700 Subject: [PATCH 04/18] test --- Controllers/RecruitController.cs | 14 +- Requests/Recruits/RecruitDateRequest.cs | 7 + Services/RecruitService.cs | 278 ++++++++++++++---------- 3 files changed, 173 insertions(+), 126 deletions(-) create mode 100644 Requests/Recruits/RecruitDateRequest.cs diff --git a/Controllers/RecruitController.cs b/Controllers/RecruitController.cs index 728c5b4..5ea2895 100644 --- a/Controllers/RecruitController.cs +++ b/Controllers/RecruitController.cs @@ -2034,8 +2034,8 @@ namespace BMA.EHR.Recruit.Service.Controllers exam_order = x.recruit.RecruitImport != null && x.recruit.RecruitImport.Order != null ? x.recruit.RecruitImport.Order.ToString() : "", - score_year = x.recruit.RecruitImport != null && x.recruit.RecruitImport.Year != null - ? x.recruit.RecruitImport.Year.ToThaiYear().ToString() + score_year = x.recruit.RecruitImport != null && x.recruit.RecruitImport.Year != null + ? x.recruit.RecruitImport.Year.ToThaiYear().ToString() : "", }) .ToListAsync(); @@ -2074,7 +2074,7 @@ namespace BMA.EHR.Recruit.Service.Controllers } else { - header = new + header = new { count = _count, pass = 0, @@ -2083,7 +2083,7 @@ namespace BMA.EHR.Recruit.Service.Controllers other = 0 }; } - + // --------------------------- // 4️. ดึง period @@ -2611,15 +2611,15 @@ namespace BMA.EHR.Recruit.Service.Controllers /// เมื่อโอนคนแข่งขันไปบรรจุสำเร็จ /// ไม่ได้ Login เข้าระบบ /// เมื่อเกิดข้อผิดพลาดในการทำงาน - [HttpGet("placement/{examId:length(36)}")] + [HttpPost("placement/{examId:length(36)}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] - public async Task> UpdateAsyncRecruitToPlacement(Guid examId) + public async Task> UpdateAsyncRecruitToPlacement(Guid examId, [FromBody] RecruitDateRequest req) { try { - await _recruitService.UpdateAsyncRecruitToPlacement(examId); + await _recruitService.UpdateAsyncRecruitToPlacement(examId, req.AccountStartDate); return Success(); } catch (Exception ex) diff --git a/Requests/Recruits/RecruitDateRequest.cs b/Requests/Recruits/RecruitDateRequest.cs new file mode 100644 index 0000000..ab9d2ba --- /dev/null +++ b/Requests/Recruits/RecruitDateRequest.cs @@ -0,0 +1,7 @@ +namespace BMA.EHR.Recruit.Service.Requests.Recruits +{ + public class RecruitDateRequest + { + public DateTime AccountStartDate { get; set; } + } +} diff --git a/Services/RecruitService.cs b/Services/RecruitService.cs index ec16ef0..8fc146b 100644 --- a/Services/RecruitService.cs +++ b/Services/RecruitService.cs @@ -175,7 +175,7 @@ namespace BMA.EHR.Recruit.Service.Services await _minIOService.DeleteFileAsync(doc_id); } - public async Task UpdateAsyncRecruitToPlacement(Guid examId) + public async Task UpdateAsyncRecruitToPlacement(Guid examId, DateTime accountStartDate) { try { @@ -187,19 +187,26 @@ namespace BMA.EHR.Recruit.Service.Services var _placement = await _contextMetadata.Placements.AsQueryable() .FirstOrDefaultAsync(x => x.PlacementType.Name == "สอบแข่งขัน" && x.RefId == recruitImport.Id); - if (_placement != null) - throw new Exception("รอบการสอบนี้ได้ทำการบรรจุไปแล้ว"); + // if (_placement != null) + // throw new Exception("รอบการสอบนี้ได้ทำการบรรจุไปแล้ว"); + + // 🚀 Pre-load all lookup data once + var placementTypesCache = await _contextMetadata.PlacementTypes.ToListAsync(); + var provincesCache = await _contextOrg.province.ToListAsync(); + var districtsCache = await _contextOrg.district.ToListAsync(); + var subDistrictsCache = await _contextOrg.subDistrict.ToListAsync(); + var educationLevelsCache = await _contextOrg.educationLevel.ToListAsync(); var placement = new Placement { Name = recruitImport.Name, RefId = recruitImport.Id, - Round = recruitImport.Order == null ? "" : recruitImport.Order.ToString(), - Year = (int)(recruitImport.Year == null ? 0 : recruitImport.Year), + Round = recruitImport.Order.ToString() ?? "", + Year = recruitImport.Year, Number = await _context.Recruits.AsQueryable().Where(x => x.RecruitImport == recruitImport).CountAsync(), - PlacementType = await _contextMetadata.PlacementTypes.FirstOrDefaultAsync(x => x.Name.Trim().ToUpper().Contains("สอบแข่งขัน")) == null ? await _contextMetadata.PlacementTypes.FirstOrDefaultAsync() : await _contextMetadata.PlacementTypes.FirstOrDefaultAsync(x => x.Name.Trim().ToUpper().Contains("สอบแข่งขัน")), - StartDate = DateTime.Now, - EndDate = DateTime.Now.AddYears(2).AddDays(-1), + PlacementType = placementTypesCache.FirstOrDefault(x => x.Name.Trim().ToUpper().Contains("สอบแข่งขัน")) ?? placementTypesCache.First(), + StartDate = accountStartDate, + EndDate = accountStartDate.AddYears(2).AddDays(-1), CreatedAt = DateTime.Now, CreatedUserId = UserId ?? "", CreatedFullName = FullName ?? "", @@ -208,6 +215,8 @@ namespace BMA.EHR.Recruit.Service.Services LastUpdateFullName = FullName ?? "", }; await _contextMetadata.Placements.AddAsync(placement); + + // 🚀 Load all related data with single queries var candidates = await _context.Recruits.AsQueryable() .Include(x => x.Addresses) .Include(x => x.Certificates) @@ -215,47 +224,61 @@ namespace BMA.EHR.Recruit.Service.Services .Include(x => x.Occupations) .Where(x => x.RecruitImport == recruitImport) .ToListAsync(); + + var scoreImport = await _context.ScoreImports.AsQueryable() + .FirstOrDefaultAsync(x => x.RecruitImport == recruitImport); + + var recruitScores = await _context.RecruitScores.AsQueryable() + .Where(x => x.ScoreImport == scoreImport && x.ExamStatus == "ผ่าน") + .ToListAsync(); + + var recruitScoresDict = recruitScores.ToDictionary(x => x.ExamId, x => x); + + // 🚀 Prepare HTTP client once + var httpClient = new HttpClient(); + httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token?.Replace("Bearer ", "")); + httpClient.DefaultRequestHeaders.Add("api_key", _configuration["API_KEY"]); + + // 🚀 Batch HTTP requests + var orgTasks = candidates.Select(async candidate => + { + var apiUrl = $"{_configuration["API"]}/org/profile/citizenid/position/{candidate.CitizenId}"; + try + { + var response = await httpClient.GetStringAsync(apiUrl); + return new { candidate.CitizenId, org = JsonConvert.DeserializeObject(response) }; + } + catch + { + return new { candidate.CitizenId, org = (dynamic?)null }; + } + }).ToList(); + + var orgResults = await Task.WhenAll(orgTasks); + var orgDict = orgResults.ToDictionary(x => x.CitizenId, x => x.org); + + // 🚀 Prepare batch inserts + var placementProfiles = new List(); + var placementEducations = new List(); + var placementCertificates = new List(); + foreach (var candidate in candidates) { - var IsOfficer = false; - dynamic org = null; - var apiUrl = $"{_configuration["API"]}/org/profile/citizenid/position/{candidate.CitizenId}"; - using (var client = new HttpClient()) - { - client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token.Replace("Bearer ", "")); - client.DefaultRequestHeaders.Add("api_key", _configuration["API_KEY"]); - var _req = new HttpRequestMessage(HttpMethod.Get, apiUrl); - var _res = await client.SendAsync(_req); - var _result = await _res.Content.ReadAsStringAsync(); - - org = JsonConvert.DeserializeObject(_result); - - if (org == null || org.result == null) - { - IsOfficer = false; - } - else - { - IsOfficer = true; - } - } - var Address = candidate.Addresses.FirstOrDefault() == null ? null : $"{candidate.Addresses.FirstOrDefault().Address}"; - var Moo = candidate.Addresses.FirstOrDefault() == null ? null : $" หมู่ {candidate.Addresses.FirstOrDefault().Moo}"; - var Soi = candidate.Addresses.FirstOrDefault() == null ? null : $" ซอย {candidate.Addresses.FirstOrDefault().Soi}"; - var Road = candidate.Addresses.FirstOrDefault() == null ? null : $" ถนน {candidate.Addresses.FirstOrDefault().Road}"; - var Address1 = candidate.Addresses.FirstOrDefault() == null ? null : $"{candidate.Addresses.FirstOrDefault().Address1}"; - var Moo1 = candidate.Addresses.FirstOrDefault() == null ? null : $" หมู่ {candidate.Addresses.FirstOrDefault().Moo1}"; - var Soi1 = candidate.Addresses.FirstOrDefault() == null ? null : $" ซอย {candidate.Addresses.FirstOrDefault().Soi1}"; - var Road1 = candidate.Addresses.FirstOrDefault() == null ? null : $" ถนน {candidate.Addresses.FirstOrDefault().Road1}"; - var scoreImport = await _context.ScoreImports.AsQueryable() - .FirstOrDefaultAsync(x => x.RecruitImport == recruitImport); - var recruitScore = await _context.RecruitScores.AsQueryable() - .Where(x => x.ScoreImport == scoreImport) - .Where(x => x.ExamId == candidate.ExamId) - .Where(x => x.ExamStatus == "ผ่าน") - .FirstOrDefaultAsync(x => x.ExamId == candidate.ExamId && x.ScoreImport == scoreImport); - if (recruitScore == null) + if (!recruitScoresDict.TryGetValue(candidate.ExamId, out var recruitScore)) continue; + + var org = orgDict.GetValueOrDefault(candidate.CitizenId); + var isOfficer = org?.result != null; + + // 🚀 Cache repeated calculations + var firstAddress = candidate.Addresses?.FirstOrDefault(); + var firstEducation = candidate.Educations?.FirstOrDefault(); + var firstCertificate = candidate.Certificates?.FirstOrDefault(); + var firstOccupation = candidate.Occupations?.FirstOrDefault(); + + var registAddress = BuildAddress(firstAddress?.Address, firstAddress?.Moo, firstAddress?.Soi, firstAddress?.Road); + var currentAddress = BuildAddress(firstAddress?.Address1, firstAddress?.Moo1, firstAddress?.Soi1, firstAddress?.Road1); + var placementProfile = new PlacementProfile { Placement = placement, @@ -272,90 +295,88 @@ namespace BMA.EHR.Recruit.Service.Services DateOfBirth = candidate.DateOfBirth, Relationship = candidate.Marry, CitizenId = candidate.CitizenId, - CitizenProvinceId = _contextOrg.province.FirstOrDefault(x => x.name == candidate.CitizenCardIssuer)?.Id ?? null, + CitizenProvinceId = provincesCache.FirstOrDefault(x => x.name == candidate.CitizenCardIssuer)?.Id, CitizenDate = candidate.CitizenCardExpireDate, - Telephone = candidate?.Addresses?.FirstOrDefault()?.Telephone ?? null, - MobilePhone = candidate?.Addresses?.FirstOrDefault()?.Mobile ?? null, - RegistAddress = $"{Address}{Moo}{Soi}{Road}", - RegistProvinceId = _contextOrg.province.FirstOrDefault(x => x.name == (candidate!.Addresses!.FirstOrDefault()!.Province ?? ""))?.Id ?? null, - RegistDistrictId = _contextOrg.district.FirstOrDefault(x => x.name == (candidate!.Addresses!.FirstOrDefault()!.Amphur ?? ""))?.Id ?? null, - RegistSubDistrictId = _contextOrg.subDistrict.FirstOrDefault(x => x.name == (candidate!.Addresses!.FirstOrDefault()!.District ?? ""))?.Id ?? null, - RegistZipCode = candidate?.Addresses?.FirstOrDefault()?.ZipCode ?? null, + Telephone = firstAddress?.Telephone, + MobilePhone = firstAddress?.Mobile, + RegistAddress = registAddress, + RegistProvinceId = provincesCache.FirstOrDefault(x => x.name == firstAddress?.Province)?.Id, + RegistDistrictId = districtsCache.FirstOrDefault(x => x.name == firstAddress?.Amphur)?.Id, + RegistSubDistrictId = subDistrictsCache.FirstOrDefault(x => x.name == firstAddress?.District)?.Id, + RegistZipCode = firstAddress?.ZipCode, RegistSame = false, - CurrentAddress = $"{Address1}{Moo1}{Soi1}{Road1}", - CurrentProvinceId = _contextOrg.province.FirstOrDefault(x => x.name == (candidate!.Addresses!.FirstOrDefault()!.Province1 ?? ""))?.Id ?? null, - CurrentDistrictId = _contextOrg.district.FirstOrDefault(x => x.name == (candidate!.Addresses!.FirstOrDefault()!.Amphur1 ?? ""))?.Id ?? null, - CurrentSubDistrictId = _contextOrg.subDistrict.FirstOrDefault(x => x.name == (candidate!.Addresses!.FirstOrDefault()!.District1 ?? ""))?.Id ?? null, - CurrentZipCode = candidate?.Addresses?.FirstOrDefault()?.ZipCode1 ?? null, - Marry = candidate?.Marry?.Contains("สมรส") ?? false, - + CurrentAddress = currentAddress, + CurrentProvinceId = provincesCache.FirstOrDefault(x => x.name == firstAddress?.Province1)?.Id, + CurrentDistrictId = districtsCache.FirstOrDefault(x => x.name == firstAddress?.Amphur1)?.Id, + CurrentSubDistrictId = subDistrictsCache.FirstOrDefault(x => x.name == firstAddress?.District1)?.Id, + CurrentZipCode = firstAddress?.ZipCode1, + Marry = candidate.Marry?.Contains("สมรส") ?? false, OccupationPositionType = "other", - OccupationTelephone = candidate?.Occupations?.FirstOrDefault()?.Telephone ?? null, - OccupationPosition = candidate?.Occupations?.FirstOrDefault()?.Position ?? null, - - PointTotalA = recruitScore == null ? null : Convert.ToDouble(recruitScore.FullA), - PointA = recruitScore == null ? null : Convert.ToDouble(recruitScore.SumA), - PointTotalB = recruitScore == null ? null : Convert.ToDouble(recruitScore.FullB), - PointB = recruitScore == null ? null : Convert.ToDouble(recruitScore.SumB), - PointTotalC = recruitScore == null ? null : Convert.ToDouble(recruitScore.FullC), - PointC = recruitScore == null ? null : Convert.ToDouble(recruitScore.SumC), - ExamNumber = recruitScore == null || int.TryParse(recruitScore.Number, out int n) == false ? null : Convert.ToInt32(recruitScore.Number), + OccupationTelephone = firstOccupation?.Telephone, + OccupationPosition = firstOccupation?.Position, + PointTotalA = Convert.ToDouble(recruitScore.FullA), + PointA = Convert.ToDouble(recruitScore.SumA), + PointTotalB = Convert.ToDouble(recruitScore.FullB), + PointB = Convert.ToDouble(recruitScore.SumB), + PointTotalC = Convert.ToDouble(recruitScore.FullC), + PointC = Convert.ToDouble(recruitScore.SumC), + ExamNumber = int.TryParse(recruitScore.Number, out int n) ? n : null, ExamRound = null, IsRelief = false, PlacementStatus = "UN-CONTAIN", - Pass = recruitScore == null ? null : recruitScore.ExamStatus, + Pass = recruitScore.ExamStatus, RemarkHorizontal = "โดยมีเงื่อนไขว่าต้องปฏิบัติงานให้กรุงเทพมหานครเป็นระยะเวลาไม่น้อยกว่า ๕ ปี นับแต่วันที่ได้รับการบรรจุและแต่งตั้ง โดยห้ามโอนไปหน่วยงานหรือส่วนราชการอื่น เว้นเเต่ลาออกจากราชการ", - Amount = org?.result?.amount ?? null, - PositionSalaryAmount = org?.result?.positionSalaryAmount ?? null, - MouthSalaryAmount = org?.result?.mouthSalaryAmount ?? null, + Amount = org?.result?.amount, + PositionSalaryAmount = org?.result?.positionSalaryAmount, + MouthSalaryAmount = org?.result?.mouthSalaryAmount, CreatedAt = DateTime.Now, CreatedUserId = UserId ?? "", CreatedFullName = FullName ?? "", LastUpdatedAt = DateTime.Now, LastUpdateUserId = UserId ?? "", LastUpdateFullName = FullName ?? "", - IsOfficer = IsOfficer, - profileId = org?.result?.profileId ?? null, - IsOld = org == null || org.result == null ? false : true, - AmountOld = org?.result?.amount ?? null, - nodeOld = org?.result?.node ?? null, - nodeIdOld = org?.result?.nodeId ?? null, - posmasterIdOld = org?.result?.posmasterId ?? null, - rootOld = org?.result?.root ?? null, - rootIdOld = org?.result?.rootId ?? null, - rootShortNameOld = org?.result?.rootShortName ?? null, - child1Old = org?.result?.child1 ?? null, - child1IdOld = org?.result?.child1Id ?? null, - child1ShortNameOld = org?.result?.child1ShortName ?? null, - child2Old = org?.result?.child2 ?? null, - child2IdOld = org?.result?.child2Id ?? null, - child2ShortNameOld = org?.result?.child2ShortName ?? null, - child3Old = org?.result?.child3 ?? null, - child3IdOld = org?.result?.child3Id ?? null, - child3ShortNameOld = org?.result?.child3ShortName ?? null, - child4Old = org?.result?.child4 ?? null, - child4IdOld = org?.result?.child4Id ?? null, - child4ShortNameOld = org?.result?.child4ShortName ?? null, - orgRevisionIdOld = org?.result?.orgRevisionId ?? null, - posMasterNoOld = org?.result?.posMasterNo ?? null, - positionNameOld = org?.result?.position ?? null, - posTypeIdOld = org?.result?.posTypeId ?? null, - posTypeNameOld = org?.result?.posTypeName ?? null, - posLevelIdOld = org?.result?.posLevelId ?? null, - posLevelNameOld = org?.result?.posLevelName ?? null, + IsOfficer = isOfficer, + profileId = org?.result?.profileId, + IsOld = org?.result != null, + AmountOld = org?.result?.amount, + nodeOld = org?.result?.node, + nodeIdOld = org?.result?.nodeId, + posmasterIdOld = org?.result?.posmasterId, + rootOld = org?.result?.root, + rootIdOld = org?.result?.rootId, + rootShortNameOld = org?.result?.rootShortName, + child1Old = org?.result?.child1, + child1IdOld = org?.result?.child1Id, + child1ShortNameOld = org?.result?.child1ShortName, + child2Old = org?.result?.child2, + child2IdOld = org?.result?.child2Id, + child2ShortNameOld = org?.result?.child2ShortName, + child3Old = org?.result?.child3, + child3IdOld = org?.result?.child3Id, + child3ShortNameOld = org?.result?.child3ShortName, + child4Old = org?.result?.child4, + child4IdOld = org?.result?.child4Id, + child4ShortNameOld = org?.result?.child4ShortName, + orgRevisionIdOld = org?.result?.orgRevisionId, + posMasterNoOld = org?.result?.posMasterNo, + positionNameOld = org?.result?.position, + posTypeIdOld = org?.result?.posTypeId, + posTypeNameOld = org?.result?.posTypeName, + posLevelIdOld = org?.result?.posLevelId, + posLevelNameOld = org?.result?.posLevelName, }; - await _contextMetadata.PlacementProfiles.AddAsync(placementProfile); + placementProfiles.Add(placementProfile); var placementEducation = new PlacementEducation { PlacementProfile = placementProfile, - EducationLevelId = _contextOrg.educationLevel.FirstOrDefault(x => x.name == (candidate!.Educations!.FirstOrDefault()!.HighDegree ?? ""))?.Id ?? null, - EducationLevelName = _contextOrg.educationLevel.FirstOrDefault(x => x.name == (candidate!.Educations!.FirstOrDefault()!.HighDegree ?? ""))?.name ?? null, - Field = candidate?.Educations?.FirstOrDefault()?.Major ?? null, - Gpa = candidate?.Educations?.FirstOrDefault()?.GPA!.ToString() ?? null, - Institute = candidate?.Educations?.FirstOrDefault()?.University ?? null, - Degree = candidate?.Educations?.FirstOrDefault()?.Degree ?? null, - FinishDate = candidate?.Educations?.FirstOrDefault()?.BachelorDate ?? null, + EducationLevelId = educationLevelsCache.FirstOrDefault(x => x.name == firstEducation?.HighDegree)?.Id, + EducationLevelName = educationLevelsCache.FirstOrDefault(x => x.name == firstEducation?.HighDegree)?.name, + Field = firstEducation?.Major, + Gpa = firstEducation?.GPA.ToString(), + Institute = firstEducation?.University, + Degree = firstEducation?.Degree, + FinishDate = firstEducation?.BachelorDate, IsDate = true, CreatedAt = DateTime.Now, CreatedUserId = UserId ?? "", @@ -364,15 +385,15 @@ namespace BMA.EHR.Recruit.Service.Services CreatedFullName = FullName ?? "", LastUpdateFullName = FullName ?? "", }; - await _contextMetadata.PlacementEducations.AddAsync(placementEducation); + placementEducations.Add(placementEducation); var placementCertificate = new PlacementCertificate { PlacementProfile = placementProfile, - CertificateNo = candidate?.Certificates?.FirstOrDefault()?.CertificateNo ?? null, - IssueDate = candidate?.Certificates?.FirstOrDefault()?.IssueDate ?? null, - ExpireDate = candidate?.Certificates?.FirstOrDefault()?.ExpiredDate ?? null, - CertificateType = candidate?.Certificates?.FirstOrDefault()?.Description ?? null, + CertificateNo = firstCertificate?.CertificateNo, + IssueDate = firstCertificate?.IssueDate, + ExpireDate = firstCertificate?.ExpiredDate, + CertificateType = firstCertificate?.Description, CreatedAt = DateTime.Now, CreatedUserId = UserId ?? "", LastUpdatedAt = DateTime.Now, @@ -380,15 +401,34 @@ namespace BMA.EHR.Recruit.Service.Services CreatedFullName = FullName ?? "", LastUpdateFullName = FullName ?? "", }; - await _contextMetadata.PlacementCertificates.AddAsync(placementCertificate); - await _contextMetadata.SaveChangesAsync(); + placementCertificates.Add(placementCertificate); } + + // 🚀 Batch insert all records + await _contextMetadata.PlacementProfiles.AddRangeAsync(placementProfiles); + await _contextMetadata.PlacementEducations.AddRangeAsync(placementEducations); + await _contextMetadata.PlacementCertificates.AddRangeAsync(placementCertificates); + + // 🚀 Single SaveChanges at the end + await _contextMetadata.SaveChangesAsync(); + + httpClient.Dispose(); } catch { throw; } } + + private string BuildAddress(string? address, string? moo, string? soi, string? road) + { + var parts = new List(); + if (!string.IsNullOrWhiteSpace(address)) parts.Add(address); + if (!string.IsNullOrWhiteSpace(moo)) parts.Add($"หมู่ {moo}"); + if (!string.IsNullOrWhiteSpace(soi)) parts.Add($"ซอย {soi}"); + if (!string.IsNullOrWhiteSpace(road)) parts.Add($"ถนน {road}"); + return string.Join(" ", parts); + } public DateTime CheckDateTime(string Date, string Formate) { // ตอนนี้ทำไว้ให้รองรับแค่ "dd/MM/yyyy", "yyyy-MM-dd" From 2b0151fd674bbf354d1097f78fb16a0fc018232c Mon Sep 17 00:00:00 2001 From: kittapath <> Date: Sat, 11 Oct 2025 18:56:47 +0700 Subject: [PATCH 05/18] test --- Requests/Recruits/RecruitPosTypeRequest.cs | 16 ++++++ Services/RecruitService.cs | 67 +++++++++++++--------- 2 files changed, 57 insertions(+), 26 deletions(-) create mode 100644 Requests/Recruits/RecruitPosTypeRequest.cs diff --git a/Requests/Recruits/RecruitPosTypeRequest.cs b/Requests/Recruits/RecruitPosTypeRequest.cs new file mode 100644 index 0000000..5ec6a62 --- /dev/null +++ b/Requests/Recruits/RecruitPosTypeRequest.cs @@ -0,0 +1,16 @@ +namespace BMA.EHR.Recruit.Service.Requests.Recruits +{ + public class RecruitPosRequest + { + public RecruitPosTypeRequest result { get; set; } = new(); + } + public class RecruitPosTypeRequest + { + public string posTypeName { get; set; } + public List posLevels { get; set; } = new(); + } + public class RecruitPosLevelRequest + { + public string posLevelName { get; set; } + } +} diff --git a/Services/RecruitService.cs b/Services/RecruitService.cs index 8fc146b..b6b08bc 100644 --- a/Services/RecruitService.cs +++ b/Services/RecruitService.cs @@ -13,6 +13,7 @@ using System.Security.Claims; using System.Net.Http.Headers; using Newtonsoft.Json; using System.Globalization; +using BMA.EHR.Recruit.Service.Requests.Recruits; namespace BMA.EHR.Recruit.Service.Services { @@ -179,6 +180,14 @@ namespace BMA.EHR.Recruit.Service.Services { try { + // 🚀 Prepare HTTP client once + var httpClient1 = new HttpClient(); + httpClient1.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token?.Replace("Bearer ", "")); + httpClient1.DefaultRequestHeaders.Add("api_key", _configuration["API_KEY"]); + var apiUrl1 = $"{_configuration["API"]}/api/v1/org/pos/type"; + var response1 = await httpClient1.GetStringAsync(apiUrl1); + var posType = JsonConvert.DeserializeObject(response1); + var recruitImport = await _context.RecruitImports.AsQueryable() .FirstOrDefaultAsync(x => x.Id == examId); @@ -232,7 +241,9 @@ namespace BMA.EHR.Recruit.Service.Services .Where(x => x.ScoreImport == scoreImport && x.ExamStatus == "ผ่าน") .ToListAsync(); - var recruitScoresDict = recruitScores.ToDictionary(x => x.ExamId, x => x); + var recruitScoresDict = recruitScores + .Where(x => !string.IsNullOrWhiteSpace(x.ExamId)) + .ToDictionary(x => x.ExamId, x => x); // 🚀 Prepare HTTP client once var httpClient = new HttpClient(); @@ -242,20 +253,23 @@ namespace BMA.EHR.Recruit.Service.Services // 🚀 Batch HTTP requests var orgTasks = candidates.Select(async candidate => { + if (string.IsNullOrWhiteSpace(candidate.CitizenId)) + return new { CitizenId = candidate.CitizenId ?? "", org = (dynamic?)null }; + var apiUrl = $"{_configuration["API"]}/org/profile/citizenid/position/{candidate.CitizenId}"; try { var response = await httpClient.GetStringAsync(apiUrl); - return new { candidate.CitizenId, org = JsonConvert.DeserializeObject(response) }; + return new { CitizenId = candidate.CitizenId, org = JsonConvert.DeserializeObject(response) }; } catch { - return new { candidate.CitizenId, org = (dynamic?)null }; + return new { CitizenId = candidate.CitizenId ?? "", org = (dynamic?)null }; } }).ToList(); var orgResults = await Task.WhenAll(orgTasks); - var orgDict = orgResults.ToDictionary(x => x.CitizenId, x => x.org); + var orgDict = orgResults.ToDictionary(x => x.CitizenId ?? "", x => x.org); // 🚀 Prepare batch inserts var placementProfiles = new List(); @@ -264,10 +278,11 @@ namespace BMA.EHR.Recruit.Service.Services foreach (var candidate in candidates) { - if (!recruitScoresDict.TryGetValue(candidate.ExamId, out var recruitScore)) + if (string.IsNullOrWhiteSpace(candidate.ExamId) || + !recruitScoresDict.TryGetValue(candidate.ExamId, out var recruitScore)) continue; - var org = orgDict.GetValueOrDefault(candidate.CitizenId); + var org = orgDict.TryGetValue(candidate.CitizenId ?? "", out var orgValue) ? orgValue : null; var isOfficer = org?.result != null; // 🚀 Cache repeated calculations @@ -282,19 +297,19 @@ namespace BMA.EHR.Recruit.Service.Services var placementProfile = new PlacementProfile { Placement = placement, - PositionCandidate = candidate.PositionName, - PositionType = candidate.PositionType, - PositionLevel = candidate.PositionLevel, - Prefix = candidate.Prefix, - Firstname = candidate.FirstName, - Lastname = candidate.LastName, - Gender = candidate.Gendor, - Nationality = candidate.National, - Race = candidate.Race, - Religion = candidate.Religion, + PositionCandidate = candidate.PositionName ?? "", + PositionType = candidate.PositionType ?? "", + PositionLevel = candidate.PositionLevel ?? "", + Prefix = candidate.Prefix ?? "", + Firstname = candidate.FirstName ?? "", + Lastname = candidate.LastName ?? "", + Gender = candidate.Gendor ?? "", + Nationality = candidate.National ?? "", + Race = candidate.Race ?? "", + Religion = candidate.Religion ?? "", DateOfBirth = candidate.DateOfBirth, - Relationship = candidate.Marry, - CitizenId = candidate.CitizenId, + Relationship = candidate.Marry ?? "", + CitizenId = candidate.CitizenId ?? "", CitizenProvinceId = provincesCache.FirstOrDefault(x => x.name == candidate.CitizenCardIssuer)?.Id, CitizenDate = candidate.CitizenCardExpireDate, Telephone = firstAddress?.Telephone, @@ -314,17 +329,17 @@ namespace BMA.EHR.Recruit.Service.Services OccupationPositionType = "other", OccupationTelephone = firstOccupation?.Telephone, OccupationPosition = firstOccupation?.Position, - PointTotalA = Convert.ToDouble(recruitScore.FullA), - PointA = Convert.ToDouble(recruitScore.SumA), - PointTotalB = Convert.ToDouble(recruitScore.FullB), - PointB = Convert.ToDouble(recruitScore.SumB), - PointTotalC = Convert.ToDouble(recruitScore.FullC), - PointC = Convert.ToDouble(recruitScore.SumC), - ExamNumber = int.TryParse(recruitScore.Number, out int n) ? n : null, + PointTotalA = recruitScore.FullA, // non-nullable int + PointA = recruitScore.SumA, // non-nullable double + PointTotalB = recruitScore.FullB ?? 0, // nullable int? + PointB = recruitScore.SumB ?? 0, // nullable double? + PointTotalC = recruitScore.FullC, // non-nullable int + PointC = recruitScore.SumC, // non-nullable double + ExamNumber = !string.IsNullOrWhiteSpace(recruitScore.Number) && int.TryParse(recruitScore.Number, out int n) ? n : null, ExamRound = null, IsRelief = false, PlacementStatus = "UN-CONTAIN", - Pass = recruitScore.ExamStatus, + Pass = recruitScore.ExamStatus ?? "", RemarkHorizontal = "โดยมีเงื่อนไขว่าต้องปฏิบัติงานให้กรุงเทพมหานครเป็นระยะเวลาไม่น้อยกว่า ๕ ปี นับแต่วันที่ได้รับการบรรจุและแต่งตั้ง โดยห้ามโอนไปหน่วยงานหรือส่วนราชการอื่น เว้นเเต่ลาออกจากราชการ", Amount = org?.result?.amount, PositionSalaryAmount = org?.result?.positionSalaryAmount, From 52f3d6e78680dee4faa305b33115b41fcad06d54 Mon Sep 17 00:00:00 2001 From: kittapath <> Date: Sat, 11 Oct 2025 19:16:50 +0700 Subject: [PATCH 06/18] test --- Requests/Recruits/RecruitPosTypeRequest.cs | 12 +++++----- Services/RecruitService.cs | 26 +++++++++++++++++----- 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/Requests/Recruits/RecruitPosTypeRequest.cs b/Requests/Recruits/RecruitPosTypeRequest.cs index 5ec6a62..85e4b62 100644 --- a/Requests/Recruits/RecruitPosTypeRequest.cs +++ b/Requests/Recruits/RecruitPosTypeRequest.cs @@ -2,15 +2,15 @@ { public class RecruitPosRequest { - public RecruitPosTypeRequest result { get; set; } = new(); - } - public class RecruitPosTypeRequest - { - public string posTypeName { get; set; } - public List posLevels { get; set; } = new(); + public List result { get; set; } = new(); } public class RecruitPosLevelRequest { public string posLevelName { get; set; } + public RecruitPosLevelRequest posTypes { get; set; } = new(); + } + public class RecruitPosTypeRequest + { + public string posTypeName { get; set; } } } diff --git a/Services/RecruitService.cs b/Services/RecruitService.cs index b6b08bc..29d054a 100644 --- a/Services/RecruitService.cs +++ b/Services/RecruitService.cs @@ -184,9 +184,9 @@ namespace BMA.EHR.Recruit.Service.Services var httpClient1 = new HttpClient(); httpClient1.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token?.Replace("Bearer ", "")); httpClient1.DefaultRequestHeaders.Add("api_key", _configuration["API_KEY"]); - var apiUrl1 = $"{_configuration["API"]}/api/v1/org/pos/type"; + var apiUrl1 = $"{_configuration["API"]}/api/v1/org/pos/level"; var response1 = await httpClient1.GetStringAsync(apiUrl1); - var posType = JsonConvert.DeserializeObject(response1); + var posOptions = JsonConvert.DeserializeObject(response1); var recruitImport = await _context.RecruitImports.AsQueryable() .FirstOrDefaultAsync(x => x.Id == examId); @@ -294,12 +294,28 @@ namespace BMA.EHR.Recruit.Service.Services var registAddress = BuildAddress(firstAddress?.Address, firstAddress?.Moo, firstAddress?.Soi, firstAddress?.Road); var currentAddress = BuildAddress(firstAddress?.Address1, firstAddress?.Moo1, firstAddress?.Soi1, firstAddress?.Road1); + // หาค่า posLevelName หลังสุด + var posLevelObject = posOptions?.result?.FirstOrDefault(x => + !string.IsNullOrWhiteSpace(x.posLevelName) && + !string.IsNullOrWhiteSpace(candidate.PositionName) && + candidate.PositionName.Contains(x.posLevelName)); + + // เก็บเฉพาะค่า posLevelName + var posLevelName = posLevelObject?.posLevelName; + + // สร้างตัวแปร PositionName ที่ตัดค่า posLevelName ออก + var positionNameWithoutLevel = candidate.PositionName ?? ""; + if (!string.IsNullOrWhiteSpace(posLevelName)) + { + positionNameWithoutLevel = positionNameWithoutLevel.Replace(posLevelName, "").Trim(); + } + var placementProfile = new PlacementProfile { Placement = placement, - PositionCandidate = candidate.PositionName ?? "", - PositionType = candidate.PositionType ?? "", - PositionLevel = candidate.PositionLevel ?? "", + PositionCandidate = positionNameWithoutLevel, + PositionType = posLevelObject?.posTypes?.posLevelName ?? "", + PositionLevel = posLevelName ?? "", Prefix = candidate.Prefix ?? "", Firstname = candidate.FirstName ?? "", Lastname = candidate.LastName ?? "", From ff547470b2346d486e28927e8be51bbf00a64c82 Mon Sep 17 00:00:00 2001 From: kittapath <> Date: Sat, 11 Oct 2025 19:49:30 +0700 Subject: [PATCH 07/18] test --- Services/RecruitService.cs | 78 +++++++++++++++++++------------------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/Services/RecruitService.cs b/Services/RecruitService.cs index 29d054a..af83564 100644 --- a/Services/RecruitService.cs +++ b/Services/RecruitService.cs @@ -313,7 +313,7 @@ namespace BMA.EHR.Recruit.Service.Services var placementProfile = new PlacementProfile { Placement = placement, - PositionCandidate = positionNameWithoutLevel, + PositionCandidate = positionNameWithoutLevel ?? "", PositionType = posLevelObject?.posTypes?.posLevelName ?? "", PositionLevel = posLevelName ?? "", Prefix = candidate.Prefix ?? "", @@ -328,13 +328,13 @@ namespace BMA.EHR.Recruit.Service.Services CitizenId = candidate.CitizenId ?? "", CitizenProvinceId = provincesCache.FirstOrDefault(x => x.name == candidate.CitizenCardIssuer)?.Id, CitizenDate = candidate.CitizenCardExpireDate, - Telephone = firstAddress?.Telephone, - MobilePhone = firstAddress?.Mobile, - RegistAddress = registAddress, + Telephone = firstAddress?.Telephone ?? "", + MobilePhone = firstAddress?.Mobile ?? "", + RegistAddress = registAddress ?? "", RegistProvinceId = provincesCache.FirstOrDefault(x => x.name == firstAddress?.Province)?.Id, RegistDistrictId = districtsCache.FirstOrDefault(x => x.name == firstAddress?.Amphur)?.Id, RegistSubDistrictId = subDistrictsCache.FirstOrDefault(x => x.name == firstAddress?.District)?.Id, - RegistZipCode = firstAddress?.ZipCode, + RegistZipCode = firstAddress?.ZipCode ?? "", RegistSame = false, CurrentAddress = currentAddress, CurrentProvinceId = provincesCache.FirstOrDefault(x => x.name == firstAddress?.Province1)?.Id, @@ -343,8 +343,8 @@ namespace BMA.EHR.Recruit.Service.Services CurrentZipCode = firstAddress?.ZipCode1, Marry = candidate.Marry?.Contains("สมรส") ?? false, OccupationPositionType = "other", - OccupationTelephone = firstOccupation?.Telephone, - OccupationPosition = firstOccupation?.Position, + OccupationTelephone = firstOccupation?.Telephone ?? "", + OccupationPosition = firstOccupation?.Position ?? "", PointTotalA = recruitScore.FullA, // non-nullable int PointA = recruitScore.SumA, // non-nullable double PointTotalB = recruitScore.FullB ?? 0, // nullable int? @@ -367,34 +367,34 @@ namespace BMA.EHR.Recruit.Service.Services LastUpdateUserId = UserId ?? "", LastUpdateFullName = FullName ?? "", IsOfficer = isOfficer, - profileId = org?.result?.profileId, + profileId = org?.result?.profileId ?? "", IsOld = org?.result != null, AmountOld = org?.result?.amount, - nodeOld = org?.result?.node, - nodeIdOld = org?.result?.nodeId, - posmasterIdOld = org?.result?.posmasterId, - rootOld = org?.result?.root, - rootIdOld = org?.result?.rootId, - rootShortNameOld = org?.result?.rootShortName, - child1Old = org?.result?.child1, - child1IdOld = org?.result?.child1Id, - child1ShortNameOld = org?.result?.child1ShortName, - child2Old = org?.result?.child2, - child2IdOld = org?.result?.child2Id, - child2ShortNameOld = org?.result?.child2ShortName, - child3Old = org?.result?.child3, - child3IdOld = org?.result?.child3Id, - child3ShortNameOld = org?.result?.child3ShortName, - child4Old = org?.result?.child4, - child4IdOld = org?.result?.child4Id, - child4ShortNameOld = org?.result?.child4ShortName, - orgRevisionIdOld = org?.result?.orgRevisionId, - posMasterNoOld = org?.result?.posMasterNo, - positionNameOld = org?.result?.position, - posTypeIdOld = org?.result?.posTypeId, - posTypeNameOld = org?.result?.posTypeName, - posLevelIdOld = org?.result?.posLevelId, - posLevelNameOld = org?.result?.posLevelName, + nodeOld = org?.result?.node ?? "", + nodeIdOld = org?.result?.nodeId ?? "", + posmasterIdOld = org?.result?.posmasterId ?? "", + rootOld = org?.result?.root ?? "", + rootIdOld = org?.result?.rootId ?? "", + rootShortNameOld = org?.result?.rootShortName ?? "", + child1Old = org?.result?.child1 ?? "", + child1IdOld = org?.result?.child1Id ?? "", + child1ShortNameOld = org?.result?.child1ShortName ?? "", + child2Old = org?.result?.child2 ?? "", + child2IdOld = org?.result?.child2Id ?? "", + child2ShortNameOld = org?.result?.child2ShortName ?? "", + child3Old = org?.result?.child3 ?? "", + child3IdOld = org?.result?.child3Id ?? "", + child3ShortNameOld = org?.result?.child3ShortName ?? "", + child4Old = org?.result?.child4 ?? "", + child4IdOld = org?.result?.child4Id ?? "", + child4ShortNameOld = org?.result?.child4ShortName ?? "", + orgRevisionIdOld = org?.result?.orgRevisionId ?? "", + posMasterNoOld = org?.result?.posMasterNo ?? "", + positionNameOld = org?.result?.position ?? "", + posTypeIdOld = org?.result?.posTypeId ?? "", + posTypeNameOld = org?.result?.posTypeName ?? "", + posLevelIdOld = org?.result?.posLevelId ?? "", + posLevelNameOld = org?.result?.posLevelName ?? "", }; placementProfiles.Add(placementProfile); @@ -403,10 +403,10 @@ namespace BMA.EHR.Recruit.Service.Services PlacementProfile = placementProfile, EducationLevelId = educationLevelsCache.FirstOrDefault(x => x.name == firstEducation?.HighDegree)?.Id, EducationLevelName = educationLevelsCache.FirstOrDefault(x => x.name == firstEducation?.HighDegree)?.name, - Field = firstEducation?.Major, - Gpa = firstEducation?.GPA.ToString(), - Institute = firstEducation?.University, - Degree = firstEducation?.Degree, + Field = firstEducation?.Major ?? "", + Gpa = firstEducation == null || firstEducation?.GPA == null ? "" : firstEducation.GPA.ToString(), + Institute = firstEducation?.University ?? "", + Degree = firstEducation?.Degree ?? "", FinishDate = firstEducation?.BachelorDate, IsDate = true, CreatedAt = DateTime.Now, @@ -421,10 +421,10 @@ namespace BMA.EHR.Recruit.Service.Services var placementCertificate = new PlacementCertificate { PlacementProfile = placementProfile, - CertificateNo = firstCertificate?.CertificateNo, + CertificateNo = firstCertificate?.CertificateNo ?? "", IssueDate = firstCertificate?.IssueDate, ExpireDate = firstCertificate?.ExpiredDate, - CertificateType = firstCertificate?.Description, + CertificateType = firstCertificate?.Description ?? "", CreatedAt = DateTime.Now, CreatedUserId = UserId ?? "", LastUpdatedAt = DateTime.Now, From 8766eb3b1f6fdef6892f0efec0756dc3e56181c7 Mon Sep 17 00:00:00 2001 From: kittapath-Jool Date: Sun, 12 Oct 2025 13:42:09 +0700 Subject: [PATCH 08/18] test --- .github/workflows/release.yaml | 194 ++++++++++++++++----------------- Services/RecruitService.cs | 17 +++ 2 files changed, 114 insertions(+), 97 deletions(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 72adff2..1de509f 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -1,106 +1,106 @@ name: release run-name: release ${{ github.actor }} on: - push: - tags: - - "version-[0-9]+.[0-9]+.[0-9]+" - workflow_dispatch: + push: + tags: + - "version-[0-9]+.[0-9]+.[0-9]+" + workflow_dispatch: env: - REGISTRY: docker.frappet.com - IMAGE_NAME: ehr/bma-ehr-recruit-service - DEPLOY_HOST: frappet.com - COMPOSE_PATH: /home/frappet/docker/bma/bma-ehr-recruit + REGISTRY: docker.frappet.com + IMAGE_NAME: ehr/bma-ehr-recruit-service + DEPLOY_HOST: frappet.com + COMPOSE_PATH: /home/frappet/docker/bma/bma-ehr-recruit jobs: - # act workflow_dispatch -W .github/workflows/release.yaml --input IMAGE_VER=latest -s DOCKER_USER=admin -s DOCKER_PASS=FPTadmin2357 -s SSH_PASSWORD=FPTadmin2357 - release: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - # skip Set up QEMU because it fail on act and container - # Gen Version try to get version from tag or inut - - name: Set output tags - id: vars - run: echo "tag=${GITHUB_REF#refs/*/}" >> $GITHUB_OUTPUT - - name: Gen Version - id: gen_ver - run: | - if [[ $GITHUB_REF == 'refs/tags/'* ]]; then - IMAGE_VER=${{ steps.vars.outputs.tag }} - else - IMAGE_VER=${{ github.event.inputs.IMAGE_VER }} - fi - if [[ $IMAGE_VER == '' ]]; then - IMAGE_VER='test-vBeta' - fi - echo '::set-output name=image_ver::'$IMAGE_VER - - name: Check Version - run: | - echo $GITHUB_REF - echo ${{ steps.gen_ver.outputs.image_ver }} + # act workflow_dispatch -W .github/workflows/release.yaml --input IMAGE_VER=latest -s DOCKER_USER=admin -s DOCKER_PASS=FPTadmin2357 -s SSH_PASSWORD=FPTadmin2357 + release: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + # skip Set up QEMU because it fail on act and container + # Gen Version try to get version from tag or inut + - name: Set output tags + id: vars + run: echo "tag=${GITHUB_REF#refs/*/}" >> $GITHUB_OUTPUT + - name: Gen Version + id: gen_ver + run: | + if [[ $GITHUB_REF == 'refs/tags/'* ]]; then + IMAGE_VER=${{ steps.vars.outputs.tag }} + else + IMAGE_VER=${{ github.event.inputs.IMAGE_VER }} + fi + if [[ $IMAGE_VER == '' ]]; then + IMAGE_VER='test-vBeta' + fi + echo '::set-output name=image_ver::'$IMAGE_VER + - name: Check Version + run: | + echo $GITHUB_REF + echo ${{ steps.gen_ver.outputs.image_ver }} - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 - - name: Login in to registry - uses: docker/login-action@v2 - with: - registry: ${{env.REGISTRY}} - username: ${{secrets.DOCKER_USER}} - password: ${{secrets.DOCKER_PASS}} - - name: Build and push docker image - uses: docker/build-push-action@v3 - with: - context: . - platforms: linux/amd64 - push: true - tags: ${{env.REGISTRY}}/${{env.IMAGE_NAME}}:${{ steps.gen_ver.outputs.image_ver }},${{env.REGISTRY}}/${{env.IMAGE_NAME}}:latest + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + - name: Login in to registry + uses: docker/login-action@v2 + with: + registry: ${{env.REGISTRY}} + username: ${{secrets.DOCKER_USER}} + password: ${{secrets.DOCKER_PASS}} + - name: Build and push docker image + uses: docker/build-push-action@v3 + with: + context: . + platforms: linux/amd64 + push: true + tags: ${{env.REGISTRY}}/${{env.IMAGE_NAME}}:${{ steps.gen_ver.outputs.image_ver }},${{env.REGISTRY}}/${{env.IMAGE_NAME}}:latest - - name: Reload docker compose - uses: appleboy/ssh-action@v0.1.8 - with: - host: ${{env.DEPLOY_HOST}} - username: frappet - password: ${{ secrets.SSH_PASSWORD }} - port: 10102 - script: | - cd "${{env.COMPOSE_PATH}}" - docker compose pull - docker compose up -d - echo "${{ steps.gen_ver.outputs.image_ver }}"> success - - name: Notify Discord Success - if: success() - run: | - curl -H "Content-Type: application/json" \ - -X POST \ - -d '{ - "embeds": [{ - "title": "✅ Deployment Success!", - "description": "**Details:**\n- Image: `${{env.IMAGE_NAME}}`\n- Version: `${{ steps.gen_ver.outputs.image_ver }}`\n- Deployed by: `${{github.actor}}`", - "color": 3066993, - "footer": { - "text": "Release Notification", - "icon_url": "https://example.com/success-icon.png" - }, - "timestamp": "'$(date -u +%Y-%m-%dT%H:%M:%SZ)'" - }] - }' \ - ${{ secrets.DISCORD_WEBHOOK }} + - name: Reload docker compose + uses: appleboy/ssh-action@v0.1.8 + with: + host: ${{env.DEPLOY_HOST}} + username: frappet + password: ${{ secrets.SSH_PASSWORD }} + port: 10102 + script: | + cd "${{env.COMPOSE_PATH}}" + docker compose pull + docker compose up -d + echo "${{ steps.gen_ver.outputs.image_ver }}"> success + # - name: Notify Discord Success + # if: success() + # run: | + # curl -H "Content-Type: application/json" \ + # -X POST \ + # -d '{ + # "embeds": [{ + # "title": "✅ Deployment Success!", + # "description": "**Details:**\n- Image: `${{env.IMAGE_NAME}}`\n- Version: `${{ steps.gen_ver.outputs.image_ver }}`\n- Deployed by: `${{github.actor}}`", + # "color": 3066993, + # "footer": { + # "text": "Release Notification", + # "icon_url": "https://example.com/success-icon.png" + # }, + # "timestamp": "'$(date -u +%Y-%m-%dT%H:%M:%SZ)'" + # }] + # }' \ + # ${{ secrets.DISCORD_WEBHOOK }} - - name: Notify Discord Failure - if: failure() - run: | - curl -H "Content-Type: application/json" \ - -X POST \ - -d '{ - "embeds": [{ - "title": "❌ Deployment Failed!", - "description": "**Details:**\n- Image: `${{env.IMAGE_NAME}}`\n- Version: `${{ steps.gen_ver.outputs.image_ver }}`\n- Attempted by: `${{github.actor}}`", - "color": 15158332, - "footer": { - "text": "Release Notification", - "icon_url": "https://example.com/failure-icon.png" - }, - "timestamp": "'$(date -u +%Y-%m-%dT%H:%M:%SZ)'" - }] - }' \ - ${{ secrets.DISCORD_WEBHOOK }} + # - name: Notify Discord Failure + # if: failure() + # run: | + # curl -H "Content-Type: application/json" \ + # -X POST \ + # -d '{ + # "embeds": [{ + # "title": "❌ Deployment Failed!", + # "description": "**Details:**\n- Image: `${{env.IMAGE_NAME}}`\n- Version: `${{ steps.gen_ver.outputs.image_ver }}`\n- Attempted by: `${{github.actor}}`", + # "color": 15158332, + # "footer": { + # "text": "Release Notification", + # "icon_url": "https://example.com/failure-icon.png" + # }, + # "timestamp": "'$(date -u +%Y-%m-%dT%H:%M:%SZ)'" + # }] + # }' \ + # ${{ secrets.DISCORD_WEBHOOK }} diff --git a/Services/RecruitService.cs b/Services/RecruitService.cs index af83564..a304cfd 100644 --- a/Services/RecruitService.cs +++ b/Services/RecruitService.cs @@ -183,19 +183,26 @@ namespace BMA.EHR.Recruit.Service.Services // 🚀 Prepare HTTP client once var httpClient1 = new HttpClient(); httpClient1.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token?.Replace("Bearer ", "")); + Console.WriteLine("118"); httpClient1.DefaultRequestHeaders.Add("api_key", _configuration["API_KEY"]); + Console.WriteLine("117"); var apiUrl1 = $"{_configuration["API"]}/api/v1/org/pos/level"; + Console.WriteLine("116"); var response1 = await httpClient1.GetStringAsync(apiUrl1); + Console.WriteLine("115"); var posOptions = JsonConvert.DeserializeObject(response1); + Console.WriteLine("114"); var recruitImport = await _context.RecruitImports.AsQueryable() .FirstOrDefaultAsync(x => x.Id == examId); + Console.WriteLine("113"); if (recruitImport == null) throw new Exception(GlobalMessages.DataNotFound); var _placement = await _contextMetadata.Placements.AsQueryable() .FirstOrDefaultAsync(x => x.PlacementType.Name == "สอบแข่งขัน" && x.RefId == recruitImport.Id); + Console.WriteLine("112"); // if (_placement != null) // throw new Exception("รอบการสอบนี้ได้ทำการบรรจุไปแล้ว"); @@ -205,6 +212,7 @@ namespace BMA.EHR.Recruit.Service.Services var districtsCache = await _contextOrg.district.ToListAsync(); var subDistrictsCache = await _contextOrg.subDistrict.ToListAsync(); var educationLevelsCache = await _contextOrg.educationLevel.ToListAsync(); + Console.WriteLine("101"); var placement = new Placement { @@ -224,6 +232,7 @@ namespace BMA.EHR.Recruit.Service.Services LastUpdateFullName = FullName ?? "", }; await _contextMetadata.Placements.AddAsync(placement); + Console.WriteLine("191"); // 🚀 Load all related data with single queries var candidates = await _context.Recruits.AsQueryable() @@ -250,6 +259,7 @@ namespace BMA.EHR.Recruit.Service.Services httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token?.Replace("Bearer ", "")); httpClient.DefaultRequestHeaders.Add("api_key", _configuration["API_KEY"]); + Console.WriteLine("181"); // 🚀 Batch HTTP requests var orgTasks = candidates.Select(async candidate => { @@ -267,6 +277,7 @@ namespace BMA.EHR.Recruit.Service.Services return new { CitizenId = candidate.CitizenId ?? "", org = (dynamic?)null }; } }).ToList(); + Console.WriteLine("171"); var orgResults = await Task.WhenAll(orgTasks); var orgDict = orgResults.ToDictionary(x => x.CitizenId ?? "", x => x.org); @@ -275,6 +286,7 @@ namespace BMA.EHR.Recruit.Service.Services var placementProfiles = new List(); var placementEducations = new List(); var placementCertificates = new List(); + Console.WriteLine("161"); foreach (var candidate in candidates) { @@ -433,12 +445,17 @@ namespace BMA.EHR.Recruit.Service.Services LastUpdateFullName = FullName ?? "", }; placementCertificates.Add(placementCertificate); + Console.WriteLine("511"); } + Console.WriteLine("141"); // 🚀 Batch insert all records await _contextMetadata.PlacementProfiles.AddRangeAsync(placementProfiles); + Console.WriteLine("131"); await _contextMetadata.PlacementEducations.AddRangeAsync(placementEducations); + Console.WriteLine("121"); await _contextMetadata.PlacementCertificates.AddRangeAsync(placementCertificates); + Console.WriteLine("111"); // 🚀 Single SaveChanges at the end await _contextMetadata.SaveChangesAsync(); From 62396584b45717217f48e1ff63744814ae1f8926 Mon Sep 17 00:00:00 2001 From: kittapath-Jool Date: Sun, 12 Oct 2025 13:52:07 +0700 Subject: [PATCH 09/18] test --- Controllers/RecruitController.cs | 2 +- Requests/Recruits/RecruitPosTypeRequest.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Controllers/RecruitController.cs b/Controllers/RecruitController.cs index 5ea2895..8f0363e 100644 --- a/Controllers/RecruitController.cs +++ b/Controllers/RecruitController.cs @@ -2611,7 +2611,7 @@ namespace BMA.EHR.Recruit.Service.Controllers /// เมื่อโอนคนแข่งขันไปบรรจุสำเร็จ /// ไม่ได้ Login เข้าระบบ /// เมื่อเกิดข้อผิดพลาดในการทำงาน - [HttpPost("placement/{examId:length(36)}")] + [HttpPut("placement/{examId:length(36)}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] diff --git a/Requests/Recruits/RecruitPosTypeRequest.cs b/Requests/Recruits/RecruitPosTypeRequest.cs index 85e4b62..90d174e 100644 --- a/Requests/Recruits/RecruitPosTypeRequest.cs +++ b/Requests/Recruits/RecruitPosTypeRequest.cs @@ -7,7 +7,7 @@ public class RecruitPosLevelRequest { public string posLevelName { get; set; } - public RecruitPosLevelRequest posTypes { get; set; } = new(); + public RecruitPosTypeRequest posTypes { get; set; } = new(); } public class RecruitPosTypeRequest { From e2328bf903e6cb5fef5d174454c93ea35fc8042c Mon Sep 17 00:00:00 2001 From: kittapath-Jool Date: Sun, 12 Oct 2025 13:55:35 +0700 Subject: [PATCH 10/18] test --- Services/RecruitService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Services/RecruitService.cs b/Services/RecruitService.cs index a304cfd..883e850 100644 --- a/Services/RecruitService.cs +++ b/Services/RecruitService.cs @@ -326,7 +326,7 @@ namespace BMA.EHR.Recruit.Service.Services { Placement = placement, PositionCandidate = positionNameWithoutLevel ?? "", - PositionType = posLevelObject?.posTypes?.posLevelName ?? "", + PositionType = posLevelObject?.posTypes?.posTypeName ?? "", PositionLevel = posLevelName ?? "", Prefix = candidate.Prefix ?? "", Firstname = candidate.FirstName ?? "", From a4968ff66f803f3f638f0826c69e656e56a99f1c Mon Sep 17 00:00:00 2001 From: kittapath-Jool Date: Sun, 12 Oct 2025 13:59:35 +0700 Subject: [PATCH 11/18] test --- Services/RecruitService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Services/RecruitService.cs b/Services/RecruitService.cs index 883e850..6f370b0 100644 --- a/Services/RecruitService.cs +++ b/Services/RecruitService.cs @@ -186,7 +186,7 @@ namespace BMA.EHR.Recruit.Service.Services Console.WriteLine("118"); httpClient1.DefaultRequestHeaders.Add("api_key", _configuration["API_KEY"]); Console.WriteLine("117"); - var apiUrl1 = $"{_configuration["API"]}/api/v1/org/pos/level"; + var apiUrl1 = $"{_configuration["API"]}/org/pos/level"; Console.WriteLine("116"); var response1 = await httpClient1.GetStringAsync(apiUrl1); Console.WriteLine("115"); From 183f7e214a718c57893bbf83cd5660c3ee829ce8 Mon Sep 17 00:00:00 2001 From: kittapath-Jool Date: Sun, 12 Oct 2025 14:04:18 +0700 Subject: [PATCH 12/18] test --- Services/RecruitService.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Services/RecruitService.cs b/Services/RecruitService.cs index 6f370b0..8851125 100644 --- a/Services/RecruitService.cs +++ b/Services/RecruitService.cs @@ -208,11 +208,15 @@ namespace BMA.EHR.Recruit.Service.Services // 🚀 Pre-load all lookup data once var placementTypesCache = await _contextMetadata.PlacementTypes.ToListAsync(); + Console.WriteLine("1011"); var provincesCache = await _contextOrg.province.ToListAsync(); + Console.WriteLine("1012"); var districtsCache = await _contextOrg.district.ToListAsync(); + Console.WriteLine("1013"); var subDistrictsCache = await _contextOrg.subDistrict.ToListAsync(); + Console.WriteLine("1014"); var educationLevelsCache = await _contextOrg.educationLevel.ToListAsync(); - Console.WriteLine("101"); + Console.WriteLine("1015"); var placement = new Placement { From d8520d650f0671562cd32b29c21542e518a75248 Mon Sep 17 00:00:00 2001 From: kittapath-Jool Date: Sun, 12 Oct 2025 14:12:35 +0700 Subject: [PATCH 13/18] test --- Models/MetaData/EducationLevel.cs | 4 ++-- Models/MetaData/SubDistrict.cs | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Models/MetaData/EducationLevel.cs b/Models/MetaData/EducationLevel.cs index 840b8d3..380f4c4 100644 --- a/Models/MetaData/EducationLevel.cs +++ b/Models/MetaData/EducationLevel.cs @@ -7,8 +7,8 @@ namespace BMA.EHR.MetaData.Service.Models { public class EducationLevel : EntityBase { - [Required, MaxLength(100), Column(Order = 1), Comment("ระดับการศึกษา")] - public string name { get; set; } = string.Empty; + [MaxLength(255), Column(Order = 1), Comment("ระดับการศึกษา")] + public string? name { get; set; } = null; // [Column(Order = 2), Comment("สถานะการใช้งาน")] // public bool IsActive { get; set; } = true; diff --git a/Models/MetaData/SubDistrict.cs b/Models/MetaData/SubDistrict.cs index 66ac804..dff6bed 100644 --- a/Models/MetaData/SubDistrict.cs +++ b/Models/MetaData/SubDistrict.cs @@ -8,11 +8,11 @@ namespace BMA.EHR.MetaData.Service.Models { public class SubDistrict : EntityBase { - [Required, MaxLength(150), Column(Order = 1), Comment("เขต/อำเภอ")] - public string name { get; set; } = string.Empty; + [MaxLength(255), Column(Order = 1), Comment("แขวง")] + public string? name { get; set; } = null; - [Required, MaxLength(10), Column(Order = 2), Comment("รหัสไปรษณีย์")] - public string zipCode { get; set; } = string.Empty; + [MaxLength(10), Column(Order = 2), Comment("รหัสไปรษณีย์")] + public string? zipCode { get; set; } = null; // [Column(Order = 3), Comment("สถานะการใช้งาน")] // public bool IsActive { get; set; } = true; From 94b7ca4d8e24af661ee49b7a79837d54debe3251 Mon Sep 17 00:00:00 2001 From: kittapath Date: Sun, 12 Oct 2025 14:35:26 +0700 Subject: [PATCH 14/18] test --- Services/RecruitService.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/Services/RecruitService.cs b/Services/RecruitService.cs index 8851125..6f4d9ba 100644 --- a/Services/RecruitService.cs +++ b/Services/RecruitService.cs @@ -294,37 +294,50 @@ namespace BMA.EHR.Recruit.Service.Services foreach (var candidate in candidates) { + Console.WriteLine("5111"); if (string.IsNullOrWhiteSpace(candidate.ExamId) || !recruitScoresDict.TryGetValue(candidate.ExamId, out var recruitScore)) continue; + Console.WriteLine("5112"); var org = orgDict.TryGetValue(candidate.CitizenId ?? "", out var orgValue) ? orgValue : null; + Console.WriteLine("5113"); var isOfficer = org?.result != null; + Console.WriteLine("5114"); // 🚀 Cache repeated calculations var firstAddress = candidate.Addresses?.FirstOrDefault(); + Console.WriteLine("5115"); var firstEducation = candidate.Educations?.FirstOrDefault(); + Console.WriteLine("5116"); var firstCertificate = candidate.Certificates?.FirstOrDefault(); + Console.WriteLine("5117"); var firstOccupation = candidate.Occupations?.FirstOrDefault(); + Console.WriteLine("5118"); var registAddress = BuildAddress(firstAddress?.Address, firstAddress?.Moo, firstAddress?.Soi, firstAddress?.Road); + Console.WriteLine("5119"); var currentAddress = BuildAddress(firstAddress?.Address1, firstAddress?.Moo1, firstAddress?.Soi1, firstAddress?.Road1); + Console.WriteLine("5120"); // หาค่า posLevelName หลังสุด var posLevelObject = posOptions?.result?.FirstOrDefault(x => !string.IsNullOrWhiteSpace(x.posLevelName) && !string.IsNullOrWhiteSpace(candidate.PositionName) && candidate.PositionName.Contains(x.posLevelName)); + Console.WriteLine("5121"); // เก็บเฉพาะค่า posLevelName var posLevelName = posLevelObject?.posLevelName; // สร้างตัวแปร PositionName ที่ตัดค่า posLevelName ออก var positionNameWithoutLevel = candidate.PositionName ?? ""; + Console.WriteLine("5122"); if (!string.IsNullOrWhiteSpace(posLevelName)) { positionNameWithoutLevel = positionNameWithoutLevel.Replace(posLevelName, "").Trim(); } + Console.WriteLine("5123"); var placementProfile = new PlacementProfile { @@ -412,7 +425,9 @@ namespace BMA.EHR.Recruit.Service.Services posLevelIdOld = org?.result?.posLevelId ?? "", posLevelNameOld = org?.result?.posLevelName ?? "", }; + Console.WriteLine("5124"); placementProfiles.Add(placementProfile); + Console.WriteLine("5125"); var placementEducation = new PlacementEducation { @@ -432,7 +447,9 @@ namespace BMA.EHR.Recruit.Service.Services CreatedFullName = FullName ?? "", LastUpdateFullName = FullName ?? "", }; + Console.WriteLine("5125"); placementEducations.Add(placementEducation); + Console.WriteLine("5126"); var placementCertificate = new PlacementCertificate { From 655f0319524530fe90b70b5013fc5fed5d6c05f5 Mon Sep 17 00:00:00 2001 From: kittapath Date: Sun, 12 Oct 2025 14:43:42 +0700 Subject: [PATCH 15/18] test --- Services/RecruitService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Services/RecruitService.cs b/Services/RecruitService.cs index 6f4d9ba..39c9d48 100644 --- a/Services/RecruitService.cs +++ b/Services/RecruitService.cs @@ -418,7 +418,7 @@ namespace BMA.EHR.Recruit.Service.Services child4IdOld = org?.result?.child4Id ?? "", child4ShortNameOld = org?.result?.child4ShortName ?? "", orgRevisionIdOld = org?.result?.orgRevisionId ?? "", - posMasterNoOld = org?.result?.posMasterNo ?? "", + posMasterNoOld = org?.result?.posMasterNo, positionNameOld = org?.result?.position ?? "", posTypeIdOld = org?.result?.posTypeId ?? "", posTypeNameOld = org?.result?.posTypeName ?? "", From 13ad60d2e3dc5ee054d96258fa841b351bf3551e Mon Sep 17 00:00:00 2001 From: kittapath Date: Sun, 12 Oct 2025 14:52:54 +0700 Subject: [PATCH 16/18] =?UTF-8?q?=E0=B9=81=E0=B8=81=E0=B9=89=E0=B8=9A?= =?UTF-8?q?=E0=B8=B1=E0=B8=84=20=E0=B8=AA=E0=B8=A3=E0=B8=A3=E0=B8=AB?= =?UTF-8?q?=E0=B8=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Controllers/RecruitController.cs | 2 +- Services/RecruitService.cs | 38 -------------------------------- 2 files changed, 1 insertion(+), 39 deletions(-) diff --git a/Controllers/RecruitController.cs b/Controllers/RecruitController.cs index 8f0363e..5ea2895 100644 --- a/Controllers/RecruitController.cs +++ b/Controllers/RecruitController.cs @@ -2611,7 +2611,7 @@ namespace BMA.EHR.Recruit.Service.Controllers /// เมื่อโอนคนแข่งขันไปบรรจุสำเร็จ /// ไม่ได้ Login เข้าระบบ /// เมื่อเกิดข้อผิดพลาดในการทำงาน - [HttpPut("placement/{examId:length(36)}")] + [HttpPost("placement/{examId:length(36)}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] diff --git a/Services/RecruitService.cs b/Services/RecruitService.cs index 39c9d48..284cfb7 100644 --- a/Services/RecruitService.cs +++ b/Services/RecruitService.cs @@ -183,40 +183,28 @@ namespace BMA.EHR.Recruit.Service.Services // 🚀 Prepare HTTP client once var httpClient1 = new HttpClient(); httpClient1.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token?.Replace("Bearer ", "")); - Console.WriteLine("118"); httpClient1.DefaultRequestHeaders.Add("api_key", _configuration["API_KEY"]); - Console.WriteLine("117"); var apiUrl1 = $"{_configuration["API"]}/org/pos/level"; - Console.WriteLine("116"); var response1 = await httpClient1.GetStringAsync(apiUrl1); - Console.WriteLine("115"); var posOptions = JsonConvert.DeserializeObject(response1); - Console.WriteLine("114"); var recruitImport = await _context.RecruitImports.AsQueryable() .FirstOrDefaultAsync(x => x.Id == examId); - Console.WriteLine("113"); if (recruitImport == null) throw new Exception(GlobalMessages.DataNotFound); var _placement = await _contextMetadata.Placements.AsQueryable() .FirstOrDefaultAsync(x => x.PlacementType.Name == "สอบแข่งขัน" && x.RefId == recruitImport.Id); - Console.WriteLine("112"); // if (_placement != null) // throw new Exception("รอบการสอบนี้ได้ทำการบรรจุไปแล้ว"); // 🚀 Pre-load all lookup data once var placementTypesCache = await _contextMetadata.PlacementTypes.ToListAsync(); - Console.WriteLine("1011"); var provincesCache = await _contextOrg.province.ToListAsync(); - Console.WriteLine("1012"); var districtsCache = await _contextOrg.district.ToListAsync(); - Console.WriteLine("1013"); var subDistrictsCache = await _contextOrg.subDistrict.ToListAsync(); - Console.WriteLine("1014"); var educationLevelsCache = await _contextOrg.educationLevel.ToListAsync(); - Console.WriteLine("1015"); var placement = new Placement { @@ -236,7 +224,6 @@ namespace BMA.EHR.Recruit.Service.Services LastUpdateFullName = FullName ?? "", }; await _contextMetadata.Placements.AddAsync(placement); - Console.WriteLine("191"); // 🚀 Load all related data with single queries var candidates = await _context.Recruits.AsQueryable() @@ -263,7 +250,6 @@ namespace BMA.EHR.Recruit.Service.Services httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token?.Replace("Bearer ", "")); httpClient.DefaultRequestHeaders.Add("api_key", _configuration["API_KEY"]); - Console.WriteLine("181"); // 🚀 Batch HTTP requests var orgTasks = candidates.Select(async candidate => { @@ -281,7 +267,6 @@ namespace BMA.EHR.Recruit.Service.Services return new { CitizenId = candidate.CitizenId ?? "", org = (dynamic?)null }; } }).ToList(); - Console.WriteLine("171"); var orgResults = await Task.WhenAll(orgTasks); var orgDict = orgResults.ToDictionary(x => x.CitizenId ?? "", x => x.org); @@ -290,54 +275,40 @@ namespace BMA.EHR.Recruit.Service.Services var placementProfiles = new List(); var placementEducations = new List(); var placementCertificates = new List(); - Console.WriteLine("161"); foreach (var candidate in candidates) { - Console.WriteLine("5111"); if (string.IsNullOrWhiteSpace(candidate.ExamId) || !recruitScoresDict.TryGetValue(candidate.ExamId, out var recruitScore)) continue; - Console.WriteLine("5112"); var org = orgDict.TryGetValue(candidate.CitizenId ?? "", out var orgValue) ? orgValue : null; - Console.WriteLine("5113"); var isOfficer = org?.result != null; - Console.WriteLine("5114"); // 🚀 Cache repeated calculations var firstAddress = candidate.Addresses?.FirstOrDefault(); - Console.WriteLine("5115"); var firstEducation = candidate.Educations?.FirstOrDefault(); - Console.WriteLine("5116"); var firstCertificate = candidate.Certificates?.FirstOrDefault(); - Console.WriteLine("5117"); var firstOccupation = candidate.Occupations?.FirstOrDefault(); - Console.WriteLine("5118"); var registAddress = BuildAddress(firstAddress?.Address, firstAddress?.Moo, firstAddress?.Soi, firstAddress?.Road); - Console.WriteLine("5119"); var currentAddress = BuildAddress(firstAddress?.Address1, firstAddress?.Moo1, firstAddress?.Soi1, firstAddress?.Road1); - Console.WriteLine("5120"); // หาค่า posLevelName หลังสุด var posLevelObject = posOptions?.result?.FirstOrDefault(x => !string.IsNullOrWhiteSpace(x.posLevelName) && !string.IsNullOrWhiteSpace(candidate.PositionName) && candidate.PositionName.Contains(x.posLevelName)); - Console.WriteLine("5121"); // เก็บเฉพาะค่า posLevelName var posLevelName = posLevelObject?.posLevelName; // สร้างตัวแปร PositionName ที่ตัดค่า posLevelName ออก var positionNameWithoutLevel = candidate.PositionName ?? ""; - Console.WriteLine("5122"); if (!string.IsNullOrWhiteSpace(posLevelName)) { positionNameWithoutLevel = positionNameWithoutLevel.Replace(posLevelName, "").Trim(); } - Console.WriteLine("5123"); var placementProfile = new PlacementProfile { @@ -425,9 +396,7 @@ namespace BMA.EHR.Recruit.Service.Services posLevelIdOld = org?.result?.posLevelId ?? "", posLevelNameOld = org?.result?.posLevelName ?? "", }; - Console.WriteLine("5124"); placementProfiles.Add(placementProfile); - Console.WriteLine("5125"); var placementEducation = new PlacementEducation { @@ -447,9 +416,7 @@ namespace BMA.EHR.Recruit.Service.Services CreatedFullName = FullName ?? "", LastUpdateFullName = FullName ?? "", }; - Console.WriteLine("5125"); placementEducations.Add(placementEducation); - Console.WriteLine("5126"); var placementCertificate = new PlacementCertificate { @@ -466,17 +433,12 @@ namespace BMA.EHR.Recruit.Service.Services LastUpdateFullName = FullName ?? "", }; placementCertificates.Add(placementCertificate); - Console.WriteLine("511"); } - Console.WriteLine("141"); // 🚀 Batch insert all records await _contextMetadata.PlacementProfiles.AddRangeAsync(placementProfiles); - Console.WriteLine("131"); await _contextMetadata.PlacementEducations.AddRangeAsync(placementEducations); - Console.WriteLine("121"); await _contextMetadata.PlacementCertificates.AddRangeAsync(placementCertificates); - Console.WriteLine("111"); // 🚀 Single SaveChanges at the end await _contextMetadata.SaveChangesAsync(); From 90e86925edc4685354c7a007eea98e8e6955509c Mon Sep 17 00:00:00 2001 From: kittapath Date: Sun, 12 Oct 2025 14:53:35 +0700 Subject: [PATCH 17/18] =?UTF-8?q?=E0=B9=80=E0=B8=8A=E0=B9=87=E0=B8=84?= =?UTF-8?q?=E0=B8=8B=E0=B9=89=E0=B8=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Services/RecruitService.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Services/RecruitService.cs b/Services/RecruitService.cs index 284cfb7..e4b1bea 100644 --- a/Services/RecruitService.cs +++ b/Services/RecruitService.cs @@ -196,8 +196,8 @@ namespace BMA.EHR.Recruit.Service.Services var _placement = await _contextMetadata.Placements.AsQueryable() .FirstOrDefaultAsync(x => x.PlacementType.Name == "สอบแข่งขัน" && x.RefId == recruitImport.Id); - // if (_placement != null) - // throw new Exception("รอบการสอบนี้ได้ทำการบรรจุไปแล้ว"); + if (_placement != null) + throw new Exception("รอบการสอบนี้ได้ทำการบรรจุไปแล้ว"); // 🚀 Pre-load all lookup data once var placementTypesCache = await _contextMetadata.PlacementTypes.ToListAsync(); From 9332086ee9e7ee7a40879a8eedff351c2c62901f Mon Sep 17 00:00:00 2001 From: kittapath Date: Sun, 12 Oct 2025 16:13:04 +0700 Subject: [PATCH 18/18] open noti --- .github/workflows/release.yaml | 72 +++++++++++++++++----------------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 1de509f..f2b3ab2 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -67,40 +67,40 @@ jobs: docker compose pull docker compose up -d echo "${{ steps.gen_ver.outputs.image_ver }}"> success - # - name: Notify Discord Success - # if: success() - # run: | - # curl -H "Content-Type: application/json" \ - # -X POST \ - # -d '{ - # "embeds": [{ - # "title": "✅ Deployment Success!", - # "description": "**Details:**\n- Image: `${{env.IMAGE_NAME}}`\n- Version: `${{ steps.gen_ver.outputs.image_ver }}`\n- Deployed by: `${{github.actor}}`", - # "color": 3066993, - # "footer": { - # "text": "Release Notification", - # "icon_url": "https://example.com/success-icon.png" - # }, - # "timestamp": "'$(date -u +%Y-%m-%dT%H:%M:%SZ)'" - # }] - # }' \ - # ${{ secrets.DISCORD_WEBHOOK }} + - name: Notify Discord Success + if: success() + run: | + curl -H "Content-Type: application/json" \ + -X POST \ + -d '{ + "embeds": [{ + "title": "✅ Deployment Success!", + "description": "**Details:**\n- Image: `${{env.IMAGE_NAME}}`\n- Version: `${{ steps.gen_ver.outputs.image_ver }}`\n- Deployed by: `${{github.actor}}`", + "color": 3066993, + "footer": { + "text": "Release Notification", + "icon_url": "https://example.com/success-icon.png" + }, + "timestamp": "'$(date -u +%Y-%m-%dT%H:%M:%SZ)'" + }] + }' \ + ${{ secrets.DISCORD_WEBHOOK }} - # - name: Notify Discord Failure - # if: failure() - # run: | - # curl -H "Content-Type: application/json" \ - # -X POST \ - # -d '{ - # "embeds": [{ - # "title": "❌ Deployment Failed!", - # "description": "**Details:**\n- Image: `${{env.IMAGE_NAME}}`\n- Version: `${{ steps.gen_ver.outputs.image_ver }}`\n- Attempted by: `${{github.actor}}`", - # "color": 15158332, - # "footer": { - # "text": "Release Notification", - # "icon_url": "https://example.com/failure-icon.png" - # }, - # "timestamp": "'$(date -u +%Y-%m-%dT%H:%M:%SZ)'" - # }] - # }' \ - # ${{ secrets.DISCORD_WEBHOOK }} + - name: Notify Discord Failure + if: failure() + run: | + curl -H "Content-Type: application/json" \ + -X POST \ + -d '{ + "embeds": [{ + "title": "❌ Deployment Failed!", + "description": "**Details:**\n- Image: `${{env.IMAGE_NAME}}`\n- Version: `${{ steps.gen_ver.outputs.image_ver }}`\n- Attempted by: `${{github.actor}}`", + "color": 15158332, + "footer": { + "text": "Release Notification", + "icon_url": "https://example.com/failure-icon.png" + }, + "timestamp": "'$(date -u +%Y-%m-%dT%H:%M:%SZ)'" + }] + }' \ + ${{ secrets.DISCORD_WEBHOOK }}