diff --git a/BMA.EHR.CheckInConsumer/.dockerignore b/BMA.EHR.CheckInConsumer/.dockerignore new file mode 100644 index 00000000..741ce7d4 --- /dev/null +++ b/BMA.EHR.CheckInConsumer/.dockerignore @@ -0,0 +1,24 @@ +# Build artifacts +bin/ +obj/ + +# IDE / tooling +Properties/ +.vs/ +.vscode/ +.idea/ + +# Source control +.git/ +.gitignore + +# Documentation +*.md + +# Docker +Dockerfile +.dockerignore + +# OS files +.DS_Store +Thumbs.db diff --git a/BMA.EHR.CheckInConsumer/CHANGELOG-checkin-speedup.md b/BMA.EHR.CheckInConsumer/CHANGELOG-checkin-speedup.md new file mode 100644 index 00000000..7b20dd1b --- /dev/null +++ b/BMA.EHR.CheckInConsumer/CHANGELOG-checkin-speedup.md @@ -0,0 +1,83 @@ +# สรุปการปรับปรุงระบบลงเวลา (CheckInConsumer) + +วันที่แก้ไข: 23 มิถุนายน 2026 + +--- + +## ปัญหาเดิม + +ตอนที่พนักงานลงเวลาพร้อมกันจำนวนมาก (ประมาณ 2,000 รายการ) ระบบประมวลผลทีละรายการ ทำให้ต้องรอคิวนานถึง **22 นาที** กว่าจะประมวลผลเสร็จทั้งหมด + +เปรียบเทียบเหมือน **โต๊ะบัญชี 1 คน รับคิวทีละคน** ทั้งที่มีคนรอ 2,000 คน → คิวยาวมาก + +--- + +## วิธีที่แก้ (เข้าใจง่าย ๆ) + +### 1. เพิ่มคนช่วยประมวลผลพร้อมกัน (Concurrency) +- **ก่อน:** ประมวลผลทีละรายการ (เหมือนมีโต๊ะบัญชี 1 โต๊ะ) +- **หลัง:** ประมวลผลพร้อมกันได้สูงสุด **5 รายการ** (เหมือนเปิดโต๊ะบัญชี 5 โต๊ะ) + +> ผลที่ได้: เวลารอคิวลดลงจาก **22 นาที → ประมาณ 4–5 นาที** + +### 2. จัดคิวล่วงหน้าให้ RabbitMQ (Prefetch) +- **ก่อน:** ระบบดึงข้อมูลมาทีละชิ้น ทำให้เสียเวลารอส่งต่อ +- **หลัง:** ระบบดึงข้อมูลมาเป็นชุด ๆ ละ 20 ชิ้นไว้เตรียมพร้อม → ลดเวลารอระหว่างรายการ + +### 3. ลดเวลารอเมื่อ API มีปัญหา (Timeout) +- **ก่อน:** ถ้า API ค้าง ระบบจะรอนานถึง **5 นาที** ต่อรายการ +- **หลัง:** ลดเหลือ **1 นาที** → รายการที่มีปัญหาจะถูกปฏิเสธเร็วขึ้น ไม่ทำให้คิวค้าง + +### 4. ปรับปรุงการเชื่อมต่อ HTTP +- เปลี่ยนระบบเชื่อมต่อให้รองรับการส่งคำขอหลายรายการพร้อมกันโดยไม่สะดุด + +--- + +## ตัวเลขเปรียบเทียบ + +| รายการ | ก่อนแก้ | หลังแก้ | +|---|---|---| +| จำนวนรายการที่ประมวลผลพร้อมกัน | 1 | 5 | +| เวลารอคิวสูงสุด (2,000 รายการ) | ~22 นาที | ~4–5 นาที | +| เวลารอเมื่อ API มีปัญหา | 5 นาที | 1 นาที | + +--- + +## ไฟล์ที่แก้ไข + +1. **`Program.cs`** — โค้ดหลักของตัวประมวลผลคิว +2. **`appsettings.json`** — ไฟล์ตั้งค่าระบบ + +--- + +## วิธีปรับความเร็วเพิ่มเติม (ไม่ต้องเขียนโค้ดใหม่) + +ถ้าหลังทดสอบแล้วเห็นว่าระบบรับได้ และอยากให้เร็วขึ้นอีก ให้แก้ไขไฟล์ `appsettings.json` แล้ว restart โปรแกรมได้เลย: + +```json +{ + "MaxConcurrency": 10, ← เพิ่มจาก 5 เป็น 10 (ประมวลผลพร้อมกัน 10 รายการ) + "PrefetchCount": 50, ← ควรตั้งเป็น ประมาณ MaxConcurrency × 2 ขึ้นไป + "HttpTimeoutSeconds": 60 ← เวลารอ API วินาที +} +``` + +**ค่าที่ใช้และผลที่คาดการณ์:** +- `MaxConcurrency = 5` → ใช้เวลา ~4–5 นาที (ค่าเริ่มต้นปลอดภัย) +- `MaxConcurrency = 10` → ใช้เวลา ~2–3 นาที +- `MaxConcurrency = 20` → ใช้เวลา ~1–2 นาที (ต้องตรวจสอบว่าระบบหลังบ้านรับไหวก่อน) + +--- + +## ข้อควรระวัง / คำแนะนำ + +1. **ควรทดสอบในระบบทดสอบก่อน** โดยดูว่า + - ไม่มี error ในระบบหลัก (API) + - ฐานข้อมูลไม่ช้าผิดปกติ + - ไม่พบปัญหาลงเวลาซ้ำซ้อน + +2. ถ้าพบปัญหา เช่น + - มี error ใน API → **ลด** `MaxConcurrency` เหลือ 2 หรือ 3 + - ลงเวลาซ้ำ → แจ้งทีมเทคนิคเพื่อแก้ฝั่ง API เพิ่มเติม + +3. **ค่า `MaxConcurrency = 5` เป็นค่าปลอดภัย** เพราะระบบ API ด้านหลังยังมีข้อจำกัดอยู่บางส่วน หากต้องการเพิ่มให้สูงกว่านี้ (เช่น 20–50) ควรปรึกษาทีมเทคนิคเพื่อปรับปรุงฝั่ง API ก่อน diff --git a/BMA.EHR.CheckInConsumer/Dockerfile b/BMA.EHR.CheckInConsumer/Dockerfile index 1c90d6f2..81c430d9 100644 --- a/BMA.EHR.CheckInConsumer/Dockerfile +++ b/BMA.EHR.CheckInConsumer/Dockerfile @@ -1,4 +1,4 @@ -## See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging. +## See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging. # ## This stage is used when running from VS in fast mode (Default for Debug configuration) #FROM mcr.microsoft.com/dotnet/runtime:8.0 AS base @@ -21,6 +21,7 @@ #ARG BUILD_CONFIGURATION=Release #RUN dotnet publish "BMA.EHR.CheckInConsumer.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false # + ## This stage is used in production or when running from VS in regular mode (Default when not using the Debug configuration) #FROM base AS final #WORKDIR /app @@ -29,30 +30,25 @@ # ใช้ official .NET SDK image สำหรับการ build +# Note: Build context = repository root (ตามที่ GitHub Actions ใช้) FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build -# กำหนด working directory ภายใน container WORKDIR /src -# คัดลอกไฟล์ .csproj และ restore dependencies -# COPY *.csproj ./ -COPY . ./ -RUN dotnet restore +# copy เฉพาะ .csproj ก่อน เพื่อใช้ layer caching (restore เร็ว เก็บ cache นาน) +COPY BMA.EHR.CheckInConsumer/BMA.EHR.CheckInConsumer.csproj ./BMA.EHR.CheckInConsumer/ +WORKDIR /src/BMA.EHR.CheckInConsumer +RUN dotnet restore "BMA.EHR.CheckInConsumer.csproj" -# คัดลอกไฟล์ทั้งหมดและ build -COPY . ./ -RUN dotnet build -c Release -o /app/build -# WORKDIR "/src/BMA.EHR.CheckInConsumer" -# RUN dotnet build "BMA.EHR.CheckInConsumer.csproj" -c Release -o /app/build +# คัดลอก source ที่เหลือแล้ว publish +COPY BMA.EHR.CheckInConsumer/ ./ +RUN dotnet publish "BMA.EHR.CheckInConsumer.csproj" -c Release -o /app/publish /p:UseAppHost=false -# ใช้ stage ใหม่สำหรับการ runtime +# ใช้ stage ใหม่สำหรับ runtime (image เล็กลง) FROM mcr.microsoft.com/dotnet/runtime:8.0 AS runtime -# กำหนด working directory สำหรับ runtime WORKDIR /app -# คัดลอกไฟล์จาก build stage มายัง runtime stage -COPY --from=build /app/build . +COPY --from=build /app/publish . -# ระบุ entry point ของแอปพลิเคชัน ENTRYPOINT ["dotnet", "BMA.EHR.CheckInConsumer.dll"] diff --git a/BMA.EHR.CheckInConsumer/Program.cs b/BMA.EHR.CheckInConsumer/Program.cs index 95dac001..a0ff686c 100644 --- a/BMA.EHR.CheckInConsumer/Program.cs +++ b/BMA.EHR.CheckInConsumer/Program.cs @@ -18,6 +18,13 @@ var user = configuration["Rabbit:User"] ?? ""; var pass = configuration["Rabbit:Password"] ?? ""; var queue = configuration["Rabbit:Queue"] ?? "basic-queue"; +// Concurrency & prefetch (configurable via appsettings.json) +var maxConcurrency = int.TryParse(configuration["MaxConcurrency"], out var c) && c > 0 ? c : 5; +var prefetchCount = ushort.TryParse(configuration["PrefetchCount"], out var p) && p > 0 ? p : (ushort)20; +var httpTimeoutSec = int.TryParse(configuration["HttpTimeoutSeconds"], out var t) && t > 0 ? t : 60; + +WriteToConsole($"Config -> MaxConcurrency: {maxConcurrency}, PrefetchCount: {prefetchCount}, HttpTimeout: {httpTimeoutSec}s"); + // create connection var factory = new ConnectionFactory() { @@ -32,39 +39,61 @@ using var channel = connection.CreateModel(); channel.QueueDeclare(queue: queue, durable: true, exclusive: false, autoDelete: false, arguments: null); -// Create a SINGLE static HttpClient instance to prevent socket exhaustion -using var httpClient = new HttpClient(); -httpClient.Timeout = TimeSpan.FromSeconds(300); // 5 นาที +// Prefetch: RabbitMQ จะส่ง message หลายตัวมาที่ consumer พร้อมกัน (ลด network round-trip) +channel.BasicQos(prefetchSize: 0, prefetchCount: prefetchCount, global: false); + +// HttpClient แบบ SocketsHttpHandler พร้อม connection pooling รองรับ concurrent requests +var socketsHandler = new SocketsHttpHandler +{ + MaxConnectionsPerServer = maxConcurrency * 2, + PooledConnectionLifetime = TimeSpan.FromMinutes(2), + PooledConnectionIdleTimeout = TimeSpan.FromSeconds(30) +}; +using var httpClient = new HttpClient(socketsHandler); +httpClient.Timeout = TimeSpan.FromSeconds(httpTimeoutSec); + +// SemaphoreSlim คุมจำนวน message ที่ประมวลผลพร้อมกัน (เนื่องจาก API มีข้อจำกัดเรื่อง concurrency) +using var semaphore = new SemaphoreSlim(maxConcurrency, maxConcurrency); var consumer = new AsyncEventingBasicConsumer(channel); -consumer.Received += async (model, ea) => +consumer.Received += (model, ea) => { - try + // รอ semaphore ก่อนเริ่มประมวลผล + semaphore.WaitAsync().ContinueWith(async _ => { - var body = ea.Body.ToArray(); - var message = Encoding.UTF8.GetString(body); - - WriteToConsole($"Received message: {message}"); - - var success = await CallRestApi(message, httpClient, configuration); - - if (success) + try { - channel.BasicAck(ea.DeliveryTag, multiple: false); - WriteToConsole("Message processed successfully"); + var body = ea.Body.ToArray(); + var message = Encoding.UTF8.GetString(body); + + WriteToConsole($"Received message: {message}"); + + var success = await CallRestApi(message, httpClient, configuration); + + if (success) + { + channel.BasicAck(ea.DeliveryTag, multiple: false); + WriteToConsole("Message processed successfully"); + } + else + { + channel.BasicNack(ea.DeliveryTag, multiple: false, requeue: false); + WriteToConsole("Message processing failed - message rejected"); + } } - else + catch (Exception ex) { + WriteToConsole($"Error processing message: {ex.Message}"); channel.BasicNack(ea.DeliveryTag, multiple: false, requeue: false); - WriteToConsole("Message processing failed - message rejected"); } - } - catch (Exception ex) - { - WriteToConsole($"Error processing message: {ex.Message}"); - channel.BasicNack(ea.DeliveryTag, multiple: false, requeue: false); - } + finally + { + semaphore.Release(); + } + }, TaskScheduler.Default).ConfigureAwait(false); + + return Task.CompletedTask; }; channel.BasicConsume(queue: queue, autoAck: false, consumer: consumer); diff --git a/BMA.EHR.CheckInConsumer/appsettings.json b/BMA.EHR.CheckInConsumer/appsettings.json index 76f86c86..1fbba85b 100644 --- a/BMA.EHR.CheckInConsumer/appsettings.json +++ b/BMA.EHR.CheckInConsumer/appsettings.json @@ -5,5 +5,8 @@ "Password": "12345678", "Queue": "hrms-checkin-queue-dev" }, - "API": "https://localhost:7283/api/v1" + "API": "https://localhost:7283/api/v1", + "MaxConcurrency": 5, + "PrefetchCount": 20, + "HttpTimeoutSeconds": 60 } diff --git a/BMA.EHR.Discipline.Service/Controllers/DisciplineResultController.cs b/BMA.EHR.Discipline.Service/Controllers/DisciplineResultController.cs index 35a9dfa2..f6697c2e 100644 --- a/BMA.EHR.Discipline.Service/Controllers/DisciplineResultController.cs +++ b/BMA.EHR.Discipline.Service/Controllers/DisciplineResultController.cs @@ -1028,10 +1028,6 @@ namespace BMA.EHR.DisciplineResult.Service.Controllers .Where(x => req.refIds.Select(x => x.refId).Contains(x.Id.ToString())) .ToListAsync(); - // Task #224 ปรับให้เป็น process ที่ควรบันทึกตามลำดับ - data.ForEach(profile => { profile.Status = "REPORTED"; profile.CommandTypeId = null; }); - await _context.SaveChangesAsync(); - var resultData = (from p in data join r in req.refIds on p.Id.ToString() equals r.refId @@ -1067,52 +1063,95 @@ namespace BMA.EHR.DisciplineResult.Service.Controllers posNoAbb = p.child4ShortName != null ? p.child4ShortName : (p.child3ShortName != null ? p.child3ShortName : (p.child2ShortName != null ? p.child2ShortName : (p.child1ShortName != null ? p.child1ShortName : (p.rootShortName != null ? p.rootShortName : "")))), }).ToList(); - var baseAPIOrg = _configuration["API"]; - var apiUrlOrg = $"{baseAPIOrg}/org/command/excexute/salary-leave-discipline"; - using (var client = new HttpClient()) + #region Old: Circular Flow + // var baseAPIOrg = _configuration["API"]; + // var apiUrlOrg = $"{baseAPIOrg}/org/command/excexute/salary-leave-discipline"; + // using (var client = new HttpClient()) + // { + // client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token.Replace("Bearer ", "")); + // client.DefaultRequestHeaders.Add("api-key", _configuration["API_KEY"]); + // var _res = await client.PostAsJsonAsync(apiUrlOrg, new + // { + // data = resultData, + // }); + // var _result = await _res.Content.ReadAsStringAsync(); + // if (_res.IsSuccessStatusCode) + // { + // // คำสั่งไล่ออก หรือ ปลดออก Status หลังออกคำสั่งใช้ "REPORTED" เพื่อไม่ให้ส่งรายชื่อไปออกคำสั่งซ้ำได้ + // data.ForEach(profile => { profile.Status = "REPORTED"; profile.CommandTypeId = null; }); + // var _profile = new List(); + // DateTime _date = DateTime.Now; + // foreach (var item in data) + // { + // _profile.Add(new ProfileComplaintInvestigate + // { + // PersonId = item.PersonId, + // Prefix = item.Prefix, + // FirstName = item.FirstName, + // LastName = item.LastName, + // CitizenId = item.CitizenId, + // rootDnaId = item.rootDnaId, + // child1DnaId = item.child1DnaId, + // child2DnaId = item.child2DnaId, + // child3DnaId = item.child3DnaId, + // child4DnaId = item.child4DnaId, + // profileType = item.profileType, + // commandType = "C-PM-19", + // CreatedAt = _date, + // CreatedUserId = UserId, + // CreatedFullName = FullName, + // LastUpdatedAt = _date, + // LastUpdateUserId = UserId, + // LastUpdateFullName = FullName, + // }); + // } + // _context.ProfileComplaintInvestigate.AddRange(_profile); + // await _context.SaveChangesAsync(); + // } + // } + #endregion + + // New: Linear Flow + var now = DateTime.Now; + data.ForEach(profile => { - client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token.Replace("Bearer ", "")); - client.DefaultRequestHeaders.Add("api-key", _configuration["API_KEY"]); - var _res = await client.PostAsJsonAsync(apiUrlOrg, new + profile.Status = "REPORTED"; + profile.CommandTypeId = null; + profile.LastUpdateFullName = FullName ?? "System Administrator"; + profile.LastUpdateUserId = UserId ?? ""; + profile.LastUpdatedAt = now; + }); + + var _profile = new List(); + foreach (var item in data) + { + _profile.Add(new ProfileComplaintInvestigate { - data = resultData, + PersonId = item.PersonId, + Prefix = item.Prefix, + FirstName = item.FirstName, + LastName = item.LastName, + CitizenId = item.CitizenId, + rootDnaId = item.rootDnaId, + child1DnaId = item.child1DnaId, + child2DnaId = item.child2DnaId, + child3DnaId = item.child3DnaId, + child4DnaId = item.child4DnaId, + profileType = item.profileType, + commandType = "C-PM-19", + CreatedAt = now, + CreatedUserId = UserId ?? "", + CreatedFullName = FullName ?? "System Administrator", + LastUpdatedAt = now, + LastUpdateUserId = UserId ?? "", + LastUpdateFullName = FullName ?? "System Administrator", }); - var _result = await _res.Content.ReadAsStringAsync(); - if (_res.IsSuccessStatusCode) - { - //// คำสั่งไล่ออก หรือ ปลดออก Status หลังออกคำสั่งใช้ "REPORTED" เพื่อไม่ให้ส่งรายชื่อไปออกคำสั่งซ้ำได้ - // data.ForEach(profile => { profile.Status = "REPORTED"; profile.CommandTypeId = null; }); - var _profile = new List(); - DateTime _date = DateTime.Now; - foreach (var item in data) - { - _profile.Add(new ProfileComplaintInvestigate - { - PersonId = item.PersonId, - Prefix = item.Prefix, - FirstName = item.FirstName, - LastName = item.LastName, - CitizenId = item.CitizenId, - rootDnaId = item.rootDnaId, - child1DnaId = item.child1DnaId, - child2DnaId = item.child2DnaId, - child3DnaId = item.child3DnaId, - child4DnaId = item.child4DnaId, - profileType = item.profileType, - commandType = "C-PM-19", - CreatedAt = _date, - CreatedUserId = UserId, - CreatedFullName = FullName, - LastUpdatedAt = _date, - LastUpdateUserId = UserId, - LastUpdateFullName = FullName, - }); - } - _context.ProfileComplaintInvestigate.AddRange(_profile); - await _context.SaveChangesAsync(); - } } - return Success(); + _context.ProfileComplaintInvestigate.AddRange(_profile); + await _context.SaveChangesAsync(); + + // Return resultData for Node to process directly (Linear Flow) + return Success(resultData); } /// @@ -1184,10 +1223,6 @@ namespace BMA.EHR.DisciplineResult.Service.Controllers .Where(x => req.refIds.Select(x => x.refId).Contains(x.Id.ToString())) .ToListAsync(); - // Task #224 ปรับให้เป็น process ที่ควรบันทึกตามลำดับ - data.ForEach(profile => { profile.Status = "REPORTED"; profile.CommandTypeId = null; }); - await _context.SaveChangesAsync(); - var resultData = (from p in data join r in req.refIds on p.Id.ToString() equals r.refId @@ -1223,52 +1258,95 @@ namespace BMA.EHR.DisciplineResult.Service.Controllers posNoAbb = p.child4ShortName != null ? p.child4ShortName : (p.child3ShortName != null ? p.child3ShortName : (p.child2ShortName != null ? p.child2ShortName : (p.child1ShortName != null ? p.child1ShortName : (p.rootShortName != null ? p.rootShortName : "")))), }).ToList(); - var baseAPIOrg = _configuration["API"]; - var apiUrlOrg = $"{baseAPIOrg}/org/command/excexute/salary-leave-discipline"; - using (var client = new HttpClient()) + #region Old: Circular Flow + // var baseAPIOrg = _configuration["API"]; + // var apiUrlOrg = $"{baseAPIOrg}/org/command/excexute/salary-leave-discipline"; + // using (var client = new HttpClient()) + // { + // client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token.Replace("Bearer ", "")); + // client.DefaultRequestHeaders.Add("api-key", _configuration["API_KEY"]); + // var _res = await client.PostAsJsonAsync(apiUrlOrg, new + // { + // data = resultData, + // }); + // var _result = await _res.Content.ReadAsStringAsync(); + // if (_res.IsSuccessStatusCode) + // { + // // คำสั่งไล่ออก หรือ ปลดออก Status หลังออกคำสั่งใช้ "REPORTED" เพื่อไม่ให้ส่งรายชื่อไปออกคำสั่งซ้ำได้ + // data.ForEach(profile => { profile.Status = "REPORTED"; profile.CommandTypeId = null; }); + // var _profile = new List(); + // DateTime _date = DateTime.Now; + // foreach (var item in data) + // { + // _profile.Add(new ProfileComplaintInvestigate + // { + // PersonId = item.PersonId, + // Prefix = item.Prefix, + // FirstName = item.FirstName, + // LastName = item.LastName, + // CitizenId = item.CitizenId, + // rootDnaId = item.rootDnaId, + // child1DnaId = item.child1DnaId, + // child2DnaId = item.child2DnaId, + // child3DnaId = item.child3DnaId, + // child4DnaId = item.child4DnaId, + // profileType = item.profileType, + // commandType = "C-PM-20", + // CreatedAt = _date, + // CreatedUserId = UserId, + // CreatedFullName = FullName, + // LastUpdatedAt = _date, + // LastUpdateUserId = UserId, + // LastUpdateFullName = FullName, + // }); + // } + // _context.ProfileComplaintInvestigate.AddRange(_profile); + // await _context.SaveChangesAsync(); + // } + // } + #endregion + + // New: Linear Flow + var now = DateTime.Now; + data.ForEach(profile => { - client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token.Replace("Bearer ", "")); - client.DefaultRequestHeaders.Add("api-key", _configuration["API_KEY"]); - var _res = await client.PostAsJsonAsync(apiUrlOrg, new + profile.Status = "REPORTED"; + profile.CommandTypeId = null; + profile.LastUpdateFullName = FullName ?? "System Administrator"; + profile.LastUpdateUserId = UserId ?? ""; + profile.LastUpdatedAt = now; + }); + + var _profile = new List(); + foreach (var item in data) + { + _profile.Add(new ProfileComplaintInvestigate { - data = resultData, + PersonId = item.PersonId, + Prefix = item.Prefix, + FirstName = item.FirstName, + LastName = item.LastName, + CitizenId = item.CitizenId, + rootDnaId = item.rootDnaId, + child1DnaId = item.child1DnaId, + child2DnaId = item.child2DnaId, + child3DnaId = item.child3DnaId, + child4DnaId = item.child4DnaId, + profileType = item.profileType, + commandType = "C-PM-20", + CreatedAt = now, + CreatedUserId = UserId ?? "", + CreatedFullName = FullName ?? "System Administrator", + LastUpdatedAt = now, + LastUpdateUserId = UserId ?? "", + LastUpdateFullName = FullName ?? "System Administrator", }); - var _result = await _res.Content.ReadAsStringAsync(); - if (_res.IsSuccessStatusCode) - { - //// คำสั่งไล่ออก หรือ ปลดออก Status หลังออกคำสั่งใช้ "REPORTED" เพื่อไม่ให้ส่งรายชื่อไปออกคำสั่งซ้ำได้ - // data.ForEach(profile => { profile.Status = "REPORTED"; profile.CommandTypeId = null; }); - var _profile = new List(); - DateTime _date = DateTime.Now; - foreach (var item in data) - { - _profile.Add(new ProfileComplaintInvestigate - { - PersonId = item.PersonId, - Prefix = item.Prefix, - FirstName = item.FirstName, - LastName = item.LastName, - CitizenId = item.CitizenId, - rootDnaId = item.rootDnaId, - child1DnaId = item.child1DnaId, - child2DnaId = item.child2DnaId, - child3DnaId = item.child3DnaId, - child4DnaId = item.child4DnaId, - profileType = item.profileType, - commandType = "C-PM-20", - CreatedAt = _date, - CreatedUserId = UserId, - CreatedFullName = FullName, - LastUpdatedAt = _date, - LastUpdateUserId = UserId, - LastUpdateFullName = FullName, - }); - } - _context.ProfileComplaintInvestigate.AddRange(_profile); - await _context.SaveChangesAsync(); - } } - return Success(); + _context.ProfileComplaintInvestigate.AddRange(_profile); + await _context.SaveChangesAsync(); + + // Return resultData for Node to process directly (Linear Flow) + return Success(resultData); } /// @@ -1418,10 +1496,6 @@ namespace BMA.EHR.DisciplineResult.Service.Controllers .Where(x => req.refIds.Select(x => x.refId).Contains(x.Id.ToString())) .ToListAsync(); - // Task #224 ปรับให้เป็น process ที่ควรบันทึกตามลำดับ - data.ForEach(profile => profile.Status = "DONE"); - await _context.SaveChangesAsync(); - var resultData = (from p in data join r in req.refIds on p.Id.ToString() equals r.refId @@ -1457,24 +1531,39 @@ namespace BMA.EHR.DisciplineResult.Service.Controllers posNoAbb = p.child4ShortName != null ? p.child4ShortName : (p.child3ShortName != null ? p.child3ShortName : (p.child2ShortName != null ? p.child2ShortName : (p.child1ShortName != null ? p.child1ShortName : (p.rootShortName != null ? p.rootShortName : "")))), }).ToList(); - var baseAPIOrg = _configuration["API"]; - var apiUrlOrg = $"{baseAPIOrg}/org/command/excexute/salary-leave-discipline"; - using (var client = new HttpClient()) + #region Old: Circular Flow + // var baseAPIOrg = _configuration["API"]; + // var apiUrlOrg = $"{baseAPIOrg}/org/command/excexute/salary-leave-discipline"; + // using (var client = new HttpClient()) + // { + // client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token.Replace("Bearer ", "")); + // client.DefaultRequestHeaders.Add("api-key", _configuration["API_KEY"]); + // var _res = await client.PostAsJsonAsync(apiUrlOrg, new + // { + // data = resultData, + // }); + // var _result = await _res.Content.ReadAsStringAsync(); + // if (_res.IsSuccessStatusCode) + // { + // data.ForEach(profile => profile.Status = "DONE"); + // await _context.SaveChangesAsync(); + // } + // } + #endregion + + // New: Linear Flow + var now = DateTime.Now; + data.ForEach(profile => { - client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token.Replace("Bearer ", "")); - client.DefaultRequestHeaders.Add("api-key", _configuration["API_KEY"]); - var _res = await client.PostAsJsonAsync(apiUrlOrg, new - { - data = resultData, - }); - //// var _result = await _res.Content.ReadAsStringAsync(); - //// if (_res.IsSuccessStatusCode) - //// { - //// data.ForEach(profile => profile.Status = "DONE"); - //// await _context.SaveChangesAsync(); - //// } - } - return Success(); + profile.Status = "DONE"; + profile.LastUpdateFullName = FullName ?? "System Administrator"; + profile.LastUpdateUserId = UserId ?? ""; + profile.LastUpdatedAt = now; + }); + await _context.SaveChangesAsync(); + + // Return resultData for Node to process directly (Linear Flow) + return Success(resultData); } /// @@ -1562,10 +1651,6 @@ namespace BMA.EHR.DisciplineResult.Service.Controllers .Where(x => req.refIds.Select(x => x.refId).Contains(x.Id.ToString())) .ToListAsync(); - // Task #224 ปรับให้เป็น process ที่ควรบันทึกตามลำดับ - data.ForEach(profile => profile.Status = "DONE"); - await _context.SaveChangesAsync(); - var resultData = (from p in data join r in req.refIds on p.Id.ToString() equals r.refId @@ -1601,24 +1686,39 @@ namespace BMA.EHR.DisciplineResult.Service.Controllers posNoAbb = p.child4ShortName != null ? p.child4ShortName : (p.child3ShortName != null ? p.child3ShortName : (p.child2ShortName != null ? p.child2ShortName : (p.child1ShortName != null ? p.child1ShortName : (p.rootShortName != null ? p.rootShortName : "")))), }).ToList(); - var baseAPIOrg = _configuration["API"]; - var apiUrlOrg = $"{baseAPIOrg}/org/command/excexute/salary-leave-discipline"; - using (var client = new HttpClient()) + #region Old: Circular Flow + // var baseAPIOrg = _configuration["API"]; + // var apiUrlOrg = $"{baseAPIOrg}/org/command/excexute/salary-leave-discipline"; + // using (var client = new HttpClient()) + // { + // client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token.Replace("Bearer ", "")); + // client.DefaultRequestHeaders.Add("api-key", _configuration["API_KEY"]); + // var _res = await client.PostAsJsonAsync(apiUrlOrg, new + // { + // data = resultData, + // }); + // var _result = await _res.Content.ReadAsStringAsync(); + // if (_res.IsSuccessStatusCode) + // { + // data.ForEach(profile => profile.Status = "DONE"); + // await _context.SaveChangesAsync(); + // } + // } + #endregion + + // New: Linear Flow + var now = DateTime.Now; + data.ForEach(profile => { - client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token.Replace("Bearer ", "")); - client.DefaultRequestHeaders.Add("api-key", _configuration["API_KEY"]); - var _res = await client.PostAsJsonAsync(apiUrlOrg, new - { - data = resultData, - }); - //// var _result = await _res.Content.ReadAsStringAsync(); - //// if (_res.IsSuccessStatusCode) - //// { - //// data.ForEach(profile => profile.Status = "DONE"); - //// await _context.SaveChangesAsync(); - //// } - } - return Success(); + profile.Status = "DONE"; + profile.LastUpdateFullName = FullName ?? "System Administrator"; + profile.LastUpdateUserId = UserId ?? ""; + profile.LastUpdatedAt = now; + }); + await _context.SaveChangesAsync(); + + // Return resultData for Node to process directly (Linear Flow) + return Success(resultData); } /// @@ -1704,10 +1804,6 @@ namespace BMA.EHR.DisciplineResult.Service.Controllers .Where(x => req.refIds.Select(x => x.refId).Contains(x.Id.ToString())) .ToListAsync(); - // Task #224 ปรับให้เป็น process ที่ควรบันทึกตามลำดับ - data.ForEach(profile => { profile.Status = "NEW"; profile.CommandTypeId = null; }); - await _context.SaveChangesAsync(); - string? _null = null; var resultData = (from p in data join r in req.refIds @@ -1743,24 +1839,40 @@ namespace BMA.EHR.DisciplineResult.Service.Controllers posNoAbb = p.child4ShortName != null ? p.child4ShortName : (p.child3ShortName != null ? p.child3ShortName : (p.child2ShortName != null ? p.child2ShortName : (p.child1ShortName != null ? p.child1ShortName : (p.rootShortName != null ? p.rootShortName : "")))), }).ToList(); - var baseAPIOrg = _configuration["API"]; - var apiUrlOrg = $"{baseAPIOrg}/org/command/excexute/salary-leave-discipline"; - using (var client = new HttpClient()) + #region Old: Circular Flow + // var baseAPIOrg = _configuration["API"]; + // var apiUrlOrg = $"{baseAPIOrg}/org/command/excexute/salary-leave-discipline"; + // using (var client = new HttpClient()) + // { + // client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token.Replace("Bearer ", "")); + // client.DefaultRequestHeaders.Add("api-key", _configuration["API_KEY"]); + // var _res = await client.PostAsJsonAsync(apiUrlOrg, new + // { + // data = resultData, + // }); + // var _result = await _res.Content.ReadAsStringAsync(); + // if (_res.IsSuccessStatusCode) + // { + // data.ForEach(profile => { profile.Status = "NEW"; profile.CommandTypeId = null; }); + // await _context.SaveChangesAsync(); + // } + // } + #endregion + + // New: Linear Flow + var now = DateTime.Now; + data.ForEach(profile => { - client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token.Replace("Bearer ", "")); - client.DefaultRequestHeaders.Add("api-key", _configuration["API_KEY"]); - var _res = await client.PostAsJsonAsync(apiUrlOrg, new - { - data = resultData, - }); - //// var _result = await _res.Content.ReadAsStringAsync(); - //// if (_res.IsSuccessStatusCode) - //// { - //// data.ForEach(profile => { profile.Status = "NEW"; profile.CommandTypeId = null; }); - //// await _context.SaveChangesAsync(); - //// } - } - return Success(); + profile.Status = "NEW"; + profile.CommandTypeId = null; + profile.LastUpdateFullName = FullName ?? "System Administrator"; + profile.LastUpdateUserId = UserId ?? ""; + profile.LastUpdatedAt = now; + }); + await _context.SaveChangesAsync(); + + // Return resultData for Node to process directly (Linear Flow) + return Success(resultData); } /// @@ -1846,10 +1958,6 @@ namespace BMA.EHR.DisciplineResult.Service.Controllers .Where(x => req.refIds.Select(x => x.refId).Contains(x.Id.ToString())) .ToListAsync(); - // Task #224 ปรับให้เป็น process ที่ควรบันทึกตามลำดับ - data.ForEach(profile => { profile.Status = "NEW"; profile.CommandTypeId = null; }); - await _context.SaveChangesAsync(); - string? _null = null; var resultData = (from p in data join r in req.refIds @@ -1885,24 +1993,40 @@ namespace BMA.EHR.DisciplineResult.Service.Controllers posNoAbb = p.child4ShortName != null ? p.child4ShortName : (p.child3ShortName != null ? p.child3ShortName : (p.child2ShortName != null ? p.child2ShortName : (p.child1ShortName != null ? p.child1ShortName : (p.rootShortName != null ? p.rootShortName : "")))), }).ToList(); - var baseAPIOrg = _configuration["API"]; - var apiUrlOrg = $"{baseAPIOrg}/org/command/excexute/salary-leave-discipline"; - using (var client = new HttpClient()) + #region Old: Circular Flow + // var baseAPIOrg = _configuration["API"]; + // var apiUrlOrg = $"{baseAPIOrg}/org/command/excexute/salary-leave-discipline"; + // using (var client = new HttpClient()) + // { + // client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token.Replace("Bearer ", "")); + // client.DefaultRequestHeaders.Add("api-key", _configuration["API_KEY"]); + // var _res = await client.PostAsJsonAsync(apiUrlOrg, new + // { + // data = resultData, + // }); + // var _result = await _res.Content.ReadAsStringAsync(); + // if (_res.IsSuccessStatusCode) + // { + // data.ForEach(profile => { profile.Status = "NEW"; profile.CommandTypeId = null; }); + // await _context.SaveChangesAsync(); + // } + // } + #endregion + + // New: Linear Flow + var now = DateTime.Now; + data.ForEach(profile => { - client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token.Replace("Bearer ", "")); - client.DefaultRequestHeaders.Add("api-key", _configuration["API_KEY"]); - var _res = await client.PostAsJsonAsync(apiUrlOrg, new - { - data = resultData, - }); - //// var _result = await _res.Content.ReadAsStringAsync(); - //// if (_res.IsSuccessStatusCode) - //// { - //// data.ForEach(profile => { profile.Status = "NEW"; profile.CommandTypeId = null; }); - //// await _context.SaveChangesAsync(); - //// } - } - return Success(); + profile.Status = "NEW"; + profile.CommandTypeId = null; + profile.LastUpdateFullName = FullName ?? "System Administrator"; + profile.LastUpdateUserId = UserId ?? ""; + profile.LastUpdatedAt = now; + }); + await _context.SaveChangesAsync(); + + // Return resultData for Node to process directly (Linear Flow) + return Success(resultData); } /// @@ -1988,10 +2112,6 @@ namespace BMA.EHR.DisciplineResult.Service.Controllers .Where(x => req.refIds.Select(x => x.refId).Contains(x.Id.ToString())) .ToListAsync(); - // Task #224 ปรับให้เป็น process ที่ควรบันทึกตามลำดับ - data.ForEach(profile => { profile.Status = "NEW"; profile.CommandTypeId = null; }); - await _context.SaveChangesAsync(); - string? _null = null; var resultData = (from p in data join r in req.refIds @@ -2027,24 +2147,40 @@ namespace BMA.EHR.DisciplineResult.Service.Controllers posNoAbb = p.child4ShortName != null ? p.child4ShortName : (p.child3ShortName != null ? p.child3ShortName : (p.child2ShortName != null ? p.child2ShortName : (p.child1ShortName != null ? p.child1ShortName : (p.rootShortName != null ? p.rootShortName : "")))), }).ToList(); - var baseAPIOrg = _configuration["API"]; - var apiUrlOrg = $"{baseAPIOrg}/org/command/excexute/salary-leave-discipline"; - using (var client = new HttpClient()) + #region Old: Circular Flow + // var baseAPIOrg = _configuration["API"]; + // var apiUrlOrg = $"{baseAPIOrg}/org/command/excexute/salary-leave-discipline"; + // using (var client = new HttpClient()) + // { + // client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token.Replace("Bearer ", "")); + // client.DefaultRequestHeaders.Add("api-key", _configuration["API_KEY"]); + // var _res = await client.PostAsJsonAsync(apiUrlOrg, new + // { + // data = resultData, + // }); + // var _result = await _res.Content.ReadAsStringAsync(); + // if (_res.IsSuccessStatusCode) + // { + // data.ForEach(profile => { profile.Status = "NEW"; profile.CommandTypeId = null; }); + // await _context.SaveChangesAsync(); + // } + // } + #endregion + + // New: Linear Flow + var now = DateTime.Now; + data.ForEach(profile => { - client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token.Replace("Bearer ", "")); - client.DefaultRequestHeaders.Add("api-key", _configuration["API_KEY"]); - var _res = await client.PostAsJsonAsync(apiUrlOrg, new - { - data = resultData, - }); - //// var _result = await _res.Content.ReadAsStringAsync(); - //// if (_res.IsSuccessStatusCode) - //// { - //// data.ForEach(profile => { profile.Status = "NEW"; profile.CommandTypeId = null; }); - //// await _context.SaveChangesAsync(); - //// } - } - return Success(); + profile.Status = "NEW"; + profile.CommandTypeId = null; + profile.LastUpdateFullName = FullName ?? "System Administrator"; + profile.LastUpdateUserId = UserId ?? ""; + profile.LastUpdatedAt = now; + }); + await _context.SaveChangesAsync(); + + // Return resultData for Node to process directly (Linear Flow) + return Success(resultData); } /// @@ -2130,10 +2266,6 @@ namespace BMA.EHR.DisciplineResult.Service.Controllers .Where(x => req.refIds.Select(x => x.refId).Contains(x.Id.ToString())) .ToListAsync(); - // Task #224 ปรับให้เป็น process ที่ควรบันทึกตามลำดับ - data.ForEach(profile => { profile.Status = "NEW"; profile.CommandTypeId = null; }); - await _context.SaveChangesAsync(); - string? _null = null; var resultData = (from p in data join r in req.refIds @@ -2169,24 +2301,40 @@ namespace BMA.EHR.DisciplineResult.Service.Controllers posNoAbb = p.child4ShortName != null ? p.child4ShortName : (p.child3ShortName != null ? p.child3ShortName : (p.child2ShortName != null ? p.child2ShortName : (p.child1ShortName != null ? p.child1ShortName : (p.rootShortName != null ? p.rootShortName : "")))), }).ToList(); - var baseAPIOrg = _configuration["API"]; - var apiUrlOrg = $"{baseAPIOrg}/org/command/excexute/salary-leave-discipline"; - using (var client = new HttpClient()) + #region Old: Circular Flow + // var baseAPIOrg = _configuration["API"]; + // var apiUrlOrg = $"{baseAPIOrg}/org/command/excexute/salary-leave-discipline"; + // using (var client = new HttpClient()) + // { + // client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token.Replace("Bearer ", "")); + // client.DefaultRequestHeaders.Add("api-key", _configuration["API_KEY"]); + // var _res = await client.PostAsJsonAsync(apiUrlOrg, new + // { + // data = resultData, + // }); + // var _result = await _res.Content.ReadAsStringAsync(); + // if (_res.IsSuccessStatusCode) + // { + // data.ForEach(profile => { profile.Status = "NEW"; profile.CommandTypeId = null; }); + // await _context.SaveChangesAsync(); + // } + // } + #endregion + + // New: Linear Flow + var now = DateTime.Now; + data.ForEach(profile => { - client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token.Replace("Bearer ", "")); - client.DefaultRequestHeaders.Add("api-key", _configuration["API_KEY"]); - var _res = await client.PostAsJsonAsync(apiUrlOrg, new - { - data = resultData, - }); - //// var _result = await _res.Content.ReadAsStringAsync(); - //// if (_res.IsSuccessStatusCode) - //// { - //// data.ForEach(profile => { profile.Status = "NEW"; profile.CommandTypeId = null; }); - //// await _context.SaveChangesAsync(); - //// } - } - return Success(); + profile.Status = "NEW"; + profile.CommandTypeId = null; + profile.LastUpdateFullName = FullName ?? "System Administrator"; + profile.LastUpdateUserId = UserId ?? ""; + profile.LastUpdatedAt = now; + }); + await _context.SaveChangesAsync(); + + // Return resultData for Node to process directly (Linear Flow) + return Success(resultData); } /// @@ -2272,10 +2420,6 @@ namespace BMA.EHR.DisciplineResult.Service.Controllers .Where(x => req.refIds.Select(x => x.refId).Contains(x.Id.ToString())) .ToListAsync(); - // Task #224 ปรับให้เป็น process ที่ควรบันทึกตามลำดับ - data.ForEach(profile => { profile.Status = "NEW"; profile.CommandTypeId = null; }); - await _context.SaveChangesAsync(); - string? _null = null; var resultData = (from p in data join r in req.refIds @@ -2311,24 +2455,40 @@ namespace BMA.EHR.DisciplineResult.Service.Controllers posNoAbb = p.child4ShortName != null ? p.child4ShortName : (p.child3ShortName != null ? p.child3ShortName : (p.child2ShortName != null ? p.child2ShortName : (p.child1ShortName != null ? p.child1ShortName : (p.rootShortName != null ? p.rootShortName : "")))), }).ToList(); - var baseAPIOrg = _configuration["API"]; - var apiUrlOrg = $"{baseAPIOrg}/org/command/excexute/salary-leave-discipline"; - using (var client = new HttpClient()) + #region Old: Circular Flow + // var baseAPIOrg = _configuration["API"]; + // var apiUrlOrg = $"{baseAPIOrg}/org/command/excexute/salary-leave-discipline"; + // using (var client = new HttpClient()) + // { + // client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token.Replace("Bearer ", "")); + // client.DefaultRequestHeaders.Add("api-key", _configuration["API_KEY"]); + // var _res = await client.PostAsJsonAsync(apiUrlOrg, new + // { + // data = resultData, + // }); + // var _result = await _res.Content.ReadAsStringAsync(); + // if (_res.IsSuccessStatusCode) + // { + // data.ForEach(profile => { profile.Status = "NEW"; profile.CommandTypeId = null; }); + // await _context.SaveChangesAsync(); + // } + // } + #endregion + + // New: Linear Flow + var now = DateTime.Now; + data.ForEach(profile => { - client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token.Replace("Bearer ", "")); - client.DefaultRequestHeaders.Add("api-key", _configuration["API_KEY"]); - var _res = await client.PostAsJsonAsync(apiUrlOrg, new - { - data = resultData, - }); - //// var _result = await _res.Content.ReadAsStringAsync(); - //// if (_res.IsSuccessStatusCode) - //// { - //// data.ForEach(profile => { profile.Status = "NEW"; profile.CommandTypeId = null; }); - //// await _context.SaveChangesAsync(); - //// } - } - return Success(); + profile.Status = "NEW"; + profile.CommandTypeId = null; + profile.LastUpdateFullName = FullName ?? "System Administrator"; + profile.LastUpdateUserId = UserId ?? ""; + profile.LastUpdatedAt = now; + }); + await _context.SaveChangesAsync(); + + // Return resultData for Node to process directly (Linear Flow) + return Success(resultData); } /// @@ -2424,15 +2584,21 @@ namespace BMA.EHR.DisciplineResult.Service.Controllers [HttpPost("command32/report/excecute")] public async Task> PostReportCommand32Execute([FromBody] ReportExecuteRequest req) { + // C-PM-32 (คำสั่งยุติเรื่อง) ต้องยุติงานใน 2 track ที่เก็บอยู่คนละตาราง: + // - data / resultData = ฝั่ง "การสอบสวน" (DisciplineInvestigate_ProfileComplaint) + // - data1 / resultData1 = ฝั่ง "การพิจารณาลงโทษ" (DisciplineDisciplinary_ProfileComplaintInvestigate) + // บุคคลเดียวกัน (profileId เดียวกัน) อาจอยู่ในทั้ง 2 track จึงต้องส่งให้ org แยก 2 ครั้ง (ห้าม merge รวมครั้งเดียว) var data = await _context.DisciplineInvestigate_ProfileComplaints .Include(x => x.DisciplineInvestigate) // .Where(x => x.IsReport == "REPORT") .Where(x => req.refIds.Select(x => x.refId).Contains(x.Id.ToString())) .ToListAsync(); - // Task #224 ปรับให้เป็น process ที่ควรบันทึกตามลำดับ - data.ForEach(profile => profile.IsReport = "DONE"); - await _context.SaveChangesAsync(); + var data1 = await _context.DisciplineDisciplinary_ProfileComplaintInvestigates + .Include(x => x.DisciplineDisciplinary) + // .Where(x => x.IsReport == "REPORT") + .Where(x => req.refIds.Select(x => x.refId).Contains(x.Id.ToString())) + .ToListAsync(); string? _null = null; var resultData = (from p in data @@ -2469,29 +2635,6 @@ namespace BMA.EHR.DisciplineResult.Service.Controllers posNoAbb = p.child4ShortName != null ? p.child4ShortName : (p.child3ShortName != null ? p.child3ShortName : (p.child2ShortName != null ? p.child2ShortName : (p.child1ShortName != null ? p.child1ShortName : (p.rootShortName != null ? p.rootShortName : "")))), }).ToList(); - var baseAPIOrg = _configuration["API"]; - var apiUrlOrg = $"{baseAPIOrg}/org/command/excexute/salary-leave-discipline"; - using (var client = new HttpClient()) - { - client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token.Replace("Bearer ", "")); - client.DefaultRequestHeaders.Add("api-key", _configuration["API_KEY"]); - var _res = await client.PostAsJsonAsync(apiUrlOrg, new - { - data = resultData, - }); - //// var _result = await _res.Content.ReadAsStringAsync(); - //// if (_res.IsSuccessStatusCode) - //// { - //// data.ForEach(profile => profile.IsReport = "DONE"); - //// await _context.SaveChangesAsync(); - //// } - } - - var data1 = await _context.DisciplineDisciplinary_ProfileComplaintInvestigates - .Include(x => x.DisciplineDisciplinary) - // .Where(x => x.IsReport == "REPORT") - .Where(x => req.refIds.Select(x => x.refId).Contains(x.Id.ToString())) - .ToListAsync(); var resultData1 = (from p in data1 join r in req.refIds on p.Id.ToString() equals r.refId @@ -2525,23 +2668,69 @@ namespace BMA.EHR.DisciplineResult.Service.Controllers posNo = p.posMasterNo != null ? p.posMasterNo.ToString() : null, posNoAbb = p.child4ShortName != null ? p.child4ShortName : (p.child3ShortName != null ? p.child3ShortName : (p.child2ShortName != null ? p.child2ShortName : (p.child1ShortName != null ? p.child1ShortName : (p.rootShortName != null ? p.rootShortName : "")))), }).ToList(); - using (var client = new HttpClient()) - { - client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token.Replace("Bearer ", "")); - client.DefaultRequestHeaders.Add("api-key", _configuration["API_KEY"]); - var _res = await client.PostAsJsonAsync(apiUrlOrg, new - { - data = resultData1, - }); - var _result = await _res.Content.ReadAsStringAsync(); - if (_res.IsSuccessStatusCode) - { - data1.ForEach(profile => profile.IsReport = "DONE"); - await _context.SaveChangesAsync(); - } - } - return Success(); + #region Old: Circular Flow + // var baseAPIOrg = _configuration["API"]; + // var apiUrlOrg = $"{baseAPIOrg}/org/command/excexute/salary-leave-discipline"; + // using (var client = new HttpClient()) + // { + // client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token.Replace("Bearer ", "")); + // client.DefaultRequestHeaders.Add("api-key", _configuration["API_KEY"]); + // var _res = await client.PostAsJsonAsync(apiUrlOrg, new + // { + // data = resultData, + // }); + // var _result = await _res.Content.ReadAsStringAsync(); + // if (_res.IsSuccessStatusCode) + // { + // data.ForEach(profile => profile.IsReport = "DONE"); + // await _context.SaveChangesAsync(); + // } + // } + // using (var client = new HttpClient()) + // { + // client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token.Replace("Bearer ", "")); + // client.DefaultRequestHeaders.Add("api-key", _configuration["API_KEY"]); + // var _res = await client.PostAsJsonAsync(apiUrlOrg, new + // { + // data = resultData1, + // }); + // var _result = await _res.Content.ReadAsStringAsync(); + // if (_res.IsSuccessStatusCode) + // { + // data1.ForEach(profile => profile.IsReport = "DONE"); + // await _context.SaveChangesAsync(); + // } + // } + #endregion + + // New: Linear Flow + var now = DateTime.Now; + data.ForEach(profile => + { + profile.IsReport = "DONE"; + profile.LastUpdateFullName = FullName ?? "System Administrator"; + profile.LastUpdateUserId = UserId ?? ""; + profile.LastUpdatedAt = now; + }); + data1.ForEach(profile => + { + profile.IsReport = "DONE"; + profile.LastUpdateFullName = FullName ?? "System Administrator"; + profile.LastUpdateUserId = UserId ?? ""; + profile.LastUpdatedAt = now; + }); + await _context.SaveChangesAsync(); + + // Return resultData for Node to process directly (Linear Flow) + // + // NOTE สำหรับฝั่ง Node (C-PM-32 เท่านั้น): + // - response.result เป็น object { data, data1 } (ไม่ใช่ array เหมือนคำสั่งอื่น) + // data = รายการฝั่ง "การสอบสวน" → ยิง POST {API}/org/command/excexute/salary-leave-discipline ครั้งที่ 1 + // data1 = รายการฝั่ง "การพิจารณาลงโทษ" → ยิง POST {API}/org/command/excexute/salary-leave-discipline ครั้งที่ 2 + // - ต้องยิง 2 ครั้งตามลำดับ data ก่อน → data1 (ห้าม merge รวมในครั้งเดียว เพราะ profileId อาจซ้ำข้าม 2 track) + // - คำสั่งอื่น (C-PM-19/20/25/26/27/28/29/30/31) response.result เป็น array → ยิง org 1 ครั้ง + return Success(new { data = resultData, data1 = resultData1 }); } /// diff --git a/BMA.EHR.Placement.Service/Controllers/PlacementAppointmentController.cs b/BMA.EHR.Placement.Service/Controllers/PlacementAppointmentController.cs index 50114759..fe2b519a 100644 --- a/BMA.EHR.Placement.Service/Controllers/PlacementAppointmentController.cs +++ b/BMA.EHR.Placement.Service/Controllers/PlacementAppointmentController.cs @@ -1062,7 +1062,14 @@ namespace BMA.EHR.Placement.Service.Controllers #endregion // New: Linear Flow - Task #224 ปรับให้เป็น process ที่ควรบันทึกตามลำดับ - data.ForEach(profile => profile.Status = "DONE"); + var now = DateTime.Now; + data.ForEach(profile => + { + profile.Status = "DONE"; + profile.LastUpdateFullName = FullName ?? "System Administrator"; + profile.LastUpdateUserId = UserId ?? ""; + profile.LastUpdatedAt = now; + }); await _context.SaveChangesAsync(); // Return resultData for Node to process directly (Linear Flow) @@ -1286,7 +1293,14 @@ namespace BMA.EHR.Placement.Service.Controllers #endregion // New: Linear Flow - Task #224 ปรับให้เป็น process ที่ควรบันทึกตามลำดับ - data.ForEach(profile => profile.Status = "DONE"); + var now = DateTime.Now; + data.ForEach(profile => + { + profile.Status = "DONE"; + profile.LastUpdateFullName = FullName ?? "System Administrator"; + profile.LastUpdateUserId = UserId ?? ""; + profile.LastUpdatedAt = now; + }); await _context.SaveChangesAsync(); // Return resultData for Node to process directly (Linear Flow) @@ -1494,7 +1508,14 @@ namespace BMA.EHR.Placement.Service.Controllers #endregion // New: Linear Flow - Task #224 ปรับให้เป็น process ที่ควรบันทึกตามลำดับ - data.ForEach(profile => profile.Status = "DONE"); + var now = DateTime.Now; + data.ForEach(profile => + { + profile.Status = "DONE"; + profile.LastUpdateFullName = FullName ?? "System Administrator"; + profile.LastUpdateUserId = UserId ?? ""; + profile.LastUpdatedAt = now; + }); await _context.SaveChangesAsync(); // Return resultData for Node to process directly (Linear Flow) @@ -1707,7 +1728,14 @@ namespace BMA.EHR.Placement.Service.Controllers #endregion // New: Linear Flow - Task #224 ปรับให้เป็น process ที่ควรบันทึกตามลำดับ - data.ForEach(profile => profile.Status = "DONE"); + var now = DateTime.Now; + data.ForEach(profile => + { + profile.Status = "DONE"; + profile.LastUpdateFullName = FullName ?? "System Administrator"; + profile.LastUpdateUserId = UserId ?? ""; + profile.LastUpdatedAt = now; + }); await _context.SaveChangesAsync(); // Return resultData for Node to process directly (Linear Flow) @@ -1943,7 +1971,14 @@ namespace BMA.EHR.Placement.Service.Controllers #endregion // New: Linear Flow - Task #224 ปรับให้เป็น process ที่ควรบันทึกตามลำดับ - data.ForEach(profile => profile.Status = "DONE"); + var now = DateTime.Now; + data.ForEach(profile => + { + profile.Status = "DONE"; + profile.LastUpdateFullName = FullName ?? "System Administrator"; + profile.LastUpdateUserId = UserId ?? ""; + profile.LastUpdatedAt = now; + }); await _context.SaveChangesAsync(); // Return resultData for Node to process directly (Linear Flow) @@ -2039,7 +2074,7 @@ namespace BMA.EHR.Placement.Service.Controllers } /// - /// ออกคำสั่ง C-PM-47 + /// ออกคำสั่ง C-PM-47 โปรดเกล้าฯ แต่งตั้งให้ดำรงตำแหน่ง /// /// /// @@ -2114,7 +2149,14 @@ namespace BMA.EHR.Placement.Service.Controllers #endregion // New: Linear Flow - Task #224 ปรับให้เป็น process ที่ควรบันทึกตามลำดับ - data.ForEach(profile => profile.Status = "DONE"); + var now = DateTime.Now; + data.ForEach(profile => + { + profile.Status = "DONE"; + profile.LastUpdateFullName = FullName ?? "System Administrator"; + profile.LastUpdateUserId = UserId ?? ""; + profile.LastUpdatedAt = now; + }); await _context.SaveChangesAsync(); // Return resultData for Node to process directly (Linear Flow) diff --git a/BMA.EHR.Placement.Service/Controllers/PlacementController.cs b/BMA.EHR.Placement.Service/Controllers/PlacementController.cs index 9557ae00..af1d954e 100644 --- a/BMA.EHR.Placement.Service/Controllers/PlacementController.cs +++ b/BMA.EHR.Placement.Service/Controllers/PlacementController.cs @@ -2032,9 +2032,13 @@ namespace BMA.EHR.Placement.Service.Controllers #endregion // New: Linear Flow + var now = DateTime.Now; placementProfile.ForEach(profile => { profile.PlacementStatus = "DONE"; + profile.LastUpdateFullName = FullName ?? "System Administrator"; + profile.LastUpdateUserId = UserId ?? ""; + profile.LastUpdatedAt = now; if (req.refIds.Length > 0) { profile.commandId = req.refIds[0].commandId; @@ -2438,9 +2442,13 @@ namespace BMA.EHR.Placement.Service.Controllers #endregion // New: Linear Flow + var now = DateTime.Now; placementProfile.ForEach(profile => { profile.PlacementStatus = "DONE"; + profile.LastUpdateFullName = FullName ?? "System Administrator"; + profile.LastUpdateUserId = UserId ?? ""; + profile.LastUpdatedAt = now; if (req.refIds.Length > 0) { profile.commandId = req.refIds[0].commandId; @@ -2710,7 +2718,14 @@ namespace BMA.EHR.Placement.Service.Controllers #endregion // New: Linear Flow - Task #224 ปรับให้เป็น process ที่ควรบันทึกตามลำดับ - data.ForEach(profile => profile.PlacementStatus = "DONE"); + var now = DateTime.Now; + data.ForEach(profile => + { + profile.PlacementStatus = "DONE"; + profile.LastUpdateFullName = FullName ?? "System Administrator"; + profile.LastUpdateUserId = UserId ?? ""; + profile.LastUpdatedAt = now; + }); await _context.SaveChangesAsync(); // Return resultData for Node to process directly (Linear Flow) @@ -2960,7 +2975,14 @@ namespace BMA.EHR.Placement.Service.Controllers #endregion // New: Linear Flow - Task #224 ปรับให้เป็น process ที่ควรบันทึกตามลำดับ - data.ForEach(profile => profile.PlacementStatus = "DONE"); + var now = DateTime.Now; + data.ForEach(profile => + { + profile.PlacementStatus = "DONE"; + profile.LastUpdateFullName = FullName ?? "System Administrator"; + profile.LastUpdateUserId = UserId ?? ""; + profile.LastUpdatedAt = now; + }); await _context.SaveChangesAsync(); // Return resultData for Node to process directly (Linear Flow) @@ -3193,7 +3215,14 @@ namespace BMA.EHR.Placement.Service.Controllers #endregion // New: Linear Flow - Task #224 ปรับให้เป็น process ที่ควรบันทึกตามลำดับ - data.ForEach(profile => profile.PlacementStatus = "DONE"); + var now = DateTime.Now; + data.ForEach(profile => + { + profile.PlacementStatus = "DONE"; + profile.LastUpdateFullName = FullName ?? "System Administrator"; + profile.LastUpdateUserId = UserId ?? ""; + profile.LastUpdatedAt = now; + }); await _context.SaveChangesAsync(); // Return resultData for Node to process directly (Linear Flow) diff --git a/BMA.EHR.Placement.Service/Controllers/PlacementOfficerController.cs b/BMA.EHR.Placement.Service/Controllers/PlacementOfficerController.cs index 8fcb51cf..29d38df5 100644 --- a/BMA.EHR.Placement.Service/Controllers/PlacementOfficerController.cs +++ b/BMA.EHR.Placement.Service/Controllers/PlacementOfficerController.cs @@ -828,10 +828,14 @@ namespace BMA.EHR.Placement.Service.Controllers // New: Linear Flow - Task #224 ปรับให้เป็น process ที่ควรบันทึกตามลำดับ var firstRef = req.refIds.FirstOrDefault(); var commandNoText = firstRef != null ? $"{firstRef.commandNo}/{firstRef.commandYear.ToThaiYear()}" : null; + var now = DateTime.Now; data.ForEach(profile => { profile.Status = "DONE"; profile.commandNo = commandNoText; + profile.LastUpdateFullName = FullName ?? "System Administrator"; + profile.LastUpdateUserId = UserId ?? ""; + profile.LastUpdatedAt = now; }); await _context.SaveChangesAsync(); diff --git a/BMA.EHR.Placement.Service/Controllers/PlacementReceiveController.cs b/BMA.EHR.Placement.Service/Controllers/PlacementReceiveController.cs index 32f91eae..31db814b 100644 --- a/BMA.EHR.Placement.Service/Controllers/PlacementReceiveController.cs +++ b/BMA.EHR.Placement.Service/Controllers/PlacementReceiveController.cs @@ -1306,7 +1306,14 @@ namespace BMA.EHR.Placement.Service.Controllers #endregion // New: Linear Flow - data.ForEach(profile => profile.Status = "DONE"); + var now = DateTime.Now; + data.ForEach(profile => + { + profile.Status = "DONE"; + profile.LastUpdateFullName = FullName ?? "System Administrator"; + profile.LastUpdateUserId = UserId ?? ""; + profile.LastUpdatedAt = now; + }); Console.WriteLine($"[ReceiveReportExcecute] Saving changes to database for {data.Count} profiles"); await _context.SaveChangesAsync(); Console.WriteLine($"[ReceiveReportExcecute] Process completed successfully at {DateTime.Now}"); diff --git a/BMA.EHR.Placement.Service/Controllers/PlacementRepatriationController.cs b/BMA.EHR.Placement.Service/Controllers/PlacementRepatriationController.cs index b56fc804..2cc8536d 100644 --- a/BMA.EHR.Placement.Service/Controllers/PlacementRepatriationController.cs +++ b/BMA.EHR.Placement.Service/Controllers/PlacementRepatriationController.cs @@ -686,7 +686,14 @@ namespace BMA.EHR.Placement.Service.Controllers #endregion // New: Linear Flow - Task #224ปรับให้เป็น process ที่ควรบันทึกตามลำดับ - data.ForEach(profile => profile.Status = "DONE"); + var now = DateTime.Now; + data.ForEach(profile => + { + profile.Status = "DONE"; + profile.LastUpdateFullName = FullName ?? "System Administrator"; + profile.LastUpdateUserId = UserId ?? ""; + profile.LastUpdatedAt = now; + }); await _context.SaveChangesAsync(); // Return resultData for Node to process directly (Linear Flow) diff --git a/BMA.EHR.Placement.Service/Controllers/PlacementTransferController.cs b/BMA.EHR.Placement.Service/Controllers/PlacementTransferController.cs index 73741918..a165be68 100644 --- a/BMA.EHR.Placement.Service/Controllers/PlacementTransferController.cs +++ b/BMA.EHR.Placement.Service/Controllers/PlacementTransferController.cs @@ -1212,7 +1212,14 @@ namespace BMA.EHR.Placement.Service.Controllers #endregion // New: Linear Flow - Task #224 ปรับให้เป็น process ที่ควรบันทึกตามลำดับ - data.ForEach(profile => profile.Status = "DONE"); + var now = DateTime.Now; + data.ForEach(profile => + { + profile.Status = "DONE"; + profile.LastUpdateFullName = FullName ?? "System Administrator"; + profile.LastUpdateUserId = UserId ?? ""; + profile.LastUpdatedAt = now; + }); // Return resultData for Node to process directly (Linear Flow) return Success(resultData); diff --git a/BMA.EHR.Retirement.Service/Controllers/RetirementOtherController.cs b/BMA.EHR.Retirement.Service/Controllers/RetirementOtherController.cs index 868aac4f..b1580cf5 100644 --- a/BMA.EHR.Retirement.Service/Controllers/RetirementOtherController.cs +++ b/BMA.EHR.Retirement.Service/Controllers/RetirementOtherController.cs @@ -926,7 +926,14 @@ namespace BMA.EHR.Retirement.Service.Controllers #endregion // New: Linear Flow - Task #224 ปรับให้เป็น process ที่ควรบันทึกตามลำดับ - data.ForEach(profile => profile.Status = "DONE"); + var now = DateTime.Now; + data.ForEach(profile => + { + profile.Status = "DONE"; + profile.LastUpdateFullName = FullName ?? "System Administrator"; + profile.LastUpdateUserId = UserId ?? ""; + profile.LastUpdatedAt = now; + }); await _context.SaveChangesAsync(); // Return resultData for Node to process directly (Linear Flow) @@ -1183,7 +1190,14 @@ namespace BMA.EHR.Retirement.Service.Controllers #endregion // New: Linear Flow - Task #224 ปรับให้เป็น process ที่ควรบันทึกตามลำดับ - data.ForEach(profile => profile.Status = "DONE"); + var now = DateTime.Now; + data.ForEach(profile => + { + profile.Status = "DONE"; + profile.LastUpdateFullName = FullName ?? "System Administrator"; + profile.LastUpdateUserId = UserId ?? ""; + profile.LastUpdatedAt = now; + }); await _context.SaveChangesAsync(); // Return resultData for Node to process directly (Linear Flow) diff --git a/BMA.EHR.Retirement.Service/Controllers/RetirementOutController.cs b/BMA.EHR.Retirement.Service/Controllers/RetirementOutController.cs index ae528316..4ef0e395 100644 --- a/BMA.EHR.Retirement.Service/Controllers/RetirementOutController.cs +++ b/BMA.EHR.Retirement.Service/Controllers/RetirementOutController.cs @@ -629,7 +629,7 @@ namespace BMA.EHR.Retirement.Service.Controllers } /// - /// ออกคำสั่ง C-PM-18 คำสั่งให้ออกจากราชการ + /// ออกคำสั่ง C-PM-18 ให้ออกจากราชการ && C-PM-43 ให้ลูกจ้างออกจากราชการ /// /// /// @@ -712,7 +712,14 @@ namespace BMA.EHR.Retirement.Service.Controllers #endregion // New: Linear Flow - Task #224 ปรับให้เป็น process ที่ควรบันทึกตามลำดับ - data.ForEach(profile => profile.Status = "DONE"); + var now = DateTime.Now; + data.ForEach(profile => + { + profile.Status = "DONE"; + profile.LastUpdateFullName = FullName ?? "System Administrator"; + profile.LastUpdateUserId = UserId ?? ""; + profile.LastUpdatedAt = now; + }); await _context.SaveChangesAsync(); // Return resultData for Node to process directly (Linear Flow) diff --git a/BMA.EHR.Retirement.Service/Controllers/RetirementResignController.cs b/BMA.EHR.Retirement.Service/Controllers/RetirementResignController.cs index 1b434380..019451c5 100644 --- a/BMA.EHR.Retirement.Service/Controllers/RetirementResignController.cs +++ b/BMA.EHR.Retirement.Service/Controllers/RetirementResignController.cs @@ -2972,7 +2972,14 @@ namespace BMA.EHR.Retirement.Service.Controllers #endregion // New: Linear Flow - Task #224 ปรับให้เป็น process ที่ควรบันทึกตามลำดับ - data.ForEach(profile => profile.Status = "DONE"); + var now = DateTime.Now; + data.ForEach(profile => + { + profile.Status = "DONE"; + profile.LastUpdateFullName = FullName ?? "System Administrator"; + profile.LastUpdateUserId = UserId ?? ""; + profile.LastUpdatedAt = now; + }); await _context.SaveChangesAsync(); // Return resultData for Node to process directly (Linear Flow) @@ -3164,7 +3171,14 @@ namespace BMA.EHR.Retirement.Service.Controllers #endregion // New: Linear Flow - Task #224 ปรับให้เป็น process ที่ควรบันทึกตามลำดับ - data.ForEach(profile => profile.Status = "DONE"); + var now = DateTime.Now; + data.ForEach(profile => + { + profile.Status = "DONE"; + profile.LastUpdateFullName = FullName ?? "System Administrator"; + profile.LastUpdateUserId = UserId ?? ""; + profile.LastUpdatedAt = now; + }); await _context.SaveChangesAsync(); // Return resultData for Node to process directly (Linear Flow) @@ -3399,7 +3413,14 @@ namespace BMA.EHR.Retirement.Service.Controllers #endregion // New: Linear Flow - Task #224 ปรับให้เป็น process ที่ควรบันทึกตามลำดับ - data.ForEach(profile => profile.Status = "DONE"); + var now = DateTime.Now; + data.ForEach(profile => + { + profile.Status = "DONE"; + profile.LastUpdateFullName = FullName ?? "System Administrator"; + profile.LastUpdateUserId = UserId ?? ""; + profile.LastUpdatedAt = now; + }); data.ForEach(profile => profile.RetirementResign.Status = "CANCEL"); await _context.SaveChangesAsync(); diff --git a/BMA.EHR.Retirement.Service/Controllers/RetirementResignEmployeeController.cs b/BMA.EHR.Retirement.Service/Controllers/RetirementResignEmployeeController.cs index 4449099b..dd0d850b 100644 --- a/BMA.EHR.Retirement.Service/Controllers/RetirementResignEmployeeController.cs +++ b/BMA.EHR.Retirement.Service/Controllers/RetirementResignEmployeeController.cs @@ -2468,7 +2468,14 @@ namespace BMA.EHR.Retirement.Service.Controllers #endregion // New: Linear Flow - Task #224 ปรับให้เป็น process ที่ควรบันทึกตามลำดับ - data.ForEach(profile => profile.Status = "DONE"); + var now = DateTime.Now; + data.ForEach(profile => + { + profile.Status = "DONE"; + profile.LastUpdateFullName = FullName ?? "System Administrator"; + profile.LastUpdateUserId = UserId ?? ""; + profile.LastUpdatedAt = now; + }); await _context.SaveChangesAsync(); // Return resultData for Node to process directly (Linear Flow) @@ -2700,7 +2707,14 @@ namespace BMA.EHR.Retirement.Service.Controllers #endregion // New: Linear Flow - Task #224 ปรับให้เป็น process ที่ควรบันทึกตามลำดับ - data.ForEach(profile => profile.Status = "DONE"); + var now = DateTime.Now; + data.ForEach(profile => + { + profile.Status = "DONE"; + profile.LastUpdateFullName = FullName ?? "System Administrator"; + profile.LastUpdateUserId = UserId ?? ""; + profile.LastUpdatedAt = now; + }); data.ForEach(profile => profile.RetirementResignEmployee.Status = "CANCEL"); await _context.SaveChangesAsync();