From f5d4038417853c126fbaefbaa8ad73c57a84060d Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Sun, 20 Aug 2023 19:29:03 +0700 Subject: [PATCH] Report V2 --- .github/workflows/release_report.yaml | 87 +++++++++++ .../ApplicationServicesRegistration.cs | 1 + .../Commands/CommandReportRepository.cs | 92 +++++++++++ .../Controllers/CommandReportController.cs | 145 +++++++++++++++++- .../01-คำสั่งบรรจุและแต่งตั้งผู้สอบแข่งขันได้-2.trdp | Bin 3277 -> 2941 bytes 5 files changed, 319 insertions(+), 6 deletions(-) create mode 100644 .github/workflows/release_report.yaml create mode 100644 BMA.EHR.Application/Repositories/Commands/CommandReportRepository.cs diff --git a/.github/workflows/release_report.yaml b/.github/workflows/release_report.yaml new file mode 100644 index 00000000..39bdf8c0 --- /dev/null +++ b/.github/workflows/release_report.yaml @@ -0,0 +1,87 @@ +name: release-dev +run-name: release-dev ${{ github.actor }} +on: + # push: + # tags: + # - 'v[0-9]+.[0-9]+.[0-9]+' + # tags-ignore: + # - '2.*' + # Allow run workflow manually from Action tab + workflow_dispatch: +env: + REGISTRY: docker.frappet.com + IMAGE_NAME: ehr/bma-ehr-report-v2-service + DEPLOY_HOST: frappet.com + COMPOSE_PATH: /home/frappet/docker/bma-ehr-report-v2 + TOKEN_LINE: uxuK5hDzS2DsoC5piJBrWRLiz8GgY7iMZZldOWsDDF0 + +jobs: + # act workflow_dispatch -W .github/workflows/release_report.yaml --input IMAGE_VER=test-v6.1 -s DOCKER_USER=sorawit -s DOCKER_PASS=P@ssword -s SSH_PASSWORD=P@ssw0rd + release-dev: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + # skip Set up QEMU because it fail on act and container + - name: Gen Version + id: gen_ver + run: | + if [[ $GITHUB_REF == 'refs/tags/'* ]]; then + IMAGE_VER='${GITHUB_REF/refs\/tags\//}' + 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: Test 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 load local docker image + uses: docker/build-push-action@v3 + with: + context: . + platforms: linux/amd64 + file: BMA.EHR.Command.Service/Dockerfile + 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: 22 + script: | + cd "${{env.COMPOSE_PATH}}" + docker-compose pull + docker-compose up -d + echo "${{ steps.gen_ver.outputs.image_ver }}"> success + - uses: snow-actions/line-notify@v1.1.0 + if: success() + with: + access_token: ${{ env.TOKEN_LINE }} + message: | + -Success✅✅✅ + Image: ${{env.IMAGE_NAME}} + Version: ${{ github.event.inputs.IMAGE_VER }} + By: ${{secrets.DOCKER_USER}} + - uses: snow-actions/line-notify@v1.1.0 + if: failure() + with: + access_token: ${{ env.TOKEN_LINE }} + message: | + -Failure❌❌❌ + Image: ${{env.IMAGE_NAME}} + Version: ${{ github.event.inputs.IMAGE_VER }} + By: ${{secrets.DOCKER_USER}} diff --git a/BMA.EHR.Application/ApplicationServicesRegistration.cs b/BMA.EHR.Application/ApplicationServicesRegistration.cs index 502acb37..fefe5d9e 100644 --- a/BMA.EHR.Application/ApplicationServicesRegistration.cs +++ b/BMA.EHR.Application/ApplicationServicesRegistration.cs @@ -23,6 +23,7 @@ namespace BMA.EHR.Application services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); return services; } diff --git a/BMA.EHR.Application/Repositories/Commands/CommandReportRepository.cs b/BMA.EHR.Application/Repositories/Commands/CommandReportRepository.cs new file mode 100644 index 00000000..a956fec8 --- /dev/null +++ b/BMA.EHR.Application/Repositories/Commands/CommandReportRepository.cs @@ -0,0 +1,92 @@ +using BMA.EHR.Application.Common.Interfaces; +using BMA.EHR.Domain.Models.Commands.Core; +using BMA.EHR.Domain.Shared; +using Microsoft.AspNetCore.Http; +using Microsoft.EntityFrameworkCore; +using System; +using System.Collections.Generic; +using System.ComponentModel.Design; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BMA.EHR.Application.Repositories.Commands +{ + public class CommandReportRepository : GenericRepository + { + #region " Fields " + + private readonly IApplicationDBContext _dbContext; + private readonly IHttpContextAccessor _httpContextAccessor; + private readonly OrganizationCommonRepository _organizationCommonRepository; + private readonly UserProfileRepository _userProfileRepository; + + #endregion + + #region " Constructor and Destuctor " + + public CommandReportRepository(IApplicationDBContext dbContext, + IHttpContextAccessor httpContextAccessor, + OrganizationCommonRepository organizationCommonRepository, + UserProfileRepository userProfileRepository) : base(dbContext, httpContextAccessor) + { + _dbContext = dbContext; + _httpContextAccessor = httpContextAccessor; + _organizationCommonRepository = organizationCommonRepository; + _userProfileRepository = userProfileRepository; + } + + #endregion + + #region " Properties " + + protected Guid UserOrganizationId + { + get + { + if (UserId != null || UserId != "") + return _userProfileRepository.GetUserOCId(Guid.Parse(UserId!)); + else + return Guid.Empty; + } + } + + #endregion + + #region " Methods " + + public async Task> GetCommandType01AttachmentAsync(Guid id) + { + try + { + var raw_data = await _dbContext.Set() + .Include(c => c.Command) + .Where(c => c.Command.Id == id) + .ToListAsync(); + if (raw_data == null) + { + throw new Exception(GlobalMessages.CommandNotFound); + } + + var ret = new List(); + + foreach (var c in raw_data) + { + ret.Add(new + { + FullName = $"{c.Prefix}{c.FirstName} {c.LastName}", + PositionName = "" + }); + } + + return ret; + } + catch + { + throw; + } + } + + #endregion + } +} diff --git a/BMA.EHR.Report.Service/Controllers/CommandReportController.cs b/BMA.EHR.Report.Service/Controllers/CommandReportController.cs index 06f71c46..458cac2b 100644 --- a/BMA.EHR.Report.Service/Controllers/CommandReportController.cs +++ b/BMA.EHR.Report.Service/Controllers/CommandReportController.cs @@ -9,6 +9,8 @@ using Swashbuckle.AspNetCore.Annotations; using static System.Runtime.InteropServices.JavaScript.JSType; using Telerik.Reporting; using Telerik.Reporting.Processing; +using Microsoft.IdentityModel.Protocols.OpenIdConnect; +using Microsoft.AspNetCore.Mvc.ModelBinding.Binders; namespace BMA.EHR.Report.Service.Controllers { @@ -25,6 +27,7 @@ namespace BMA.EHR.Report.Service.Controllers private readonly CommandRepository _repository; private readonly IWebHostEnvironment _hostingEnvironment; private readonly IConfiguration _configuration; + private readonly CommandReportRepository _commandReportRepository; #endregion @@ -32,11 +35,13 @@ namespace BMA.EHR.Report.Service.Controllers public CommandReportController(CommandRepository repository, IWebHostEnvironment hostingEnvironment, - IConfiguration configuration) + IConfiguration configuration, + CommandReportRepository commandReportRepository) { _repository = repository; _hostingEnvironment = hostingEnvironment; _configuration = configuration; + _commandReportRepository = commandReportRepository; } #endregion @@ -45,7 +50,7 @@ namespace BMA.EHR.Report.Service.Controllers #region " Private " - private async Task GenerateCommandReportType01(Guid commandId, string exportType) + private async Task GenerateCommandReportType01_Cover(Guid commandId, string exportType) { try { @@ -101,11 +106,74 @@ namespace BMA.EHR.Report.Service.Controllers } } + private async Task GenerateCommandReportType01_Attachment(Guid commandId, string exportType) + { + try + { + var command = await _repository.GetByIdAsync(commandId); + if (command == null) + { + throw new Exception(GlobalMessages.CommandNotFound); + } + + var data = await _commandReportRepository.GetCommandType01AttachmentAsync(commandId); + + var rptFile = Path.Combine(_hostingEnvironment.ContentRootPath, "Reports", $"01-คำสั่งบรรจุและแต่งตั้งผู้สอบแข่งขันได้-2.trdp"); + + ReportPackager reportPackager = new ReportPackager(); + Telerik.Reporting.Report? report = null; + using (var sourceStream = System.IO.File.OpenRead(rptFile)) + { + report = (Telerik.Reporting.Report)reportPackager.UnpackageDocument(sourceStream); + } + + var tblData = (Telerik.Reporting.Table)report.Items["detailSection1"].Items["tblData"]; + + tblData.DataSource = data; + + report.ReportParameters["IssuerOrganizationName"].Value = command.IssuerOrganizationName; + report.ReportParameters["CommandNo"].Value = command.CommandNo; + report.ReportParameters["CommandYear"].Value = command.CommandYear.ToInteger().ToThaiYear().ToString(); + report.ReportParameters["CommandExecuteDate"].Value = command.CommandExcecuteDate == null ? "" : command.CommandExcecuteDate.Value.ToThaiFullDate3(); + + System.Collections.Hashtable deviceInfo = new System.Collections.Hashtable(); + + InstanceReportSource instanceReportSource = new InstanceReportSource() + { + ReportDocument = report + }; + + + ReportProcessor reportProcessor = new ReportProcessor(_configuration); + RenderingResult result = reportProcessor.RenderReport(exportType, instanceReportSource, deviceInfo); + + var content = result.DocumentBytes; + + return content; + } + catch + { + throw; + } + } + #endregion - [HttpGet("{exportType}/{id}")] + /// + /// รายงานหน้าคำสั่ง + /// + /// Record Id ของคำสั่ง + /// pdf, docx หรือ xlsx + /// + /// เมื่อทำการอ่านข้อมูลจาก Relational Database สำเร็จ + /// ไม่ได้ Login เข้าระบบ + /// เมื่อเกิดข้อผิดพลาดในการทำงาน + [HttpGet("cover/{exportType}/{id}")] [AllowAnonymous] - public async Task> GetCommandReport(Guid id, string exportType) + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status500InternalServerError)] + public async Task> GetCommandCoverReportAsync(Guid id, string exportType = "pdf") { try { @@ -113,8 +181,73 @@ namespace BMA.EHR.Report.Service.Controllers if (cmd == null) throw new Exception(GlobalMessages.CommandNotFound); - var contentData = await GenerateCommandReportType01(id, exportType); - return File(contentData, "application/pdf", $"command-{cmd.CommandNo}-{cmd.CommandYear.ToInteger().ToThaiYear()}.pdf"); + var mimeType = ""; + switch (exportType.Trim().ToLower()) + { + case "pdf": mimeType = "application/pdf"; break; + case "docx": mimeType = "application/vnd.openxmlformats-officedocument.wordprocessingml.document"; break; + case "xlsx": mimeType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; break; + } + + switch (cmd.CommandType.CommandCode.Trim().ToUpper()) + { + case "C-PM-01": + case "C-PM-02": + case "C-PM-03": + { + var contentData = await GenerateCommandReportType01_Cover(id, exportType); + return File(contentData, mimeType, $"command-{cmd.CommandNo}-{cmd.CommandYear.ToInteger().ToThaiYear()}.{exportType.Trim().ToLower()}"); + } + default: throw new Exception(GlobalMessages.MethodForCommandTypeNotImplement); + } + } + catch + { + throw; + } + } + + /// + /// รายงานเอกสารแนบท้าย + /// + /// Record Id ของคำสั่ง + /// pdf, docx หรือ xlsx + /// + /// เมื่อทำการอ่านข้อมูลจาก Relational Database สำเร็จ + /// ไม่ได้ Login เข้าระบบ + /// เมื่อเกิดข้อผิดพลาดในการทำงาน + [HttpGet("attachment/{exportType}/{id}")] + [AllowAnonymous] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status500InternalServerError)] + public async Task> GetCommandAttachmentReportAsync(Guid id, string exportType = "pdf") + { + try + { + var cmd = await _repository.GetByIdAsync(id); + if (cmd == null) + throw new Exception(GlobalMessages.CommandNotFound); + + var mimeType = ""; + switch (exportType.Trim().ToLower()) + { + case "pdf": mimeType = "application/pdf"; break; + case "docx": mimeType = "application/vnd.openxmlformats-officedocument.wordprocessingml.document"; break; + case "xlsx": mimeType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; break; + } + + switch (cmd.CommandType.CommandCode.Trim().ToUpper()) + { + case "C-PM-01": + case "C-PM-02": + case "C-PM-03": + { + var contentData = await GenerateCommandReportType01_Attachment(id, exportType); + return File(contentData, mimeType, $"command-attachment-{cmd.CommandNo}-{cmd.CommandYear.ToInteger().ToThaiYear()}.{exportType.Trim().ToLower()}"); + } + default: throw new Exception(GlobalMessages.MethodForCommandTypeNotImplement); + } } catch { diff --git a/BMA.EHR.Report.Service/Reports/01-คำสั่งบรรจุและแต่งตั้งผู้สอบแข่งขันได้-2.trdp b/BMA.EHR.Report.Service/Reports/01-คำสั่งบรรจุและแต่งตั้งผู้สอบแข่งขันได้-2.trdp index 5807ce324ea902a0d8f23f84f10ae170ba91c1db..575b3c56d8b6b6df5602f9915b9a97c1642ecec5 100644 GIT binary patch delta 2627 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 l0002C0000J00000000000000000;_`q6{Sl>Iwh=006M27>NJ? delta 2966 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