Merge branch 'develop' into working
This commit is contained in:
commit
c65a4a04ac
13 changed files with 769 additions and 32 deletions
|
|
@ -192,7 +192,7 @@ namespace BMA.EHR.Application.Repositories.Reports
|
||||||
}).ToList();
|
}).ToList();
|
||||||
}
|
}
|
||||||
string SignDate = retireHistorys.SignDate != null ? DateTime.Parse(retireHistorys.SignDate.ToString()).ToThaiFullDate().ToString().ToThaiNumber() : "-";
|
string SignDate = retireHistorys.SignDate != null ? DateTime.Parse(retireHistorys.SignDate.ToString()).ToThaiFullDate().ToString().ToThaiNumber() : "-";
|
||||||
return new { SignDate, retireHistorys.Detail, retireHistorys.Id, retireHistorys.CreatedAt, Year = retireHistorys.Year.ToThaiYear().ToString().ToThaiNumber(), retireHistorys.Round, retireHistorys.Type, retireHistorys.TypeReport, Total = retireHistorys.Total.ToString().ToThaiNumber(), profiles = mapProfiles };
|
return new { SignDate, Detail = retireHistorys.Detail.ToThaiNumber(), retireHistorys.Id, retireHistorys.CreatedAt, Year = retireHistorys.Year.ToThaiYear().ToString().ToThaiNumber(), retireHistorys.Round, retireHistorys.Type, retireHistorys.TypeReport, Total = retireHistorys.Total.ToString().ToThaiNumber(), profiles = mapProfiles };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
@ -312,7 +312,7 @@ namespace BMA.EHR.Application.Repositories.Reports
|
||||||
root = (isDuplicateRoot ? "" : profile.root + "\n") +
|
root = (isDuplicateRoot ? "" : profile.root + "\n") +
|
||||||
(isDuplicateHospital || !hospital.ToObject<List<string>>().Contains(profile.child1) ? "" : profile.child1 + "\n") +
|
(isDuplicateHospital || !hospital.ToObject<List<string>>().Contains(profile.child1) ? "" : profile.child1 + "\n") +
|
||||||
(isDuplicatePosType ? "" : $"ตำแหน่งประเภท{profile.posTypeName}" + "\n") +
|
(isDuplicatePosType ? "" : $"ตำแหน่งประเภท{profile.posTypeName}" + "\n") +
|
||||||
(isDuplicatePosLevel ? "" : $"ระดับ{profile.posLevelName}"),
|
(isDuplicatePosLevel ? "" : $"ระดับ{profile.posLevelName}").ToThaiNumber(),
|
||||||
child = (profile.posExecutiveName == null ? "" : profile.posExecutiveName + "\n") +
|
child = (profile.posExecutiveName == null ? "" : profile.posExecutiveName + "\n") +
|
||||||
(profile.child4 == null ? "" : profile.child4 + "\n") +
|
(profile.child4 == null ? "" : profile.child4 + "\n") +
|
||||||
(profile.child3 == null ? "" : profile.child3 + "\n") +
|
(profile.child3 == null ? "" : profile.child3 + "\n") +
|
||||||
|
|
@ -326,7 +326,7 @@ namespace BMA.EHR.Application.Repositories.Reports
|
||||||
}).ToList();
|
}).ToList();
|
||||||
}
|
}
|
||||||
string SignDate = retire.SignDate != null ? DateTime.Parse(retire.SignDate.ToString()).ToThaiFullDate().ToString().ToThaiNumber() : "-";
|
string SignDate = retire.SignDate != null ? DateTime.Parse(retire.SignDate.ToString()).ToThaiFullDate().ToString().ToThaiNumber() : "-";
|
||||||
return new { SignDate, retire.Detail, retire.Id, retire.CreatedAt, Year = retire.Year.ToThaiYear().ToString().ToThaiNumber(), retire.Round, retire.Type, retire.TypeReport, Total = profile_retire.Count.ToString().ToThaiNumber(), profiles = mapProfiles };
|
return new { SignDate, Detail = retire.Detail.ToThaiNumber(), retire.Id, retire.CreatedAt, Year = retire.Year.ToThaiYear().ToString().ToThaiNumber(), retire.Round, retire.Type, retire.TypeReport, Total = profile_retire.Count.ToString().ToThaiNumber(), profiles = mapProfiles };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
|
||||||
|
|
@ -3246,23 +3246,25 @@ namespace BMA.EHR.Leave.Service.Controllers
|
||||||
}
|
}
|
||||||
else if (actRole == "BROTHER")
|
else if (actRole == "BROTHER")
|
||||||
{
|
{
|
||||||
actNodeId = act.child3DnaId != null ?
|
actNodeId = act.child4DnaId != null ?
|
||||||
act.child3DnaId.Value.ToString("D") :
|
act.child3DnaId.Value.ToString("D") :
|
||||||
act.child2DnaId != null ?
|
act.child3DnaId != null ?
|
||||||
act.child2DnaId.Value.ToString("D") :
|
act.child2DnaId.Value.ToString("D") :
|
||||||
|
act.child2DnaId != null ?
|
||||||
|
act.child1DnaId!.Value.ToString("D") :
|
||||||
act.child1DnaId != null ?
|
act.child1DnaId != null ?
|
||||||
act.rootDnaId!.Value.ToString("D") :
|
act.rootDnaId.Value.ToString("D") :
|
||||||
act.rootDnaId != null ?
|
act.rootDnaId != null ?
|
||||||
act.rootDnaId.Value.ToString("D") :
|
act.rootDnaId.Value.ToString("D") :
|
||||||
"";
|
"";
|
||||||
actNode = act.child4DnaId != null ?
|
actNode = act.child4DnaId != null ?
|
||||||
4 :
|
4 :
|
||||||
act.child3DnaId != null ?
|
act.child3DnaId != null ?
|
||||||
4 :
|
|
||||||
act.child2DnaId != null ?
|
|
||||||
3 :
|
3 :
|
||||||
act.child1DnaId != null ?
|
act.child2DnaId != null ?
|
||||||
2 :
|
2 :
|
||||||
|
act.child1DnaId != null ?
|
||||||
|
1 :
|
||||||
act.rootDnaId != null ?
|
act.rootDnaId != null ?
|
||||||
0 :
|
0 :
|
||||||
null;
|
null;
|
||||||
|
|
|
||||||
|
|
@ -1439,23 +1439,25 @@ namespace BMA.EHR.Leave.Service.Controllers
|
||||||
}
|
}
|
||||||
else if (actRole == "BROTHER")
|
else if (actRole == "BROTHER")
|
||||||
{
|
{
|
||||||
actNodeId = act.child3DnaId != null ?
|
actNodeId = act.child4DnaId != null ?
|
||||||
act.child3DnaId.Value.ToString("D") :
|
act.child3DnaId.Value.ToString("D") :
|
||||||
act.child2DnaId != null ?
|
act.child3DnaId != null ?
|
||||||
act.child2DnaId.Value.ToString("D") :
|
act.child2DnaId.Value.ToString("D") :
|
||||||
|
act.child2DnaId != null ?
|
||||||
|
act.child1DnaId!.Value.ToString("D") :
|
||||||
act.child1DnaId != null ?
|
act.child1DnaId != null ?
|
||||||
act.rootDnaId!.Value.ToString("D") :
|
act.rootDnaId.Value.ToString("D") :
|
||||||
act.rootDnaId != null ?
|
act.rootDnaId != null ?
|
||||||
act.rootDnaId.Value.ToString("D") :
|
act.rootDnaId.Value.ToString("D") :
|
||||||
"";
|
"";
|
||||||
actNode = act.child4DnaId != null ?
|
actNode = act.child4DnaId != null ?
|
||||||
4 :
|
4 :
|
||||||
act.child3DnaId != null ?
|
act.child3DnaId != null ?
|
||||||
4 :
|
|
||||||
act.child2DnaId != null ?
|
|
||||||
3 :
|
3 :
|
||||||
act.child1DnaId != null ?
|
act.child2DnaId != null ?
|
||||||
2 :
|
2 :
|
||||||
|
act.child1DnaId != null ?
|
||||||
|
1 :
|
||||||
act.rootDnaId != null ?
|
act.rootDnaId != null ?
|
||||||
0 :
|
0 :
|
||||||
null;
|
null;
|
||||||
|
|
@ -1917,23 +1919,25 @@ namespace BMA.EHR.Leave.Service.Controllers
|
||||||
}
|
}
|
||||||
else if (actRole == "BROTHER")
|
else if (actRole == "BROTHER")
|
||||||
{
|
{
|
||||||
actNodeId = act.child3DnaId != null ?
|
actNodeId = act.child4DnaId != null ?
|
||||||
act.child3DnaId.Value.ToString("D") :
|
act.child3DnaId.Value.ToString("D") :
|
||||||
act.child2DnaId != null ?
|
act.child3DnaId != null ?
|
||||||
act.child2DnaId.Value.ToString("D") :
|
act.child2DnaId.Value.ToString("D") :
|
||||||
|
act.child2DnaId != null ?
|
||||||
|
act.child1DnaId!.Value.ToString("D") :
|
||||||
act.child1DnaId != null ?
|
act.child1DnaId != null ?
|
||||||
act.rootDnaId!.Value.ToString("D") :
|
act.rootDnaId.Value.ToString("D") :
|
||||||
act.rootDnaId != null ?
|
act.rootDnaId != null ?
|
||||||
act.rootDnaId.Value.ToString("D") :
|
act.rootDnaId.Value.ToString("D") :
|
||||||
"";
|
"";
|
||||||
actNode = act.child4DnaId != null ?
|
actNode = act.child4DnaId != null ?
|
||||||
4 :
|
4 :
|
||||||
act.child3DnaId != null ?
|
act.child3DnaId != null ?
|
||||||
4 :
|
|
||||||
act.child2DnaId != null ?
|
|
||||||
3 :
|
3 :
|
||||||
act.child1DnaId != null ?
|
act.child2DnaId != null ?
|
||||||
2 :
|
2 :
|
||||||
|
act.child1DnaId != null ?
|
||||||
|
1 :
|
||||||
act.rootDnaId != null ?
|
act.rootDnaId != null ?
|
||||||
0 :
|
0 :
|
||||||
null;
|
null;
|
||||||
|
|
@ -2220,23 +2224,25 @@ namespace BMA.EHR.Leave.Service.Controllers
|
||||||
}
|
}
|
||||||
else if (actRole == "BROTHER")
|
else if (actRole == "BROTHER")
|
||||||
{
|
{
|
||||||
actNodeId = act.child3DnaId != null ?
|
actNodeId = act.child4DnaId != null ?
|
||||||
act.child3DnaId.Value.ToString("D") :
|
act.child3DnaId.Value.ToString("D") :
|
||||||
act.child2DnaId != null ?
|
act.child3DnaId != null ?
|
||||||
act.child2DnaId.Value.ToString("D") :
|
act.child2DnaId.Value.ToString("D") :
|
||||||
|
act.child2DnaId != null ?
|
||||||
|
act.child1DnaId!.Value.ToString("D") :
|
||||||
act.child1DnaId != null ?
|
act.child1DnaId != null ?
|
||||||
act.rootDnaId!.Value.ToString("D") :
|
act.rootDnaId.Value.ToString("D") :
|
||||||
act.rootDnaId != null ?
|
act.rootDnaId != null ?
|
||||||
act.rootDnaId.Value.ToString("D") :
|
act.rootDnaId.Value.ToString("D") :
|
||||||
"";
|
"";
|
||||||
actNode = act.child4DnaId != null ?
|
actNode = act.child4DnaId != null ?
|
||||||
4 :
|
4 :
|
||||||
act.child3DnaId != null ?
|
act.child3DnaId != null ?
|
||||||
4 :
|
|
||||||
act.child2DnaId != null ?
|
|
||||||
3 :
|
3 :
|
||||||
act.child1DnaId != null ?
|
act.child2DnaId != null ?
|
||||||
2 :
|
2 :
|
||||||
|
act.child1DnaId != null ?
|
||||||
|
1 :
|
||||||
act.rootDnaId != null ?
|
act.rootDnaId != null ?
|
||||||
0 :
|
0 :
|
||||||
null;
|
null;
|
||||||
|
|
|
||||||
|
|
@ -40,10 +40,17 @@
|
||||||
<PackageReference Include="Serilog.Sinks.Elasticsearch" Version="9.0.3" />
|
<PackageReference Include="Serilog.Sinks.Elasticsearch" Version="9.0.3" />
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="6.5.0" />
|
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="6.5.0" />
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
|
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
|
||||||
|
<PackageReference Include="DocumentFormat.OpenXml" Version="2.20.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\BMA.EHR.Infrastructure\BMA.EHR.Infrastructure.csproj" />
|
<ProjectReference Include="..\BMA.EHR.Infrastructure\BMA.EHR.Infrastructure.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Content Include="Templates\**\*.docx">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</Content>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ using BMA.EHR.Domain.Models.Retirement;
|
||||||
using BMA.EHR.Domain.Shared;
|
using BMA.EHR.Domain.Shared;
|
||||||
using BMA.EHR.Infrastructure.Persistence;
|
using BMA.EHR.Infrastructure.Persistence;
|
||||||
using BMA.EHR.Retirement.Service.Requests;
|
using BMA.EHR.Retirement.Service.Requests;
|
||||||
|
using BMA.EHR.Retirement.Service.Services;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
@ -37,6 +38,7 @@ namespace BMA.EHR.Retirement.Service.Controllers
|
||||||
private readonly PermissionRepository _permission;
|
private readonly PermissionRepository _permission;
|
||||||
private readonly DisciplineDbContext _contextDiscipline;
|
private readonly DisciplineDbContext _contextDiscipline;
|
||||||
private readonly RetireReportRepository _service;
|
private readonly RetireReportRepository _service;
|
||||||
|
private readonly RetirementReportService _reportService;
|
||||||
public RetirementController(RetirementRepository repository,
|
public RetirementController(RetirementRepository repository,
|
||||||
NotificationRepository repositoryNoti,
|
NotificationRepository repositoryNoti,
|
||||||
ApplicationDBContext context,
|
ApplicationDBContext context,
|
||||||
|
|
@ -46,7 +48,8 @@ namespace BMA.EHR.Retirement.Service.Controllers
|
||||||
IHttpContextAccessor httpContextAccessor,
|
IHttpContextAccessor httpContextAccessor,
|
||||||
PermissionRepository permission,
|
PermissionRepository permission,
|
||||||
DisciplineDbContext contextDiscipline,
|
DisciplineDbContext contextDiscipline,
|
||||||
RetireReportRepository service)
|
RetireReportRepository service,
|
||||||
|
RetirementReportService reportService)
|
||||||
{
|
{
|
||||||
_repository = repository;
|
_repository = repository;
|
||||||
_repositoryNoti = repositoryNoti;
|
_repositoryNoti = repositoryNoti;
|
||||||
|
|
@ -58,6 +61,7 @@ namespace BMA.EHR.Retirement.Service.Controllers
|
||||||
_permission = permission;
|
_permission = permission;
|
||||||
_contextDiscipline = contextDiscipline;
|
_contextDiscipline = contextDiscipline;
|
||||||
_service = service;
|
_service = service;
|
||||||
|
_reportService = reportService;
|
||||||
}
|
}
|
||||||
|
|
||||||
#region " Properties "
|
#region " Properties "
|
||||||
|
|
@ -2213,5 +2217,83 @@ namespace BMA.EHR.Retirement.Service.Controllers
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region รายงานรายชื่อผู้เกษียณอายุราชการ ข้าราชการ & ลูกจ้างประจำ
|
||||||
|
/// <summary>
|
||||||
|
/// รายงานรายชื่อผู้เกษียณอายุราชการ ข้าราชการ & ลูกจ้างประจำ
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="Id">Id ของรอบเกษียณ</param>
|
||||||
|
/// <param name="exportType">pdf, docx</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
/// <response code="200">เมื่อทำการอ่านข้อมูลจาก Relational Database สำเร็จ</response>
|
||||||
|
/// <response code="401">ไม่ได้ Login เข้าระบบ</response>
|
||||||
|
/// <response code="500">เมื่อเกิดข้อผิดพลาดในการทำงาน</response>
|
||||||
|
[HttpGet("report/{exportType}/{Id}")]
|
||||||
|
public async Task<ActionResult<ResponseObject>> GetReportProfileRetirement([FromRoute] Guid Id, string exportType = "pdf")
|
||||||
|
{
|
||||||
|
var retire = await _service.GetProfileRetirementdAsync(Id, token);
|
||||||
|
if (retire != null)
|
||||||
|
{
|
||||||
|
var reportfile = string.Empty;
|
||||||
|
exportType = exportType.Trim();
|
||||||
|
|
||||||
|
switch (retire.GetType().GetProperty("Type").GetValue(retire))
|
||||||
|
{
|
||||||
|
case "OFFICER":
|
||||||
|
if (string.IsNullOrEmpty(retire.GetType().GetProperty("TypeReport").GetValue(retire)))
|
||||||
|
{
|
||||||
|
reportfile = $"retire-1";
|
||||||
|
}
|
||||||
|
else if (retire.GetType().GetProperty("TypeReport").GetValue(retire) == "ADD" || retire.GetType().GetProperty("TypeReport").GetValue(retire) == "EDIT")
|
||||||
|
{
|
||||||
|
reportfile = $"retire-2";
|
||||||
|
}
|
||||||
|
else if (retire.GetType().GetProperty("TypeReport").GetValue(retire) == "REMOVE")
|
||||||
|
{
|
||||||
|
reportfile = $"retire-3";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return Error(retire.GetType().GetProperty("TypeReport").GetValue(retire));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "EMPLOYEE":
|
||||||
|
if (string.IsNullOrEmpty(retire.GetType().GetProperty("TypeReport").GetValue(retire)))
|
||||||
|
{
|
||||||
|
reportfile = $"retire-emp-1";
|
||||||
|
}
|
||||||
|
else if (retire.GetType().GetProperty("TypeReport").GetValue(retire) == "ADD" || retire.GetType().GetProperty("TypeReport").GetValue(retire) == "EDIT")
|
||||||
|
{
|
||||||
|
reportfile = $"retire-emp-2";
|
||||||
|
}
|
||||||
|
else if (retire.GetType().GetProperty("TypeReport").GetValue(retire) == "REMOVE")
|
||||||
|
{
|
||||||
|
reportfile = $"retire-emp-3";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return Error(retire.GetType().GetProperty("TypeReport").GetValue(retire));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return Error(retire.GetType().GetProperty("Type").GetValue(retire));
|
||||||
|
}
|
||||||
|
|
||||||
|
var reportBytes = await _reportService.GenerateReportAsync(reportfile, retire, exportType);
|
||||||
|
|
||||||
|
var fileName = $"reportRetirement-{DateTime.Now:yyyyMMdd-HHmmss}.{exportType}";
|
||||||
|
var contentType = exportType.Trim().ToLower() == "pdf"
|
||||||
|
? "application/pdf"
|
||||||
|
: "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
|
||||||
|
|
||||||
|
return File(reportBytes, contentType, fileName);
|
||||||
|
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return NotFound();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ using BMA.EHR.Domain.Middlewares;
|
||||||
using BMA.EHR.Infrastructure;
|
using BMA.EHR.Infrastructure;
|
||||||
using BMA.EHR.Infrastructure.Persistence;
|
using BMA.EHR.Infrastructure.Persistence;
|
||||||
using BMA.EHR.Retirement.Service;
|
using BMA.EHR.Retirement.Service;
|
||||||
|
using BMA.EHR.Retirement.Service.Services;
|
||||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.AspNetCore.Mvc.ApiExplorer;
|
using Microsoft.AspNetCore.Mvc.ApiExplorer;
|
||||||
|
|
@ -86,6 +87,7 @@ var builder = WebApplication.CreateBuilder(args);
|
||||||
builder.Services.AddApplication();
|
builder.Services.AddApplication();
|
||||||
builder.Services.AddLeaveApplication();
|
builder.Services.AddLeaveApplication();
|
||||||
builder.Services.AddPersistence(builder.Configuration);
|
builder.Services.AddPersistence(builder.Configuration);
|
||||||
|
builder.Services.AddScoped<RetirementReportService>();
|
||||||
builder.Services.AddLeavePersistence(builder.Configuration);
|
builder.Services.AddLeavePersistence(builder.Configuration);
|
||||||
|
|
||||||
builder.Services.AddHttpClient();
|
builder.Services.AddHttpClient();
|
||||||
|
|
|
||||||
638
BMA.EHR.Retirement.Service/Services/RetirementReportService.cs
Normal file
638
BMA.EHR.Retirement.Service/Services/RetirementReportService.cs
Normal file
|
|
@ -0,0 +1,638 @@
|
||||||
|
using BMA.EHR.Application.Responses;
|
||||||
|
using DocumentFormat.OpenXml.Packaging;
|
||||||
|
using DocumentFormat.OpenXml.Wordprocessing;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace BMA.EHR.Retirement.Service.Services
|
||||||
|
{
|
||||||
|
public class RetirementReportService
|
||||||
|
{
|
||||||
|
private readonly IWebHostEnvironment _environment;
|
||||||
|
private readonly ILogger<RetirementReportService> _logger;
|
||||||
|
private readonly IConfiguration _configuration;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the RetirementReportService class.
|
||||||
|
/// </summary>
|
||||||
|
public RetirementReportService(
|
||||||
|
IWebHostEnvironment environment,
|
||||||
|
ILogger<RetirementReportService> logger,
|
||||||
|
IConfiguration configuration)
|
||||||
|
{
|
||||||
|
_environment = environment;
|
||||||
|
_logger = logger;
|
||||||
|
_configuration = configuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Public Methods
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// สร้างรายงานจาก Template (.docx)
|
||||||
|
/// </summary>
|
||||||
|
public async Task<byte[]> GenerateReportAsync(string templateName, dynamic data, string exportType)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var templatePath = GetTemplatePath(templateName);
|
||||||
|
var docxBytes = await ProcessTemplateAsync(templatePath, data);
|
||||||
|
|
||||||
|
return exportType.ToLower() == "pdf"
|
||||||
|
? await ConvertToPdfAsync(docxBytes)
|
||||||
|
: docxBytes;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error generating report");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Template Processing
|
||||||
|
|
||||||
|
private string GetTemplatePath(string templateName)
|
||||||
|
{
|
||||||
|
var path = Path.Combine(_environment.ContentRootPath, "Templates", $"{templateName}.docx");
|
||||||
|
if (!File.Exists(path))
|
||||||
|
throw new FileNotFoundException($"Template not found: {templateName}");
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<byte[]> ProcessTemplateAsync(string templatePath, dynamic data)
|
||||||
|
{
|
||||||
|
using var templateStream = File.OpenRead(templatePath);
|
||||||
|
using var outputStream = new MemoryStream();
|
||||||
|
await templateStream.CopyToAsync(outputStream);
|
||||||
|
outputStream.Position = 0;
|
||||||
|
|
||||||
|
using (var wordDoc = WordprocessingDocument.Open(outputStream, true))
|
||||||
|
{
|
||||||
|
var mainPart = wordDoc.MainDocumentPart;
|
||||||
|
if (mainPart == null) return Array.Empty<byte>();
|
||||||
|
|
||||||
|
ReplacePlaceholders(mainPart, data);
|
||||||
|
wordDoc.Save();
|
||||||
|
}
|
||||||
|
|
||||||
|
return outputStream.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ReplacePlaceholders(MainDocumentPart mainPart, dynamic data)
|
||||||
|
{
|
||||||
|
var document = mainPart.Document;
|
||||||
|
if (document == null) return;
|
||||||
|
|
||||||
|
var processor = CreateDataProcessor(data);
|
||||||
|
processor.Process(document, new Action<Document, System.Collections.IEnumerable>(FillTableRows));
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Data Processing Strategy
|
||||||
|
|
||||||
|
private IDataProcessor CreateDataProcessor(dynamic data)
|
||||||
|
{
|
||||||
|
var dataType = data.GetType();
|
||||||
|
var isDictionary = dataType.IsGenericType &&
|
||||||
|
dataType.GetGenericTypeDefinition() == typeof(Dictionary<,>);
|
||||||
|
|
||||||
|
return isDictionary
|
||||||
|
? new DictionaryDataProcessor(data)
|
||||||
|
: new ObjectDataProcessor(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Table Processing
|
||||||
|
|
||||||
|
private void FillTableRows(Document document, System.Collections.IEnumerable profiles)
|
||||||
|
{
|
||||||
|
var table = document.Descendants<Table>().FirstOrDefault();
|
||||||
|
if (table == null) return;
|
||||||
|
|
||||||
|
var rows = table.Elements<TableRow>().ToList();
|
||||||
|
if (rows.Count == 0) return;
|
||||||
|
|
||||||
|
var strategy = CreateTableStrategy(rows);
|
||||||
|
strategy.Process(table, rows, profiles);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ITableStrategy CreateTableStrategy(List<TableRow> rows)
|
||||||
|
{
|
||||||
|
// retire-1 format: 1 row, 1 cell, 1 paragraph
|
||||||
|
if (IsSingleParagraphFormat(rows))
|
||||||
|
return new SingleParagraphTableStrategy();
|
||||||
|
|
||||||
|
// retire-3 format: 2+ rows (header + template)
|
||||||
|
return new MultiRowTableStrategy();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsSingleParagraphFormat(List<TableRow> rows) =>
|
||||||
|
rows.Count == 1 &&
|
||||||
|
rows[0].Elements<TableCell>().Count() == 1 &&
|
||||||
|
rows[0].Elements<TableCell>().First().Elements<Paragraph>().Count() == 1;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region PDF Conversion
|
||||||
|
|
||||||
|
private async Task<byte[]> ConvertToPdfAsync(byte[] docxBytes)
|
||||||
|
{
|
||||||
|
var tempDocx = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.docx");
|
||||||
|
var tempPdf = Path.ChangeExtension(tempDocx, ".pdf");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await File.WriteAllBytesAsync(tempDocx, docxBytes);
|
||||||
|
await ConvertToPdfInternalAsync(tempDocx, tempPdf);
|
||||||
|
return await File.ReadAllBytesAsync(tempPdf);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (File.Exists(tempDocx)) File.Delete(tempDocx);
|
||||||
|
if (File.Exists(tempPdf)) File.Delete(tempPdf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ConvertToPdfInternalAsync(string docxPath, string pdfPath)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var useDocker = _configuration.GetValue<bool>("LibreOffice:UseDocker", false);
|
||||||
|
var timeout = _configuration.GetValue<int>("LibreOffice:Timeout", 180000);
|
||||||
|
|
||||||
|
if (useDocker)
|
||||||
|
{
|
||||||
|
await ConvertToPdfViaDockerAsync(docxPath, pdfPath, timeout);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// // PROD: Disabled local LibreOffice conversion
|
||||||
|
// await ConvertToPdfLocallyAsync(docxPath, pdfPath, timeout);
|
||||||
|
throw new NotSupportedException("LibreOffice conversion is disabled.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error converting to PDF");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ConvertToPdfViaDockerAsync(string docxPath, string pdfPath, int timeout)
|
||||||
|
{
|
||||||
|
var inputDir = _configuration["LibreOffice:InputDirectory"] ?? "/app/libreoffice/input";
|
||||||
|
var outputDir = _configuration["LibreOffice:OutputDirectory"] ?? "/app/libreoffice/output";
|
||||||
|
var fileName = Path.GetFileName(docxPath);
|
||||||
|
var pdfName = Path.ChangeExtension(fileName, ".pdf");
|
||||||
|
|
||||||
|
// Ensure directories exist
|
||||||
|
Directory.CreateDirectory(inputDir);
|
||||||
|
Directory.CreateDirectory(outputDir);
|
||||||
|
|
||||||
|
// Copy file to input folder (LibreOffice watcher will pick it up)
|
||||||
|
var inputPath = Path.Combine(inputDir, fileName).Replace('\\', '/');
|
||||||
|
var outputPath = Path.Combine(outputDir, pdfName).Replace('\\', '/');
|
||||||
|
|
||||||
|
_logger.LogInformation("📤 Sending file to LibreOffice: {FileName}", fileName);
|
||||||
|
await File.WriteAllBytesAsync(inputPath, await File.ReadAllBytesAsync(docxPath));
|
||||||
|
|
||||||
|
// Wait for LibreOffice to convert (file watcher handles it)
|
||||||
|
var stopwatch = System.Diagnostics.Stopwatch.StartNew();
|
||||||
|
var pollInterval = TimeSpan.FromMilliseconds(500);
|
||||||
|
|
||||||
|
while (stopwatch.ElapsedMilliseconds < timeout)
|
||||||
|
{
|
||||||
|
if (File.Exists(outputPath))
|
||||||
|
{
|
||||||
|
_logger.LogInformation("✅ PDF received: {PdfName} (took {ElapsedMs}ms)", pdfName, stopwatch.ElapsedMilliseconds);
|
||||||
|
|
||||||
|
await File.WriteAllBytesAsync(pdfPath, await File.ReadAllBytesAsync(outputPath));
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (File.Exists(outputPath)) File.Delete(outputPath);
|
||||||
|
_logger.LogDebug("🗑️ Cleaned up output file: {PdfName}", pdfName);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(ex, "Failed to cleanup output file");
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await Task.Delay(pollInterval);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new TimeoutException($"LibreOffice conversion timed out after {timeout}ms. File not found: {outputPath}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// // PROD: Disabled local LibreOffice conversion
|
||||||
|
// private async Task ConvertToPdfLocallyAsync(string docxPath, string pdfPath, int timeout)
|
||||||
|
// {
|
||||||
|
// var libreOfficePath = _configuration["LibreOffice:Path"] ?? GetDefaultLibreOfficePath();
|
||||||
|
// var arguments = _configuration["LibreOffice:Arguments"] ?? "--headless --convert-to pdf --nologo --norestore";
|
||||||
|
// var outputDir = Path.GetDirectoryName(pdfPath);
|
||||||
|
|
||||||
|
// if (string.IsNullOrEmpty(outputDir))
|
||||||
|
// {
|
||||||
|
// throw new DirectoryNotFoundException("Output directory cannot be determined");
|
||||||
|
// }
|
||||||
|
|
||||||
|
// var psi = new ProcessStartInfo
|
||||||
|
// {
|
||||||
|
// FileName = libreOfficePath,
|
||||||
|
// Arguments = $"{arguments} --outdir \"{outputDir}\" \"{docxPath}\"",
|
||||||
|
// UseShellExecute = false,
|
||||||
|
// RedirectStandardOutput = true,
|
||||||
|
// RedirectStandardError = true,
|
||||||
|
// CreateNoWindow = true
|
||||||
|
// };
|
||||||
|
|
||||||
|
// using var process = Process.Start(psi);
|
||||||
|
// var exited = process.WaitForExit(timeout);
|
||||||
|
|
||||||
|
// if (!exited)
|
||||||
|
// {
|
||||||
|
// process.Kill(entireProcessTree: true);
|
||||||
|
// throw new TimeoutException($"LibreOffice conversion timed out after {timeout}ms");
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if (process.ExitCode != 0)
|
||||||
|
// {
|
||||||
|
// var error = await process.StandardError.ReadToEndAsync();
|
||||||
|
// throw new Exception($"LibreOffice conversion failed: {error}");
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // PROD: Disabled local LibreOffice path detection
|
||||||
|
// private static string GetDefaultLibreOfficePath()
|
||||||
|
// {
|
||||||
|
// if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||||
|
// {
|
||||||
|
// var possiblePaths = new[]
|
||||||
|
// {
|
||||||
|
// @"C:\Program Files\LibreOffice\program\soffice.exe",
|
||||||
|
// @"C:\Program Files (x86)\LibreOffice\program\soffice.exe",
|
||||||
|
// @"C:\Program Files\LibreOffice\program\soffice.com"
|
||||||
|
// };
|
||||||
|
|
||||||
|
// return possiblePaths.FirstOrDefault(File.Exists)
|
||||||
|
// ?? throw new FileNotFoundException("LibreOffice not found. Please install LibreOffice or configure the path in appsettings.json");
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Linux/Docker: use default path
|
||||||
|
// return "libreoffice";
|
||||||
|
// }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Data Processor Interfaces & Implementations
|
||||||
|
|
||||||
|
internal interface IDataProcessor
|
||||||
|
{
|
||||||
|
void Process(Document document, Action<Document, System.Collections.IEnumerable> tableFiller);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class DictionaryDataProcessor : IDataProcessor
|
||||||
|
{
|
||||||
|
private readonly dynamic _data;
|
||||||
|
|
||||||
|
public DictionaryDataProcessor(dynamic data)
|
||||||
|
{
|
||||||
|
_data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Process(Document document, Action<Document, System.Collections.IEnumerable> tableFiller)
|
||||||
|
{
|
||||||
|
var keys = _data.Keys as System.Collections.ICollection;
|
||||||
|
if (keys == null) return;
|
||||||
|
|
||||||
|
System.Collections.IEnumerable? profiles = null;
|
||||||
|
|
||||||
|
foreach (string key in keys)
|
||||||
|
{
|
||||||
|
if (key.Equals("profiles", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
profiles = _data[key] as System.Collections.IEnumerable;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var valueObj = _data[key];
|
||||||
|
if (valueObj != null && typeof(System.Collections.IEnumerable).IsAssignableFrom(valueObj.GetType()) &&
|
||||||
|
valueObj.GetType() != typeof(string))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var value = valueObj?.ToString() ?? string.Empty;
|
||||||
|
var placeholder = $"{{{{{key}}}}}";
|
||||||
|
TextReplacer.ReplaceAll(document, placeholder, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (profiles != null)
|
||||||
|
{
|
||||||
|
tableFiller(document, profiles);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class ObjectDataProcessor : IDataProcessor
|
||||||
|
{
|
||||||
|
private readonly dynamic _data;
|
||||||
|
|
||||||
|
public ObjectDataProcessor(dynamic data)
|
||||||
|
{
|
||||||
|
_data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Process(Document document, Action<Document, System.Collections.IEnumerable> tableFiller)
|
||||||
|
{
|
||||||
|
var dataType = _data.GetType();
|
||||||
|
var allProps = dataType.GetProperties();
|
||||||
|
var validProps = new List<PropertyInfo>();
|
||||||
|
|
||||||
|
foreach (var p in allProps)
|
||||||
|
{
|
||||||
|
if (p.GetIndexParameters().Length == 0)
|
||||||
|
{
|
||||||
|
validProps.Add(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
System.Collections.IEnumerable? profiles = null;
|
||||||
|
|
||||||
|
foreach (var prop in validProps)
|
||||||
|
{
|
||||||
|
var propType = prop.PropertyType;
|
||||||
|
bool isEnumerable = typeof(System.Collections.IEnumerable).IsAssignableFrom(propType);
|
||||||
|
bool isString = propType == typeof(string);
|
||||||
|
|
||||||
|
if (isEnumerable && !isString)
|
||||||
|
{
|
||||||
|
if (prop.Name.Equals("profiles", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
profiles = prop.GetValue(_data) as System.Collections.IEnumerable;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var value = prop.GetValue(_data)?.ToString() ?? string.Empty;
|
||||||
|
var placeholder = $"{{{{{prop.Name}}}}}";
|
||||||
|
TextReplacer.ReplaceAll(document, placeholder, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (profiles != null)
|
||||||
|
{
|
||||||
|
tableFiller(document, profiles);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Text Replacer
|
||||||
|
|
||||||
|
internal static class TextReplacer
|
||||||
|
{
|
||||||
|
public static void ReplaceAll(Document document, string oldValue, string newValue)
|
||||||
|
{
|
||||||
|
bool found = false;
|
||||||
|
|
||||||
|
// Method 1: Check within single Run
|
||||||
|
foreach (var run in document.Descendants<Run>())
|
||||||
|
{
|
||||||
|
var textElements = run.Elements<Text>().ToList();
|
||||||
|
if (textElements.Count == 0) continue;
|
||||||
|
|
||||||
|
var combinedText = string.Concat(textElements.Select(t => t.Text));
|
||||||
|
if (combinedText.Contains(oldValue))
|
||||||
|
{
|
||||||
|
found = true;
|
||||||
|
var replacedText = combinedText.Replace(oldValue, newValue);
|
||||||
|
textElements[0].Text = replacedText;
|
||||||
|
for (int i = 1; i < textElements.Count; i++)
|
||||||
|
{
|
||||||
|
textElements[i].Text = string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Method 2: Check across all Runs in Paragraph
|
||||||
|
foreach (var para in document.Descendants<Paragraph>())
|
||||||
|
{
|
||||||
|
var allRuns = para.Elements<Run>().ToList();
|
||||||
|
if (allRuns.Count == 0) continue;
|
||||||
|
|
||||||
|
var combinedParaText = string.Concat(allRuns.SelectMany(r => r.Elements<Text>().Select(t => t.Text)));
|
||||||
|
if (combinedParaText.Contains(oldValue))
|
||||||
|
{
|
||||||
|
found = true;
|
||||||
|
var replacedText = combinedParaText.Replace(oldValue, newValue);
|
||||||
|
|
||||||
|
var firstRunTexts = allRuns[0].Elements<Text>().ToList();
|
||||||
|
if (firstRunTexts.Count > 0)
|
||||||
|
{
|
||||||
|
firstRunTexts[0].Text = replacedText;
|
||||||
|
for (int i = 1; i < firstRunTexts.Count; i++)
|
||||||
|
{
|
||||||
|
firstRunTexts[i].Text = string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 1; i < allRuns.Count; i++)
|
||||||
|
{
|
||||||
|
foreach (var t in allRuns[i].Elements<Text>())
|
||||||
|
{
|
||||||
|
t.Text = string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: Check individual Text elements
|
||||||
|
if (!found)
|
||||||
|
{
|
||||||
|
foreach (var text in document.Descendants<Text>())
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(text.Text) && text.Text.Contains(oldValue))
|
||||||
|
{
|
||||||
|
found = true;
|
||||||
|
text.Text = text.Text.Replace(oldValue, newValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ReplaceInRow(TableRow row, string oldValue, string newValue)
|
||||||
|
{
|
||||||
|
bool found = false;
|
||||||
|
|
||||||
|
foreach (var cell in row.Descendants<TableCell>())
|
||||||
|
{
|
||||||
|
foreach (var para in cell.Elements<Paragraph>())
|
||||||
|
{
|
||||||
|
found = ReplaceInParagraph(para, oldValue, newValue) || found;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: Check individual Text elements
|
||||||
|
if (!found)
|
||||||
|
{
|
||||||
|
foreach (var text in row.Descendants<Text>())
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(text.Text) && text.Text.Contains(oldValue))
|
||||||
|
{
|
||||||
|
found = true;
|
||||||
|
text.Text = text.Text.Replace(oldValue, newValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool ReplaceInParagraph(Paragraph paragraph, string oldValue, string newValue)
|
||||||
|
{
|
||||||
|
bool found = false;
|
||||||
|
|
||||||
|
var allTexts = paragraph.Descendants<Text>().ToList();
|
||||||
|
if (allTexts.Count == 0) return false;
|
||||||
|
|
||||||
|
var combinedParaText = string.Concat(allTexts.Select(t => t.Text));
|
||||||
|
|
||||||
|
if (combinedParaText.Contains(oldValue))
|
||||||
|
{
|
||||||
|
found = true;
|
||||||
|
var replacedText = combinedParaText.Replace(oldValue, newValue);
|
||||||
|
allTexts[0].Text = replacedText;
|
||||||
|
|
||||||
|
for (int i = 1; i < allTexts.Count; i++)
|
||||||
|
{
|
||||||
|
allTexts[i].Text = string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: Check individual Text elements
|
||||||
|
if (!found)
|
||||||
|
{
|
||||||
|
foreach (var text in allTexts)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(text.Text) && text.Text.Contains(oldValue))
|
||||||
|
{
|
||||||
|
found = true;
|
||||||
|
text.Text = text.Text.Replace(oldValue, newValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Table Strategy Interfaces & Implementations
|
||||||
|
|
||||||
|
internal interface ITableStrategy
|
||||||
|
{
|
||||||
|
void Process(Table table, List<TableRow> rows, System.Collections.IEnumerable profiles);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class SingleParagraphTableStrategy : ITableStrategy
|
||||||
|
{
|
||||||
|
public void Process(Table table, List<TableRow> rows, System.Collections.IEnumerable profiles)
|
||||||
|
{
|
||||||
|
var cell = rows[0].Elements<TableCell>().First();
|
||||||
|
var templatePara = cell.Elements<Paragraph>().First();
|
||||||
|
|
||||||
|
var profileList = profiles.Cast<object>().ToList();
|
||||||
|
|
||||||
|
foreach (var profile in profileList)
|
||||||
|
{
|
||||||
|
var props = profile.GetType()
|
||||||
|
.GetProperties()
|
||||||
|
.Where(p => p.GetIndexParameters().Length == 0)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
var newPara = (Paragraph)templatePara.CloneNode(true);
|
||||||
|
|
||||||
|
foreach (var prop in props)
|
||||||
|
{
|
||||||
|
var value = prop.GetValue(profile)?.ToString() ?? string.Empty;
|
||||||
|
var placeholder = $"{{{{{prop.Name}}}}}";
|
||||||
|
TextReplacer.ReplaceInParagraph(newPara, placeholder, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
cell.Append(newPara);
|
||||||
|
}
|
||||||
|
|
||||||
|
templatePara.Remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class MultiRowTableStrategy : ITableStrategy
|
||||||
|
{
|
||||||
|
public void Process(Table table, List<TableRow> rows, System.Collections.IEnumerable profiles)
|
||||||
|
{
|
||||||
|
var templateRowIndex = rows.Count >= 2 ? 1 : 0;
|
||||||
|
var templateRow = rows[templateRowIndex];
|
||||||
|
templateRow.Remove();
|
||||||
|
|
||||||
|
var profileList = profiles.Cast<object>().ToList();
|
||||||
|
|
||||||
|
// Process header row if exists
|
||||||
|
if (rows.Count >= 2)
|
||||||
|
{
|
||||||
|
ProcessHeaderRow(rows[0], profileList);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process template rows
|
||||||
|
foreach (var profile in profileList)
|
||||||
|
{
|
||||||
|
var newRow = (TableRow)templateRow.CloneNode(true);
|
||||||
|
var props = profile.GetType()
|
||||||
|
.GetProperties()
|
||||||
|
.Where(p => p.GetIndexParameters().Length == 0)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
foreach (var prop in props)
|
||||||
|
{
|
||||||
|
var value = prop.GetValue(profile)?.ToString() ?? string.Empty;
|
||||||
|
var placeholder = $"{{{{{prop.Name}}}}}";
|
||||||
|
TextReplacer.ReplaceInRow(newRow, placeholder, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
table.AppendChild(newRow);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ProcessHeaderRow(TableRow headerRow, List<object> profileList)
|
||||||
|
{
|
||||||
|
var firstProfile = profileList.FirstOrDefault();
|
||||||
|
if (firstProfile == null) return;
|
||||||
|
|
||||||
|
var props = firstProfile.GetType()
|
||||||
|
.GetProperties()
|
||||||
|
.Where(p => p.GetIndexParameters().Length == 0)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
foreach (var prop in props)
|
||||||
|
{
|
||||||
|
var value = prop.GetValue(firstProfile)?.ToString() ?? string.Empty;
|
||||||
|
var placeholder = $"{{{{{prop.Name}}}}}";
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(value))
|
||||||
|
{
|
||||||
|
TextReplacer.ReplaceInRow(headerRow, placeholder, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
BIN
BMA.EHR.Retirement.Service/Templates/retire-1.docx
Normal file
BIN
BMA.EHR.Retirement.Service/Templates/retire-1.docx
Normal file
Binary file not shown.
BIN
BMA.EHR.Retirement.Service/Templates/retire-2.docx
Normal file
BIN
BMA.EHR.Retirement.Service/Templates/retire-2.docx
Normal file
Binary file not shown.
BIN
BMA.EHR.Retirement.Service/Templates/retire-3.docx
Normal file
BIN
BMA.EHR.Retirement.Service/Templates/retire-3.docx
Normal file
Binary file not shown.
BIN
BMA.EHR.Retirement.Service/Templates/retire-emp-1.docx
Normal file
BIN
BMA.EHR.Retirement.Service/Templates/retire-emp-1.docx
Normal file
Binary file not shown.
BIN
BMA.EHR.Retirement.Service/Templates/retire-emp-2.docx
Normal file
BIN
BMA.EHR.Retirement.Service/Templates/retire-emp-2.docx
Normal file
Binary file not shown.
BIN
BMA.EHR.Retirement.Service/Templates/retire-emp-3.docx
Normal file
BIN
BMA.EHR.Retirement.Service/Templates/retire-emp-3.docx
Normal file
Binary file not shown.
Loading…
Add table
Add a link
Reference in a new issue