diff --git a/src/main/java/com/ffii/lioner/modules/lioner/client/service/ClientService.java b/src/main/java/com/ffii/lioner/modules/lioner/client/service/ClientService.java index b969ce5..eb44e40 100644 --- a/src/main/java/com/ffii/lioner/modules/lioner/client/service/ClientService.java +++ b/src/main/java/com/ffii/lioner/modules/lioner/client/service/ClientService.java @@ -189,6 +189,7 @@ public class ClientService extends AbstractBaseEntityService> data = jdbcDao.queryForList(sql, args); diff --git a/src/main/java/com/ffii/lioner/modules/lioner/client/web/ClientController.java b/src/main/java/com/ffii/lioner/modules/lioner/client/web/ClientController.java index abc4fe8..c1a52df 100644 --- a/src/main/java/com/ffii/lioner/modules/lioner/client/web/ClientController.java +++ b/src/main/java/com/ffii/lioner/modules/lioner/client/web/ClientController.java @@ -152,13 +152,19 @@ public class ClientController{ public void exportClientConsultantReport( @RequestParam("fromDate") @DateTimeFormat(pattern = "yyyy-MM-dd") Date fromDate, @RequestParam("toDate") @DateTimeFormat(pattern = "yyyy-MM-dd") Date toDate, + @RequestParam(required = false) List consultantIds, HttpServletResponse response) throws IOException { Map args = new HashMap<>(); - if(fromDate != null) + if (fromDate != null) { args.put("fromDate", fromDate); - if(fromDate != null) - args.put("toDate", DateUtils.addDay(toDate, 1)); + } + if (toDate != null) { + args.put("toDate", DateUtils.addDay(toDate, 1)); + } + if (consultantIds != null && !consultantIds.isEmpty()) { + args.put("consultantIds", consultantIds); + } response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); String fileName = "Client_Consultant_Report.xlsx"; diff --git a/src/main/java/com/ffii/lioner/modules/lioner/pdf/entity/FormSigPage.java b/src/main/java/com/ffii/lioner/modules/lioner/pdf/entity/FormSigPage.java index 5761615..9dc1a95 100644 --- a/src/main/java/com/ffii/lioner/modules/lioner/pdf/entity/FormSigPage.java +++ b/src/main/java/com/ffii/lioner/modules/lioner/pdf/entity/FormSigPage.java @@ -34,10 +34,6 @@ public class FormSigPage extends BaseEntity { @Column(name = "pageTo") private Integer pageTo; - @NotBlank - @Column(name = "action") - private String action; - public String getFormCode() { return formCode; } @@ -77,12 +73,4 @@ public class FormSigPage extends BaseEntity { public void setPageTo(Integer pageTo) { this.pageTo = pageTo; } - - public String getAction() { - return action; - } - - public void setAction(String action) { - this.action = action; - } } diff --git a/src/main/java/com/ffii/lioner/modules/lioner/pdf/req/UpdateFormSigPageReq.java b/src/main/java/com/ffii/lioner/modules/lioner/pdf/req/UpdateFormSigPageReq.java index e891d50..c27bdf0 100644 --- a/src/main/java/com/ffii/lioner/modules/lioner/pdf/req/UpdateFormSigPageReq.java +++ b/src/main/java/com/ffii/lioner/modules/lioner/pdf/req/UpdateFormSigPageReq.java @@ -27,10 +27,6 @@ public class UpdateFormSigPageReq { @NotNull private Integer pageTo; - @NotBlank - @Size(max = 30) - private String action; - public Long getId() { return id; } @@ -78,12 +74,4 @@ public class UpdateFormSigPageReq { public void setPageTo(Integer pageTo) { this.pageTo = pageTo; } - - public String getAction() { - return action; - } - - public void setAction(String action) { - this.action = action; - } } diff --git a/src/main/java/com/ffii/lioner/modules/lioner/pdf/service/FormSigPageService.java b/src/main/java/com/ffii/lioner/modules/lioner/pdf/service/FormSigPageService.java index e57f0af..dcf0e22 100644 --- a/src/main/java/com/ffii/lioner/modules/lioner/pdf/service/FormSigPageService.java +++ b/src/main/java/com/ffii/lioner/modules/lioner/pdf/service/FormSigPageService.java @@ -2,6 +2,7 @@ package com.ffii.lioner.modules.lioner.pdf.service; import java.time.LocalDate; import java.time.LocalDateTime; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -20,6 +21,7 @@ public class FormSigPageService { private static final String UPLOAD1 = "upload1"; private static final String UPLOAD2 = "upload2"; + private static final String UPLOAD3 = "upload3"; private final FormSigPageRepository formSigPageRepository; @@ -54,7 +56,6 @@ public class FormSigPageService { entity.setSigType(req.getSigType()); entity.setPageFrom(req.getPageFrom()); entity.setPageTo(req.getPageTo()); - entity.setAction(req.getAction()); } else { entity = new FormSigPage(); entity.setFormCode(req.getFormCode()); @@ -62,7 +63,6 @@ public class FormSigPageService { entity.setSigType(req.getSigType()); entity.setPageFrom(req.getPageFrom()); entity.setPageTo(req.getPageTo()); - entity.setAction(req.getAction()); } return formSigPageRepository.save(entity); } @@ -97,6 +97,10 @@ public class FormSigPageService { return getConfig(formCode, asOfDate, UPLOAD2); } + public Optional getUpload3Config(String formCode, LocalDate asOfDate) { + return getConfig(formCode, asOfDate, UPLOAD3); + } + /** * Converts LocalDateTime (e.g. filled_form.created) to LocalDate for lookup. */ @@ -108,14 +112,19 @@ public class FormSigPageService { return asOf == null ? Optional.empty() : getUpload2Config(formCode, asOf.toLocalDate()); } + public Optional getUpload3Config(String formCode, LocalDateTime asOf) { + return asOf == null ? Optional.empty() : getUpload3Config(formCode, asOf.toLocalDate()); + } + /** - * Returns config for both upload1 and upload2 for the given formCode and asOf date. + * Returns config for upload1, upload2, and upload3 for the given formCode and asOf date. * Used by merge service and by the API for frontend labels. */ public Map getConfigForForm(String formCode, LocalDate asOfDate) { Map map = new HashMap<>(); getUpload1Config(formCode, asOfDate).ifPresent(c -> map.put(UPLOAD1, c)); getUpload2Config(formCode, asOfDate).ifPresent(c -> map.put(UPLOAD2, c)); + getUpload3Config(formCode, asOfDate).ifPresent(c -> map.put(UPLOAD3, c)); return map; } @@ -124,12 +133,14 @@ public class FormSigPageService { } /** - * Returns DTOs for API: list of { sigType, pageFrom, pageTo, label }. + * Returns DTOs for API: list of { sigType, pageFrom, pageTo, label } (upload1, upload2, upload3 when present). */ public List> getConfigDtosForForm(String formCode, LocalDate asOfDate) { - return getConfigForForm(formCode, asOfDate).values().stream() - .map(this::toDto) - .collect(Collectors.toList()); + List> out = new ArrayList<>(); + getUpload1Config(formCode, asOfDate).map(this::toDto).ifPresent(out::add); + getUpload2Config(formCode, asOfDate).map(this::toDto).ifPresent(out::add); + getUpload3Config(formCode, asOfDate).map(this::toDto).ifPresent(out::add); + return out; } public List> getConfigDtosForForm(String formCode, LocalDateTime asOf) { diff --git a/src/main/java/com/ffii/lioner/modules/lioner/pdf/service/PdfMergeService.java b/src/main/java/com/ffii/lioner/modules/lioner/pdf/service/PdfMergeService.java index a2f8ccd..d9aefe7 100644 --- a/src/main/java/com/ffii/lioner/modules/lioner/pdf/service/PdfMergeService.java +++ b/src/main/java/com/ffii/lioner/modules/lioner/pdf/service/PdfMergeService.java @@ -22,8 +22,6 @@ import com.itextpdf.kernel.utils.PdfMerger; @Service public class PdfMergeService { - private static final String SKIP_AND_APPEND = "SKIP_AND_APPEND"; - private final FormSigPageService formSigPageService; public PdfMergeService(FormSigPageService formSigPageService) { @@ -143,22 +141,7 @@ public class PdfMergeService { FormSigPage cfg = configOpt.get(); int pageFrom = cfg.getPageFrom(); int pageTo = cfg.getPageTo(); - boolean skipAndAppend = SKIP_AND_APPEND.equals(cfg.getAction()); - - if (skipAndAppend && totalPagesA >= pageFrom) { - if (pageFrom > 1) { - merger.merge(docA, 1, pageFrom - 1); - } - if (totalPagesA > pageFrom) { - merger.merge(docA, pageFrom + 1, totalPagesA); - } - if (pdfBFlattenedBytes != null) { - try (PdfReader readerB_merge = new PdfReader(new ByteArrayInputStream(pdfBFlattenedBytes)); - PdfDocument docB = new PdfDocument(readerB_merge)) { - merger.merge(docB, 1, 1); - } - } - } else if (!skipAndAppend && totalPagesA >= pageTo) { + if (totalPagesA >= pageTo) { int repStartA = pageFrom; int repEndA = pageTo; int pagesToInsertB = pageTo - pageFrom + 1; @@ -268,4 +251,82 @@ public class PdfMergeService { public byte[] mergePdf2sItext7(String formCode, byte[] pdfABytes, byte[] pdfBBytes) throws IOException { return mergePdf2sItext7(formCode, LocalDate.now(), pdfABytes, pdfBBytes); } + + /** + * Merge upload3 PDF using config from form_sig_page (third signature block; run after upload1 and upload2). + */ + public byte[] mergePdf3sItext7(String formCode, LocalDate asOfDate, byte[] pdfABytes, byte[] pdfBBytes) throws IOException { + Optional configOpt = formSigPageService.getUpload3Config(formCode, asOfDate); + + byte[] pdfAFlattenedBytes; + try (PdfReader readerA = new PdfReader(new ByteArrayInputStream(pdfABytes)); + ByteArrayOutputStream tempBaosA = new ByteArrayOutputStream(); + PdfWriter tempWriterA = new PdfWriter(tempBaosA); + PdfDocument docA_mod = new PdfDocument(readerA, tempWriterA)) { + + flattenPdf(docA_mod); + docA_mod.close(); + pdfAFlattenedBytes = tempBaosA.toByteArray(); + } + + byte[] pdfBFlattenedBytes = null; + if (pdfBBytes != null && pdfBBytes.length > 0) { + try (PdfReader readerB = new PdfReader(new ByteArrayInputStream(pdfBBytes)); + ByteArrayOutputStream tempBaosB = new ByteArrayOutputStream(); + PdfWriter tempWriterB = new PdfWriter(tempBaosB); + PdfDocument docB_mod = new PdfDocument(readerB, tempWriterB)) { + + flattenPdf(docB_mod); + docB_mod.close(); + pdfBFlattenedBytes = tempBaosB.toByteArray(); + } + } + + try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); + PdfWriter writer = new PdfWriter(baos); + PdfDocument mergedPdf = new PdfDocument(writer); + PdfReader readerA_merge = new PdfReader(new ByteArrayInputStream(pdfAFlattenedBytes)); + PdfDocument docA = new PdfDocument(readerA_merge)) { + + PdfMerger merger = new PdfMerger(mergedPdf); + int totalPagesA = docA.getNumberOfPages(); + + if (configOpt.isEmpty()) { + merger.merge(docA, 1, totalPagesA); + mergedPdf.close(); + return baos.toByteArray(); + } + + FormSigPage cfg = configOpt.get(); + int repStartA = cfg.getPageFrom(); + int repEndA = cfg.getPageTo(); + int repCountB = repEndA - repStartA + 1; + + if (totalPagesA >= repEndA) { + if (repStartA > 1) { + merger.merge(docA, 1, repStartA - 1); + } + if (pdfBFlattenedBytes != null) { + try (PdfReader readerB_merge = new PdfReader(new ByteArrayInputStream(pdfBFlattenedBytes)); + PdfDocument docB = new PdfDocument(readerB_merge)) { + if (docB.getNumberOfPages() >= repCountB) { + merger.merge(docB, 1, repCountB); + } + } + } + if (totalPagesA > repEndA) { + merger.merge(docA, repEndA + 1, totalPagesA); + } + } else { + merger.merge(docA, 1, totalPagesA); + } + + mergedPdf.close(); + return baos.toByteArray(); + } + } + + public byte[] mergePdf3sItext7(String formCode, byte[] pdfABytes, byte[] pdfBBytes) throws IOException { + return mergePdf3sItext7(formCode, LocalDate.now(), pdfABytes, pdfBBytes); + } } diff --git a/src/main/java/com/ffii/lioner/modules/lioner/pdf/service/PdfService.java b/src/main/java/com/ffii/lioner/modules/lioner/pdf/service/PdfService.java index 921951b..f6565ed 100644 --- a/src/main/java/com/ffii/lioner/modules/lioner/pdf/service/PdfService.java +++ b/src/main/java/com/ffii/lioner/modules/lioner/pdf/service/PdfService.java @@ -6,14 +6,21 @@ import java.io.FileOutputStream; import java.io.IOException; import java.text.DecimalFormat; import java.text.NumberFormat; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.sql.Timestamp; + import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.math.NumberUtils; import org.apache.pdfbox.Loader; @@ -37,6 +44,7 @@ import com.ffii.lioner.modules.lioner.client.service.ClientService; import com.ffii.lioner.modules.lioner.commonField.entity.CommonField; import com.ffii.lioner.modules.lioner.commonField.service.CommonFieldService; import com.ffii.lioner.modules.lioner.pdf.entity.Consultant; +import com.ffii.lioner.modules.lioner.pdf.entity.FormSigPage; import com.ffii.lioner.modules.lioner.pdf.entity.Pdf; import com.ffii.lioner.modules.lioner.pdf.entity.PdfRepository; import com.ffii.lioner.modules.lioner.pdf.req.UpdatePdfReq; @@ -65,9 +73,11 @@ public class PdfService extends AbstractBaseEntityService getAuditLogObject(Map req){ @@ -2707,6 +2718,8 @@ public class PdfService extends AbstractBaseEntityService> rows = jdbcDao.queryForList(sql.toString(), args); + enrichRowsWithSigTypes(rows); + return rows; + } + + /** + * Which signature upload slots apply for this row (same resolution as merge: formCode + filled_form.created). + */ + private void enrichRowsWithSigTypes(List> rows) { + if (rows == null || rows.isEmpty()) { + return; + } + for (Map row : rows) { + String formCode = (String) row.get("formCode"); + if (formCode == null || formCode.isBlank()) { + row.put("sigTypes", List.of("upload1")); + continue; + } + LocalDate asOf = jdbcTimestampToLocalDate(row.get("created")); + if (asOf == null) { + asOf = LocalDate.now(); + } + Map cfg = formSigPageService.getConfigForForm(formCode, asOf); + List sigTypes = new ArrayList<>(); + if (cfg.containsKey("upload1")) { + sigTypes.add("upload1"); + } + if (cfg.containsKey("upload2")) { + sigTypes.add("upload2"); + } + if (cfg.containsKey("upload3")) { + sigTypes.add("upload3"); + } + if (sigTypes.isEmpty()) { + sigTypes.add("upload1"); + } + row.put("sigTypes", sigTypes); + } + } + + /** + * As-of date for form_sig_page (same basis as merged download: filled_form.created in JVM default zone). + */ + public Optional findFilledFormAsOfDate(Long filledFormId) { + if (filledFormId == null) { + return Optional.empty(); + } + String sql = "SELECT ff.created FROM filled_form ff WHERE ff.deleted = FALSE AND ff.id = :id"; + return jdbcDao.queryForMap(sql, Map.of("id", filledFormId)) + .map(m -> jdbcTimestampToLocalDate(m.get("created"))); + } + + private static LocalDate jdbcTimestampToLocalDate(Object o) { + if (o == null) { + return null; + } + if (o instanceof LocalDate) { + return (LocalDate) o; + } + if (o instanceof LocalDateTime) { + return ((LocalDateTime) o).toLocalDate(); + } + if (o instanceof Timestamp) { + return ((Timestamp) o).toInstant().atZone(ZoneId.systemDefault()).toLocalDate(); + } + if (o instanceof java.util.Date) { + return ((java.util.Date) o).toInstant().atZone(ZoneId.systemDefault()).toLocalDate(); + } + return null; } public Map loadPDF(Long id) { @@ -4693,6 +4774,7 @@ public class PdfService extends AbstractBaseEntityService ((Integer) val).longValue()) .orElse(null); + Long upload3FileId = Optional.ofNullable(d.get("upload3FileId")) + .map(val -> ((Integer) val).longValue()) + .orElse(null); + // Safely retrieve byte arrays using Optional chaining (prevents NullPointerExceptions) byte[] pdfBytes = Optional.ofNullable(fileId) .flatMap(fileService::findFileBlobByFileId) @@ -289,6 +293,11 @@ public class PdfController { .map(FileBlob::getBytes) .orElse(null); + byte[] pdfUpload3Bytes = Optional.ofNullable(upload3FileId) + .flatMap(fileService::findFileBlobByFileId) + .map(FileBlob::getBytes) + .orElse(null); + // Check if the primary file is missing if (pdfBytes == null || pdfBytes.length == 0) { logger.info("Both null:"); @@ -310,6 +319,10 @@ public class PdfController { if (d.get("upload2FileId") != null) { finalPdfBytes = pdfMergeService.mergePdf2sItext7(formCode, asOfDate, finalPdfBytes, pdfUpload2Bytes); } + + if (d.get("upload3FileId") != null) { + finalPdfBytes = pdfMergeService.mergePdf3sItext7(formCode, asOfDate, finalPdfBytes, pdfUpload3Bytes); + } // --- 4. Build ResponseEntity --- HttpHeaders headers = new HttpHeaders(); @@ -326,14 +339,22 @@ public class PdfController { } /** - * Returns form_sig_page config for labels (upload1/upload2 page range and label text). - * Optional asOfDate (yyyy-MM-dd); defaults to today if omitted. + * Returns form_sig_page config for labels (upload1/upload2/upload3 page range and label text). + * Prefer filledFormId so the as-of date matches merged download (filled_form.created). + * Otherwise optional asOfDate (yyyy-MM-dd); defaults to today if still unresolved. */ @GetMapping("/form-sig-page-config") public Map getFormSigPageConfig( @RequestParam String formCode, - @RequestParam(required = false) String asOfDate) { - LocalDate date = parseLocalDate(asOfDate); + @RequestParam(required = false) String asOfDate, + @RequestParam(required = false) Long filledFormId) { + LocalDate date = null; + if (filledFormId != null) { + date = pdfService.findFilledFormAsOfDate(filledFormId).orElse(null); + } + if (date == null) { + date = parseLocalDate(asOfDate); + } if (date == null) { date = LocalDate.now(); } diff --git a/src/main/resources/db/changelog/changes/39_form_sig_page_drop_action/01_drop_form_sig_page_action.sql b/src/main/resources/db/changelog/changes/39_form_sig_page_drop_action/01_drop_form_sig_page_action.sql new file mode 100644 index 0000000..c323c60 --- /dev/null +++ b/src/main/resources/db/changelog/changes/39_form_sig_page_drop_action/01_drop_form_sig_page_action.sql @@ -0,0 +1,4 @@ +--liquibase formatted sql + +--changeset lioner:form_sig_page_drop_action +ALTER TABLE `form_sig_page` DROP COLUMN `action`;