diff --git a/Dockerfile b/Dockerfile index ceacf16e..18ed6c1a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,5 @@ FROM amazoncorretto:17.0.8-alpine3.17 +ENV TZ="Europe/Rome" EXPOSE 8080 ADD /target/tendermanagement-0.0.1-SNAPSHOT.jar tendermanagement-0.0.1-SNAPSHOT.jar ENTRYPOINT ["java", "-jar","tendermanagement-0.0.1-SNAPSHOT.jar"] diff --git a/pom.xml b/pom.xml index db1f4b8f..1e6d394a 100644 --- a/pom.xml +++ b/pom.xml @@ -191,6 +191,26 @@ 2.3.0 + + com.itextpdf + itextpdf + 5.5.13.3 + + + + + com.itextpdf + itext7-core + 8.0.5 + pom + + + + + com.itextpdf + layout + 8.0.5 + diff --git a/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java b/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java index ce35fbc4..b1b05dc4 100644 --- a/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java +++ b/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java @@ -203,5 +203,12 @@ public class GepafinConstant { public static final String USER_NOT_AUTHORIZED_TO_CREATE_APPLICATION = "user.not.authorized.create.application"; public static final String APPLICATION_SUBMITTED_CANNOT_CHANGE = "application.submitted.cannot.change"; + public static final String CALL_DOCUMENTS_FETCH_SUCCESS_MSG = "call.documents.fetch.success"; + public static final String CALL_DOCUMENTS_NOT_FOUND_MSG = "call.documents.not.found"; + public static final String PERMISSION_DENIED = "permission.denied"; + public static final String SIGNED_DOCUMENT_FILE_UPLOAD_SUCCESS = "signed.document.file.upload.success"; + public static final String GET_SIGNED_DOCUMENT_FILE_SUCCESS = "get.signed.document.file.success"; + public static final String APPLICATION_SIGNED_DOCUMENT_NOT_FOUND = "application.signed.document.not.found"; + public static final String DELETE_SIGNED_DOCUMENT_FILE_SUCCESS = "delete.signed.document.file.success"; } diff --git a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java index 1a621072..879c12b2 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java @@ -4,6 +4,7 @@ import net.gepafin.tendermanagement.config.Translator; import net.gepafin.tendermanagement.constants.GepafinConstant; import net.gepafin.tendermanagement.entities.*; import net.gepafin.tendermanagement.entities.SystemEmailTemplatesEntity.SystemEmailTemplatesEntityTypeEnum; +import net.gepafin.tendermanagement.enums.ApplicationSignedDocumentStatusEnum; import net.gepafin.tendermanagement.enums.ApplicationStatusTypeEnum; import net.gepafin.tendermanagement.enums.DocumentSourceTypeEnum; import net.gepafin.tendermanagement.enums.RoleStatusEnum; @@ -13,6 +14,7 @@ import net.gepafin.tendermanagement.model.request.ApplicationRequest; import net.gepafin.tendermanagement.model.request.ApplicationRequestBean; import net.gepafin.tendermanagement.model.response.*; import net.gepafin.tendermanagement.repositories.*; +import net.gepafin.tendermanagement.service.AmazonS3Service; import net.gepafin.tendermanagement.service.CallService; import net.gepafin.tendermanagement.service.CompanyService; import net.gepafin.tendermanagement.service.DocumentService; @@ -33,8 +35,10 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.jpa.domain.Specification; import org.springframework.stereotype.Component; +import org.springframework.web.multipart.MultipartFile; import jakarta.persistence.criteria.Predicate; +import jakarta.servlet.http.HttpServletRequest; import java.text.MessageFormat; import java.time.LocalDateTime; @@ -76,12 +80,13 @@ public class ApplicationDao { @Autowired private FlowDataRepository flowDataRepository; - @Autowired - private UserWithCompanyRepository userWithCompanyRepository; + @Autowired private UserCompanyDelegationRepository userCompanyDelegationRepository; + @Autowired private Validator validator; + @Autowired private CompanyService companyService; @@ -89,6 +94,7 @@ public class ApplicationDao { private ProtocolRepository protocolRepository; @Autowired + private SystemEmailTemplatesService systemEmailTemplatesService; @Autowired @@ -106,6 +112,13 @@ public class ApplicationDao { @Value("${carlo_email}") private String carloEmail; + private AmazonS3Service amazonS3Service; + + @Autowired + private ApplicationSignedDocumentRepository applicationSignedDocumentRepository; + + @Value("${aws.s3.url.folder.signed.document}") + private String signedDocumentS3Folder; public ApplicationResponseBean createApplication(ApplicationRequestBean applicationRequestBean, UserEntity userEntity, Long formId, Long applicationId) { @@ -689,7 +702,6 @@ public class ApplicationDao { protocolRepository.save(protocolEntity); return protocolEntity; } - private void sendMailToUserAndCompany(UserEntity userEntity, ApplicationEntity applicationEntity) { CallEntity call =applicationEntity.getCall(); CompanyEntity company = applicationEntity.getCompany(); @@ -724,34 +736,109 @@ public class ApplicationDao { } private void sendMailTodefaultSystemAndGepafin(UserEntity userEntity, ApplicationEntity applicationEntity) { - CallEntity call =applicationEntity.getCall(); - CompanyEntity company = applicationEntity.getCompany(); - ProtocolEntity protocol = applicationEntity.getProtocol(); - SystemEmailTemplateResponse systemEmailTemplateResponse = systemEmailTemplatesService - .retrieveTemplateByTypeAndCall(SystemEmailTemplatesEntityTypeEnum.APPLICATION_SUBMISSION_TO_GEPAFIN, - call, null); + CallEntity call = applicationEntity.getCall(); + CompanyEntity company = applicationEntity.getCompany(); + ProtocolEntity protocol = applicationEntity.getProtocol(); + SystemEmailTemplateResponse systemEmailTemplateResponse = systemEmailTemplatesService + .retrieveTemplateByTypeAndCall(SystemEmailTemplatesEntityTypeEnum.APPLICATION_SUBMISSION_TO_GEPAFIN, + call, null); + + // Create the map for subject placeholders + Map subjectPlaceholders = new HashMap<>(); + subjectPlaceholders.put("{{call_name}}", call.getName()); + subjectPlaceholders.put("{{company_name}}", company.getCompanyName()); + + // Create the map for body placeholders + Map bodyPlaceholders = new HashMap<>(); + bodyPlaceholders.put("{{call_name}}", call.getName()); + bodyPlaceholders.put("{{protocol_number}}", protocol.getProtocolNumber().toString()); + bodyPlaceholders.put("{{date}}", DateTimeUtil.formatLocalDateTime(protocol.getCreatedDate(), GepafinConstant.YYYY_MM_DD_SLASH)); + bodyPlaceholders.put("{{time}}", DateTimeUtil.parseLocalTimeToString(protocol.getTime(), GepafinConstant.HH_MM_SS)); + + // Replace placeholders in the subject and body + String subject = Utils.replacePlaceholders(systemEmailTemplateResponse.getSubject(), subjectPlaceholders); + String body = Utils.replacePlaceholders(systemEmailTemplateResponse.getHtmlContent(), bodyPlaceholders); + + + mailUtil.sendByMailGun(subject, body, List.of(defaultSystemReceiverEmail), null); + mailUtil.sendByMailGun(subject, body, List.of(gepafinEmail), null); + mailUtil.sendByMailGun(subject, body, List.of(rinaldoEmail), null); + mailUtil.sendByMailGun(subject, body, List.of(carloEmail), null); + + } + public ApplicationSignedDocumentResponse uploadSignedDocument(HttpServletRequest request, Long applicationId, + MultipartFile file) { + ApplicationEntity applicationEntity = validateApplication(applicationId); + validator.validateUserWithCompany(request, applicationEntity.getCompany().getId()); + validateFileType(file); + ApplicationSignedDocumentEntity applicationSignedDocument = applicationSignedDocumentRepository + .findByApplicationIdAndStatus(applicationId, ApplicationSignedDocumentStatusEnum.ACTIVE.getValue()); + if (applicationSignedDocument != null) { + applicationSignedDocument.setStatus(ApplicationSignedDocumentStatusEnum.INACTIVE.getValue()); + applicationSignedDocumentRepository.save(applicationSignedDocument); + } + UploadFileOnAmazonS3Response uploadFileOnAmazonS3 = amazonS3Service.uploadFileOnAmazonS3(signedDocumentS3Folder, + file); + applicationSignedDocument = new ApplicationSignedDocumentEntity(); + applicationSignedDocument.setApplication(applicationEntity); + applicationSignedDocument.setFileName(uploadFileOnAmazonS3.getFileName()); + applicationSignedDocument.setFilePath(uploadFileOnAmazonS3.getFilePath()); + applicationSignedDocument.setStatus(ApplicationSignedDocumentStatusEnum.ACTIVE.getValue()); + applicationSignedDocumentRepository.save(applicationSignedDocument); + return convertApplicationSignedDocumentToApplicationSignedDocumentResponse(applicationSignedDocument); + } + + private ApplicationSignedDocumentResponse convertApplicationSignedDocumentToApplicationSignedDocumentResponse( + ApplicationSignedDocumentEntity applicationSignedDocument) { + ApplicationSignedDocumentResponse applicationSignedDocumentResponse = new ApplicationSignedDocumentResponse(); + applicationSignedDocumentResponse.setId(applicationSignedDocument.getId()); + applicationSignedDocumentResponse.setApplicationId(applicationSignedDocument.getApplication().getId()); + applicationSignedDocumentResponse.setFileName(applicationSignedDocument.getFileName()); + applicationSignedDocumentResponse.setFilePath(applicationSignedDocument.getFilePath()); + applicationSignedDocumentResponse + .setStatus(ApplicationSignedDocumentStatusEnum.valueOf(applicationSignedDocument.getStatus())); + applicationSignedDocumentResponse.setCreatedDate(applicationSignedDocument.getCreatedDate()); + applicationSignedDocumentResponse.setUpdatedDate(applicationSignedDocument.getUpdatedDate()); + return applicationSignedDocumentResponse; + } + + private void validateFileType(MultipartFile file) { + if (file.isEmpty()) { + throw new CustomValidationException(Status.VALIDATION_ERROR, + Translator.toLocale(GepafinConstant.VALIDATION_ERROR_FILE_EMPTY)); + } + String filename = file.getOriginalFilename(); + if (filename == null || !filename.endsWith(".p7m")) { + throw new CustomValidationException(Status.VALIDATION_ERROR, + Translator.toLocale(GepafinConstant.VALIDATION_ERROR_FILE_INVALIDTYPE)); + } + } + + public ApplicationSignedDocumentResponse getSignedDocument(HttpServletRequest request, Long applicationId) { - // Create the map for subject placeholders - Map subjectPlaceholders = new HashMap<>(); - subjectPlaceholders.put("{{call_name}}", call.getName()); - subjectPlaceholders.put("{{company_name}}", company.getCompanyName()); - - // Create the map for body placeholders - Map bodyPlaceholders = new HashMap<>(); - bodyPlaceholders.put("{{call_name}}", call.getName()); - bodyPlaceholders.put("{{protocol_number}}", protocol.getProtocolNumber().toString()); - bodyPlaceholders.put("{{date}}", DateTimeUtil.formatLocalDateTime(protocol.getCreatedDate(), GepafinConstant.YYYY_MM_DD_SLASH)); - bodyPlaceholders.put("{{time}}", DateTimeUtil.parseLocalTimeToString(protocol.getTime(), GepafinConstant.HH_MM_SS)); - - // Replace placeholders in the subject and body - String subject = Utils.replacePlaceholders(systemEmailTemplateResponse.getSubject(), subjectPlaceholders); - String body = Utils.replacePlaceholders(systemEmailTemplateResponse.getHtmlContent(), bodyPlaceholders); - + ApplicationEntity applicationEntity = validateApplication(applicationId); + validator.validateUserWithCompany(request, applicationEntity.getCompany().getId()); - mailUtil.sendByMailGun(subject, body, List.of(defaultSystemReceiverEmail), null); - mailUtil.sendByMailGun(subject, body, List.of(gepafinEmail), null); - mailUtil.sendByMailGun(subject, body, List.of(rinaldoEmail), null); - mailUtil.sendByMailGun(subject, body, List.of(carloEmail), null); - + ApplicationSignedDocumentEntity applicationSignedDocument = applicationSignedDocumentRepository + .findByApplicationIdAndStatus(applicationId, ApplicationSignedDocumentStatusEnum.ACTIVE.getValue()); + if(applicationSignedDocument == null) { + throw new ResourceNotFoundException(Status.NOT_FOUND, + Translator.toLocale(GepafinConstant.APPLICATION_SIGNED_DOCUMENT_NOT_FOUND)); + } + return convertApplicationSignedDocumentToApplicationSignedDocumentResponse(applicationSignedDocument); + } + + public void deleteSignedDocument(HttpServletRequest request, Long applicationId) { + ApplicationEntity applicationEntity = validateApplication(applicationId); + validator.validateUserWithCompany(request, applicationEntity.getCompany().getId()); + + ApplicationSignedDocumentEntity applicationSignedDocument = applicationSignedDocumentRepository + .findByApplicationIdAndStatus(applicationId, ApplicationSignedDocumentStatusEnum.ACTIVE.getValue()); + if(applicationSignedDocument == null) { + throw new ResourceNotFoundException(Status.NOT_FOUND, + Translator.toLocale(GepafinConstant.APPLICATION_SIGNED_DOCUMENT_NOT_FOUND)); + } + applicationSignedDocument.setStatus(ApplicationSignedDocumentStatusEnum.INACTIVE.getValue()); + applicationSignedDocumentRepository.save(applicationSignedDocument); } } diff --git a/src/main/java/net/gepafin/tendermanagement/dao/CallDao.java b/src/main/java/net/gepafin/tendermanagement/dao/CallDao.java index c6345d59..0827b62a 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/CallDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/CallDao.java @@ -1,5 +1,9 @@ package net.gepafin.tendermanagement.dao; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; import java.math.BigDecimal; import java.time.LocalDate; import java.time.LocalDateTime; @@ -8,13 +12,21 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.stream.Collectors; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; import net.gepafin.tendermanagement.enums.DocumentSourceTypeEnum; import net.gepafin.tendermanagement.model.response.*; import net.gepafin.tendermanagement.service.*; import net.gepafin.tendermanagement.util.DateTimeUtil; import net.gepafin.tendermanagement.util.Utils; +import org.h2.util.IOUtils; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; @@ -85,6 +97,10 @@ public class CallDao { private FlowDao flowDao; @Autowired private FormDao formDao; + @Value("${aws.s3.url.folder}") + private String s3Folder; + @Autowired + private AmazonS3Service amazonS3Service; public CallResponse createCallStep1(CreateCallRequestStep1 createCallRequest, Long userId) { UserEntity userEntity = userService.validateUser(userId); @@ -101,6 +117,35 @@ public class CallDao { return createCallResponseBean; } + public byte[] downloadCallDocumentsAsZip(Long callId) { + List documents = documentRepository.findBySourceIdAndSourceAndTypeAndIsDeletedFalse(callId, DocumentSourceTypeEnum.CALL.getValue(),DocumentTypeEnum.DOCUMENT.getValue()); + if (documents.isEmpty()) { + throw new ResourceNotFoundException(Status.NOT_FOUND, Translator.toLocale(GepafinConstant.DOCUMENT_NOT_FOUND)); + } + + try (ByteArrayOutputStream zipOutputStream = new ByteArrayOutputStream(); + ZipOutputStream zos = new ZipOutputStream(zipOutputStream)) { + + for (DocumentEntity document : documents) { + try (InputStream fileInputStream = amazonS3Service.getFile(s3Folder, document.getFileName())) { + ZipEntry zipEntry = new ZipEntry(document.getFileName()); + zos.putNextEntry(zipEntry); + IOUtils.copy(fileInputStream, zos); + zos.closeEntry(); + } catch (IOException e) { + throw new RuntimeException("Error downloading or adding document to ZIP: " + document.getFileName(), e); + } + } + + zos.finish(); + return zipOutputStream.toByteArray(); + + } catch (IOException e) { + throw new RuntimeException("Error while creating ZIP file", e); + } + } + + public CallEntity convertToCallEntity(CreateCallRequestStep1 createCallRequest) { CallEntity callEntity = new CallEntity(); diff --git a/src/main/java/net/gepafin/tendermanagement/dao/CompanyDao.java b/src/main/java/net/gepafin/tendermanagement/dao/CompanyDao.java index 728990b5..3a9b92ad 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/CompanyDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/CompanyDao.java @@ -18,6 +18,7 @@ import net.gepafin.tendermanagement.repositories.UserWithCompanyRepository; import net.gepafin.tendermanagement.service.UserService; import net.gepafin.tendermanagement.util.Utils; import net.gepafin.tendermanagement.web.rest.api.errors.CustomValidationException; +import net.gepafin.tendermanagement.web.rest.api.errors.ForbiddenAccessException; import net.gepafin.tendermanagement.web.rest.api.errors.ResourceNotFoundException; import net.gepafin.tendermanagement.web.rest.api.errors.Status; @@ -190,8 +191,8 @@ public class CompanyDao { } public UserWithCompanyEntity validateUserWithCompny(Long userId, Long companyId) { - return userWithCompanyRepository.findByUserIdAndCompanyId(userId, companyId).orElseThrow(() -> new CustomValidationException(Status.UNAUTHORIZED, - Translator.toLocale(GepafinConstant.UNAUTHORIZED))); + return userWithCompanyRepository.findByUserIdAndCompanyId(userId, companyId).orElseThrow(() -> new ForbiddenAccessException(Status.FORBIDDEN, + Translator.toLocale(GepafinConstant.PERMISSION_DENIED))); } public UserWithCompanyEntity getUserWithCompany(Long userId, Long compnayId) { diff --git a/src/main/java/net/gepafin/tendermanagement/dao/PdfDao.java b/src/main/java/net/gepafin/tendermanagement/dao/PdfDao.java new file mode 100644 index 00000000..30e10fe8 --- /dev/null +++ b/src/main/java/net/gepafin/tendermanagement/dao/PdfDao.java @@ -0,0 +1,600 @@ +package net.gepafin.tendermanagement.dao; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.itextpdf.kernel.colors.ColorConstants; +import com.itextpdf.kernel.colors.DeviceRgb; +import com.itextpdf.kernel.pdf.canvas.PdfCanvas; +import com.itextpdf.layout.properties.UnitValue; +import com.itextpdf.layout.renderer.CellRenderer; +import com.itextpdf.layout.renderer.DrawContext; +import com.itextpdf.text.*; +import com.itextpdf.text.Element; +import com.itextpdf.text.Font; +import com.itextpdf.text.Image; +import com.itextpdf.text.Rectangle; +import com.itextpdf.text.pdf.*; + +import jakarta.servlet.http.HttpServletRequest; +import net.gepafin.tendermanagement.config.Translator; +import net.gepafin.tendermanagement.constants.GepafinConstant; +import net.gepafin.tendermanagement.entities.*; +import net.gepafin.tendermanagement.model.request.CustomPageEvent; +import net.gepafin.tendermanagement.model.request.FieldLabelValuePairRequest; +import net.gepafin.tendermanagement.model.response.*; +import net.gepafin.tendermanagement.repositories.ApplicationRepository; +import net.gepafin.tendermanagement.service.CallService; +import net.gepafin.tendermanagement.util.Validator; +import net.gepafin.tendermanagement.web.rest.api.errors.ResourceNotFoundException; +import net.gepafin.tendermanagement.web.rest.api.errors.Status; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import com.itextpdf.layout.element.Table; +import com.itextpdf.layout.element.Cell; +//import com.itextpdf.layout.element. + + +import java.awt.*; +import java.io.ByteArrayOutputStream; +import java.util.*; +import java.util.List; +import java.util.stream.Collectors; + +@Component +public class PdfDao { + + @Autowired + private CallService callService; + + @Autowired + private ApplicationDao applicationDao; + + @Autowired + private Validator validator; + + + public byte[] generatePdf(HttpServletRequest request,Long applicationId) { + try { + UserEntity userEntity = validator.validateUser(request); + ApplicationEntity applicationEntity = applicationDao.validateApplication(applicationId); + validator.validateUserWithCompany(request, applicationEntity.getCompany().getId()); + CallEntity call=callService.validateCall(applicationEntity.getCall().getId()); + + // Create a byte stream to hold the PDF + ByteArrayOutputStream out = new ByteArrayOutputStream(); + float leftMargin = 50f; // Adjust this for the left margin + + Document document = new Document(PageSize.A4, leftMargin, 36f, 50f, 35); + PdfWriter writer = PdfWriter.getInstance(document, out); + CustomPageEvent pageEvent = new CustomPageEvent(call.getName(), 0); + writer.setPageEvent(pageEvent); + document.open(); + pageEvent.setTotalPages(writer.getPageNumber()); + addLogo(document, "https://mementoresources.s3.eu-west-1.amazonaws.com/gepafin/logo.jpg"); // Add your image path here + + + BaseColor customColor = new BaseColor(0, 128, 0); // Adjust RGB values as needed + // Define fonts and styles + BaseColor greenColor = new BaseColor(0, 128, 0); // Adjust RGB values as needed + BaseColor darkGreenColor = new BaseColor(1, 50, 32); // Adjust RGB values as needed + Font titleFont = FontFactory.getFont(FontFactory.HELVETICA_BOLD, 16, customColor); + Font sectionFont = FontFactory.getFont(FontFactory.HELVETICA_BOLD, 12,darkGreenColor); + Font labelFont = FontFactory.getFont(FontFactory.HELVETICA_BOLD, 12,new BaseColor(113,121,126)); // Light grey); + Font smallFont = FontFactory.getFont(FontFactory.HELVETICA_BOLD, 8,new BaseColor(105, 105, 105)); + Font valueFont=FontFactory.getFont(FontFactory.HELVETICA_BOLD,10,new BaseColor(178, 190, 181)); + Paragraph title = new Paragraph(call.getName(), titleFont); + title.setAlignment(Element.ALIGN_LEFT); + document.add(title); + + BaseColor greyColor=new BaseColor(178, 190, 181); // Very light grey color + addColoredLines(writer,document,greyColor); + document.add(new Paragraph(" ")); + + // Application ID section (Centered) + pageEvent.setTotalPages(writer.getPageNumber()); + Paragraph appId = new Paragraph("ID domanda :" +"XX00"); + appId.setAlignment(Element.ALIGN_RIGHT); + document.add(appId); + + if(applicationEntity.getProtocol()!=null) { + appId = new Paragraph("ID domanda :"+String.valueOf(applicationEntity.getProtocol().getProtocolNumber()), valueFont); + appId.setAlignment(Element.ALIGN_RIGHT); + document.add(appId); + } + document.add(new Paragraph(" ")); + + addColoredLines(writer,document,greenColor); + document.add(new Paragraph(" ")); + document.add(new Paragraph("\n")); // Add line break +// String companyName= companyEntity.getCompanyName(); +// String vatNumber=companyEntity.getVatNumber(); +// String address=companyEntity.getAddress(); +// // Section: Dati Anagrafici Azienda +// document.add(new Paragraph("Dati Anagrafici Azienda", sectionFont)); +// addLabelValuePair(document, "Codice ATECO", "SEZIONE C “ATTIVITÀ MANUFATTURIERE”", regularFont); +// addLabelValuePair(document, "Ragione Sociale", companyName, regularFont); +// addLabelValuePair(document, "Partita IVA", vatNumber, regularFont); +// addLabelValuePair(document, "Indirizzo sede Legale", address, regularFont); +// +// document.add(new Paragraph("\n")); // Add line break +// +// // Section: Domanda presentata da +// document.add(new Paragraph("Domanda presentata da:", sectionFont)); +// addLabelValuePair(document, "Nome e cognome", userEntity.getBeneficiary().getFirstName()+" "+userEntity.getBeneficiary().getLastName(), regularFont); +// addLabelValuePair(document, "Codice fiscale", userEntity.getBeneficiary().getCodiceFiscale(), regularFont); +// addLabelValuePair(document, "Telefono", userEntity.getBeneficiary().getPhoneNumber(), regularFont); +// addLabelValuePair(document, "Email", userEntity.getBeneficiary().getEmail(), regularFont); +// addLabelValuePair(document, "Con il titolo di", "Rappresentante legale", regularFont); + document.add(new Paragraph(" ")); + + ApplicationGetResponseBean applicationGetResponseBean=applicationDao.getApplicationByFormId(applicationId,null, userEntity); + for(FormApplicationResponse formApplicationResponse: applicationGetResponseBean.getForm()) { + document.add(new Paragraph(formApplicationResponse.getLabel(),sectionFont)); + document.add(new Paragraph(" ")); // Add line break + List fieldLabelValuePairRequests = getFormFieldsToLabels(formApplicationResponse); + for (FieldLabelValuePairRequest pair : fieldLabelValuePairRequests) { + String label = pair.getLabel(); + Object value = pair.getValue(); + Integer pages=0; + pages=addLabelValuePair(writer,document, label, value, labelFont,valueFont,call.getName(),pages); + if(pages !=0 ){ + pageEvent.setTotalPages(writer.getPageNumber()); + } + } + addColoredLines(writer,document,greenColor); + document.add(new Paragraph(" ")); // Add line break + } + document.add(new Paragraph("\n")); // Add line break + Font boldSmallFont = new Font(Font.FontFamily.HELVETICA, 10, Font.BOLD,new BaseColor(105, 105, 105)); + + // Adding the "Documenti Allegati" section title + document.add(new Paragraph(" ")); + + pageEvent.setTotalPages(writer.getPageNumber()); + document.newPage(); + document.add(new Paragraph("Documenti Allegati", sectionFont)); + document.add(new Paragraph(" ")); + + +// 1. Autocertificazione possesso Requisiti + Paragraph p1 = new Paragraph(); + p1.add(new Chunk("1. ", boldSmallFont)); + p1.add(new Chunk("Autocertificazione possesso Requisiti ", boldSmallFont)); + p1.add(new Chunk("ai sensi degli artt. 46 e 47 del DPR 445/2000", smallFont)); + document.add(p1); + document.add(new Paragraph(" ")); + + + +// 2. Informativa Privacy relativa al trattamento dei dati personali + Paragraph p2 = new Paragraph(); + p2.add(new Chunk("2. ", boldSmallFont)); + p2.add(new Chunk("Informativa Privacy relativa al trattamento dei dati personali", boldSmallFont)); + document.add(p2); + document.add(new Paragraph(" ")); + + +// 3. Dati richiesti per la valutazione dell’adeguatezza dei flussi finanziari + Paragraph p3 = new Paragraph(); + p3.add(new Chunk("3. ", boldSmallFont)); + p3.add(new Chunk("Dati richiesti per la valutazione dell’adeguatezza dei flussi finanziari prospettici come da tabella di cui all’Appendice 9", boldSmallFont)); + document.add(p3); + document.add(new Paragraph(" ")); + + +// 4. Rilevazione Centrale dei Rischi + Paragraph p4 = new Paragraph(); + p4.add(new Chunk("4. ", boldSmallFont)); + p4.add(new Chunk("Rilevazione Centrale dei Rischi riferita agli ultimi 36 mesi disponibili alla data di presentazione della Domanda", boldSmallFont)); + document.add(p4); + document.add(new Paragraph(" ")); + + +// 5. Schema di presentazione dei dati di bilancio + Paragraph p5 = new Paragraph(); + p5.add(new Chunk("5. ", boldSmallFont)); + p5.add(new Chunk("Schema di presentazione dei dati di bilancio", boldSmallFont)); + document.add(p5); + document.add(new Paragraph(" ")); + + +// 6. Dettagli bilanci in forma abbreviata + Paragraph p6 = new Paragraph(); + p6.add(new Chunk("6. ", boldSmallFont)); + p6.add(new Chunk("Dettagli bilanci in forma abbreviata", boldSmallFont)); + document.add(p6); + document.add(new Paragraph(" ")); + + +// 7. Relazione aziendale illustrativa + Paragraph p7 = new Paragraph(); + p7.add(new Chunk("7. ", boldSmallFont)); + p7.add(new Chunk("Relazione aziendale illustrativa", boldSmallFont)); + document.add(p7); + document.add(new Paragraph(" ")); + + addColoredLines(writer,document,greenColor); + + // Close the document + document.close(); + + // Convert to byte array for response + byte[] pdfBytes = out.toByteArray(); + return pdfBytes; + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + private Integer addLabelValuePair(PdfWriter writer,Document document, String label, Object value, Font labelFont,Font valueFont,String title,Integer totalPages) throws DocumentException { + // Add label + Paragraph labelParagraph = new Paragraph(label, labelFont); + document.add(labelParagraph); + float leftMargin = 20f; + + PdfContentByte canvas = writer.getDirectContent(); + + // Setting the color and width of the line + float lineWidth = 1.0f; // Thickness of the line + canvas.setLineWidth(lineWidth); + + // Get the current vertical position in the document + float yPos = writer.getVerticalPosition(true) - 10f; // Adjust this to move line slightly below current content + + // Define start and end points for the line (relative to the page size and margins) + + if (yPos <= 140) { + // If xEnd is less than or equal to 200, generate a new page + + totalPages++; + document.newPage(); + } // Add a gap between the label and value + document.add(new Paragraph(" ")); // Adding an empty paragraph for spacing + // Create value cell with rounded corners + PdfPTable valueTable = new PdfPTable(1); + valueTable.setWidthPercentage(100); + if (value instanceof List) { + // Further check if the list contains Strings + List list = (List) value; + if (!list.isEmpty() && list.get(0) instanceof String) { + // Cast to List + List values = (List) value; + + // Loop through the list of strings and create a cell for each string + for (String item : values) { + PdfPCell valueCell = new PdfPCell(new Phrase(item, valueFont)); + valueCell.setPadding(5f); // Increase padding for better spacing + valueCell.setPaddingLeft(leftMargin); // Increase left margin for value + valueCell.setBorder(Rectangle.NO_BORDER); // Remove border for value cell + valueCell.setMinimumHeight(30f); + valueCell.setVerticalAlignment(Element.ALIGN_MIDDLE); + valueCell.setCellEvent(new RoundedCorners()); // Apply rounded corners + + // Add the cell to the table + valueTable.addCell(valueCell); + } + + // Finally, add the table to the document + document.add(valueTable); + } else { + boolean containsThreeValues = false; // Variable to track if any map contains three keys + List> dataList = (List>) value; // Cast Object to List of Maps + for (Map entry : dataList) { + if (entry.size() == 3) { // Check if the current map has three keys + containsThreeValues = true; // If found, set the variable to true + break; // No need to check further, exit loop + } + } + List> extractedData = new ArrayList<>(); // To hold extracted data + for (Map entry : dataList) { + Map extractedMap = new HashMap<>(); // To hold the current extracted row of data + + List keys = new ArrayList<>(entry.keySet()); // Get all keys in the current map + + // Handle based on the number of keys in the map + if (Boolean.FALSE.equals(containsThreeValues) && keys.size() == 2) { + // Treat the first key as the "key" and second key as the "value" + String heading = (String) entry.get(keys.get(0)); // Get value of first key + String value1 = (String) entry.get(keys.get(1)); // Get value of second key + extractedMap.put(heading,value1); // Store the first key's value as "heading" + } if (Boolean.TRUE.equals(containsThreeValues) ) { + String amount=""; + // Treat the first as number, second as description, third as amount + if(keys.size()==3){ + amount = (String) entry.get(keys.get(2)); // Third key's value + } + String number = (String) entry.get(keys.get(0)); // First key's value + String description = (String) entry.get(keys.get(1)); // Second key's value + + // Store the combined result as a value in the map, with a suitable key + String combinedValue = number + ", " + description + ", " + amount; // Concatenate them as a single value + extractedMap.put("combined", combinedValue); // Store as a single entry, key as "combined" + } + + extractedData.add(extractedMap); // Add each extracted map to the list + } + document=createPdfTable(extractedData,document); + } + } + else { + PdfPCell valueCell = new PdfPCell(new Phrase(String.valueOf(value), valueFont)); + valueCell.setPadding(5f); // Increase padding for better spacing + valueCell.setPaddingLeft(leftMargin); // Increase left margin for value + valueCell.setBorder(Rectangle.NO_BORDER); // Remove border for value cell + valueCell.setMinimumHeight(30f); + valueCell.setVerticalAlignment(Element.ALIGN_MIDDLE); + valueCell.setCellEvent(new RoundedCorners()); // Apply rounded corners + + valueTable.addCell(valueCell); + document.add(valueTable); + } + + document.add(new Paragraph("\n")); // Add line break after each value + return totalPages; + } + + private Document createPdfTable(List> extractedData,Document document) throws DocumentException { // Create a PdfPTable with 2 columns + PdfPTable table = new PdfPTable(2); // Initial assumption for 2 columns + table.setWidthPercentage(100); // Set table width to 100% + Font textFont = FontFactory.getFont(FontFactory.HELVETICA, 12, Font.NORMAL, new BaseColor(105, 105, 105)); // Gray text + boolean combinedHeaderAdded = false; // Flag to track if headers for combined have been added + float rowHeight = 50f; // Example row height, adjust as necessary + float maxTableHeight = 700f; // Maximum height of the table before a page break + int rowCount = 0; // Counter for rows + + +// Add table header +// Populate the table with extracted data and style rows + for (Map row : extractedData) { + for (Map.Entry entry : row.entrySet()) { + String key = entry.getKey(); // This will give you the key + String value = entry.getValue(); // This will give you the value + + // Check if the current entry is for the combined section + if ("combined".equals(key)) { + // Ensure the combined header is added only once + if (!combinedHeaderAdded) { + // Create a new table for combined entries + table = new PdfPTable(3); // 3 columns for combined entries + + PdfPCell headerCell1 = new PdfPCell(new Phrase("Number")); + headerCell1.setHorizontalAlignment(Element.ALIGN_CENTER); // Center align + headerCell1.setVerticalAlignment(Element.ALIGN_MIDDLE); + headerCell1.setBackgroundColor(new BaseColor(178, 190, 181)); // Light gray background for header + table.addCell(headerCell1); + + PdfPCell headerCell2 = new PdfPCell(new Phrase("Details")); + headerCell2.setHorizontalAlignment(Element.ALIGN_CENTER); // Center align + headerCell2.setVerticalAlignment(Element.ALIGN_MIDDLE); + headerCell2.setBackgroundColor(new BaseColor(178, 190, 181)); // Light gray background for header + table.addCell(headerCell2); + + PdfPCell headerCell3 = new PdfPCell(new Phrase("Amount")); + headerCell3.setHorizontalAlignment(Element.ALIGN_CENTER); // Center align + headerCell3.setVerticalAlignment(Element.ALIGN_MIDDLE); + headerCell3.setBackgroundColor(new BaseColor(178, 190, 181)); // Light gray background for header + table.addCell(headerCell3); + + combinedHeaderAdded = true; // Mark header as added + } + + // Split the value for "combined" into separate parts + String[] combinedValues = value.split(", "); + + // Check if we have 3 parts (number, description, amount) + String number = combinedValues[0].trim(); // 1st part (number) + String description = combinedValues[1].trim(); // 2nd part (description) + String amount = ""; + if (combinedValues.length == 3) { + amount = combinedValues[2].trim(); // 3rd part (amount) + } + + // Create PDF cells using the split values + PdfPCell cellNumber = new PdfPCell(new Phrase(number, textFont)); // Cell for number + PdfPCell cellDescription = new PdfPCell(new Phrase(description, textFont)); // Cell for description + PdfPCell cellAmount = new PdfPCell(new Phrase(amount, textFont)); // Cell for amount + + // Set row background color for combined values + cellNumber.setBackgroundColor(new BaseColor(239, 243, 248)); // Light blue for combined rows + cellDescription.setBackgroundColor(new BaseColor(239, 243, 248)); + cellAmount.setBackgroundColor(new BaseColor(239, 243, 248)); + + // Set cell height and add rounded borders + cellNumber.setFixedHeight(rowHeight); + cellDescription.setFixedHeight(rowHeight); + cellAmount.setFixedHeight(rowHeight); + cellNumber.setPadding(7f); + cellDescription.setPadding(7f); + cellAmount.setPadding(7f); + + // Add the cells to the table only once + table.addCell(cellNumber); + table.addCell(cellDescription); + table.addCell(cellAmount); + + + } else { + if (!combinedHeaderAdded) { + // Create a new table for combined entries + table= new PdfPTable(2); // 3 columns for combined entries + table.setWidthPercentage(100); + + PdfPCell headerCell1 = new PdfPCell(new Phrase("Details")); + headerCell1.setHorizontalAlignment(Element.ALIGN_CENTER); // Center align + headerCell1.setVerticalAlignment(Element.ALIGN_MIDDLE); + headerCell1.setBackgroundColor(new BaseColor(178, 190, 181)); // Light gray background for header + table.addCell(headerCell1); + + PdfPCell headerCell2 = new PdfPCell(new Phrase("Amount")); + headerCell2.setHorizontalAlignment(Element.ALIGN_CENTER); // Center align + headerCell2.setVerticalAlignment(Element.ALIGN_MIDDLE); + headerCell2.setBackgroundColor(new BaseColor(178, 190, 181)); // Light gray background for header + table.addCell(headerCell2); + combinedHeaderAdded=true; +} + // Add cells for regular key-value pairs without headers + PdfPCell cellKey = new PdfPCell(new Phrase(key, textFont)); + PdfPCell cellValue = new PdfPCell(new Phrase(value, textFont)); + + // Set background color for both cells + cellKey.setBackgroundColor(new BaseColor(239, 243, 248)); // Light blue for other rows + cellValue.setBackgroundColor(new BaseColor(239, 243, 248)); + + // Set cell height and add rounded borders + cellKey.setFixedHeight(rowHeight); + cellValue.setFixedHeight(rowHeight); + + // Add the cells to the table + table.addCell(cellKey); + table.addCell(cellValue); + } + if (table.getTotalHeight() + rowHeight > maxTableHeight) { + // Start a new page if needed + document.add(table); + table = new PdfPTable(2); // Reset table for new page + table.setWidthPercentage(100); // Reset width percentage + combinedHeaderAdded = false; // Reset header flag + } + } + } + document.add(table); // Add the last table before returning + + + // Check if adding a new row would exceed the maximum height +// Return the populated table + return document; + } + + public List getFormFieldsToLabels(FormApplicationResponse responseBean) { + List labelValuePairs = new ArrayList<>(); + + // Iterate through each form in the application response + + List formFields = responseBean.getFormFields(); + List contents = responseBean.getContent(); + + // Iterate through each formField in the current form + for (ApplicationFormFieldResponseBean formField : formFields) { + String fieldId = formField.getFieldId(); + Object fieldValue = formField.getFieldValue(); + + // Find the content in the form that matches the fieldId + Optional matchingContent = contents.stream() + .filter(content -> content.getId().equals(fieldId)) + .findFirst(); + + + // If the content with the matching fieldId is found, create a label-value pair + if (matchingContent.isPresent()) { + String name = matchingContent.get().getName(); + if (name.equals("fileupload")) { + +// Step 1: Check if fieldValue is an instance of List + if (fieldValue instanceof List && ((List) fieldValue).stream().allMatch(item -> item instanceof DocumentResponseBean)) { + // Step 2: Safely cast to List + List documentList = (List) fieldValue; + + // Step 3: Extract names from the document list + List names = documentList.stream() + .map(DocumentResponseBean::getName) // Extract the name from each DocumentResponseBean + .collect(Collectors.toList()); + + fieldValue=names; + } + } + if(name.equals("checkboxes")) { + List check = (List) fieldValue; + List settingResponseBeans = matchingContent.get().getSettings(); + for (SettingResponseBean settingResponseBean : settingResponseBeans) { + // Initialize a list to hold matched labels for each SettingResponseBean + List matchedLabels = new ArrayList<>(); + if (settingResponseBean.getValue() instanceof List) { + + List valueList = (List) settingResponseBean.getValue(); + if (!valueList.isEmpty() && valueList.get(0) instanceof Map) { + // Cast to List> + List> options = (List>) valueList; + for (Map field : options) { + for (String val : check) { + String name1=field.get("name"); + if (val.equals(name1)) { // Check if the key exists in the current field map + String label = field.get("label"); // Extract the label + if (field != null) { // Check if the value is not null + matchedLabels.add(label); // Add the value to the matchedValues list + } + } + } + } + fieldValue = matchedLabels; + } + } + } + } + String label = matchingContent.get().getLabel(); + // Add the label-value pair to the list + if (fieldValue != null && !fieldValue.toString().trim().isEmpty()) { + fieldValue = findLabelInOptions(matchingContent.get().getSettings(), fieldValue); + labelValuePairs.add(new FieldLabelValuePairRequest(label, fieldValue)); + } + } + } + + return labelValuePairs; + } + + public static Object findLabelInOptions(List settings, Object valueToFind) { + ObjectMapper objectMapper = new ObjectMapper(); + + try { + if (valueToFind instanceof String) { + String searchValue = (String) valueToFind; + for (SettingResponseBean setting : settings) { + Object value = setting.getValue(); + if (value instanceof List) { + List options = (List) value; + for (Object option : options) { + JsonNode optionNode = objectMapper.convertValue(option, JsonNode.class); + if (optionNode.get("name").asText().equals(searchValue)) { + return optionNode.get("label").asText(); + } + } + } + } + } + } catch (Exception e) { + } + return valueToFind; + } + + public void addLogo(Document document, String logoPath) throws Exception { + Image logo = Image.getInstance(logoPath); + logo.scaleToFit(document.getPageSize().getWidth() - document.leftMargin() - document.rightMargin(), // Fit to document width + document.getPageSize().getHeight() / 4); // Adjust the height as needed (1/4th of the page height) + logo.setAlignment(Image.ALIGN_CENTER); // Align logo to center + document.add(logo); + + // Add some space after logo + document.add(new Paragraph("\n")); // Adding space after the logo + } + public void addColoredLines(PdfWriter writer, Document document, BaseColor color){ + PdfContentByte canvas = writer.getDirectContent(); + + // Setting the color and width of the line + canvas.setColorStroke(color); + float lineWidth = 1.0f; // Thickness of the line + canvas.setLineWidth(lineWidth); + + // Get the current vertical position in the document + float yPos = writer.getVerticalPosition(true) - 10f; // Adjust this to move line slightly below current content + + // Define start and end points for the line (relative to the page size and margins) + float xStart = document.leftMargin(); // Start from the left margin + float xEnd = document.getPageSize().getWidth() - document.rightMargin(); // End at the right margin + + // Draw the line at the current Y position + canvas.moveTo(xStart, yPos); // Move to the starting point + canvas.lineTo(xEnd, yPos); // Draw the line to the end point + canvas.stroke(); // Apply the stroke (line) + } +} diff --git a/src/main/java/net/gepafin/tendermanagement/dao/RoundedCorners.java b/src/main/java/net/gepafin/tendermanagement/dao/RoundedCorners.java new file mode 100644 index 00000000..9aa82dc1 --- /dev/null +++ b/src/main/java/net/gepafin/tendermanagement/dao/RoundedCorners.java @@ -0,0 +1,45 @@ +package net.gepafin.tendermanagement.dao; + +import com.itextpdf.text.BaseColor; +import com.itextpdf.text.Rectangle; +import com.itextpdf.text.pdf.PdfContentByte; +import com.itextpdf.text.pdf.PdfPCell; +import com.itextpdf.text.pdf.PdfPCellEvent; +import com.itextpdf.text.pdf.PdfPTable; + +public class RoundedCorners implements PdfPCellEvent { + @Override + public void cellLayout(PdfPCell cell, Rectangle position, PdfContentByte[] canvas) { + // Retrieve the canvas for drawing the background and border + PdfContentByte cbBackground = canvas[PdfPTable.BACKGROUNDCANVAS]; + PdfContentByte cb = canvas[PdfPTable.LINECANVAS]; + + cbBackground.saveState(); + cb.saveState(); + + // Define the rounded rectangle radius and padding + float radius = 10f; + float padding = 2f; // Small padding to avoid overlap + + // Get position values with adjusted height and width + float x = position.getLeft() + padding; + float y = position.getBottom() + padding; + float width = position.getWidth() - 2 * padding; + float height = position.getHeight() - 2 * padding; + + // Fill the rounded rectangle with lighter grey + cbBackground.setColorFill(new BaseColor(239, 243, 248)); // Very light grey color + cbBackground.roundRectangle(x, y, width, height, radius); + cbBackground.fill(); // Fill the background + + // Set the border stroke to thin and draw the rounded rectangle with dark grey color + cb.setLineWidth(0.5f); // Thin border width + cb.setColorStroke(new BaseColor(105, 105, 105)); // Dark grey border + cb.roundRectangle(x, y, width, height, radius); + cb.stroke(); // Draw the border + + // Restore the canvas states + cbBackground.restoreState(); + cb.restoreState(); + } + } diff --git a/src/main/java/net/gepafin/tendermanagement/entities/ApplicationSignedDocumentEntity.java b/src/main/java/net/gepafin/tendermanagement/entities/ApplicationSignedDocumentEntity.java new file mode 100644 index 00000000..58975cca --- /dev/null +++ b/src/main/java/net/gepafin/tendermanagement/entities/ApplicationSignedDocumentEntity.java @@ -0,0 +1,28 @@ +package net.gepafin.tendermanagement.entities; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import lombok.Data; + +@Data +@Entity +@Table(name = "application_signed_document") +public class ApplicationSignedDocumentEntity extends BaseEntity { + + @ManyToOne + @JoinColumn(name = "APPLICATION_ID") + private ApplicationEntity application; + + @Column(name = "FILE_NAME") + private String fileName; + + @Column(name = "FILE_PATH") + private String filePath; + + @Column(name="STATUS") + private String status; + +} diff --git a/src/main/java/net/gepafin/tendermanagement/enums/ApplicationSignedDocumentStatusEnum.java b/src/main/java/net/gepafin/tendermanagement/enums/ApplicationSignedDocumentStatusEnum.java new file mode 100644 index 00000000..c0064d87 --- /dev/null +++ b/src/main/java/net/gepafin/tendermanagement/enums/ApplicationSignedDocumentStatusEnum.java @@ -0,0 +1,18 @@ +package net.gepafin.tendermanagement.enums; + +import com.fasterxml.jackson.annotation.JsonValue; + +public enum ApplicationSignedDocumentStatusEnum { + ACTIVE("ACTIVE"), INACTIVE("INACTIVE"); + + private String value; + + ApplicationSignedDocumentStatusEnum(String value) { + this.value = value; + } + + @JsonValue + public String getValue() { + return value; + } +} diff --git a/src/main/java/net/gepafin/tendermanagement/model/request/CustomPageEvent.java b/src/main/java/net/gepafin/tendermanagement/model/request/CustomPageEvent.java new file mode 100644 index 00000000..72aef09a --- /dev/null +++ b/src/main/java/net/gepafin/tendermanagement/model/request/CustomPageEvent.java @@ -0,0 +1,71 @@ +package net.gepafin.tendermanagement.model.request; + +import com.itextpdf.text.*; +import com.itextpdf.text.pdf.ColumnText; +import com.itextpdf.text.pdf.PdfContentByte; +import com.itextpdf.text.pdf.PdfPageEventHelper; +import com.itextpdf.text.pdf.PdfWriter; + +public class CustomPageEvent extends PdfPageEventHelper { + private String title; + private int totalPages; + + public CustomPageEvent(String title, int totalPages) { + this.title = title; + this.totalPages = totalPages; + } + + @Override + public void onEndPage(PdfWriter writer, Document document) { + PdfContentByte canvas = writer.getDirectContent(); + + // Header - Add a title to each page at the top + if (writer.getPageNumber() > 1) { + Font headerFont = new Font(Font.FontFamily.HELVETICA, 6, Font.BOLD, new BaseColor(113, 121, 126)); // Gray color for header + ColumnText.showTextAligned( + canvas, + Element.ALIGN_LEFT, + new Phrase(title, headerFont), + document.leftMargin(), // Use left margin to align fully to the left + document.getPageSize().getHeight() - 30, // Positioning header near top + 0 // No rotation + ); + } + + // Footer - Add page number at the bottom + String footerText = String.format("Page %d of %d", writer.getPageNumber(), totalPages); + + // Set font for the footer + Font footerFont = new Font(Font.FontFamily.HELVETICA, 10, Font.NORMAL, BaseColor.BLACK); + + // Positioning footer near bottom + ColumnText.showTextAligned(writer.getDirectContent(), + Element.ALIGN_LEFT, + new Phrase(footerText, footerFont), + (document.right() + document.left()) / 2, + document.bottomMargin() - 10, // Positioning footer near bottom + 0); + + // Draw a yellow line below header + if (writer.getPageNumber() > 1) { + canvas.setLineWidth(1.5f); + canvas.setColorStroke(new BaseColor(255, 219, 88)); // Yellow color + float yPos = document.getPageSize().getHeight() - 50f; // Position for the line below header + canvas.moveTo(0, yPos); + canvas.lineTo(document.getPageSize().getWidth(), yPos); + canvas.stroke(); + } + + // Draw another line 50 points above the bottom of the document + canvas.setLineWidth(1.5f); + canvas.setColorStroke(new BaseColor(255, 219, 88)); // Yellow color + float lineYPos = document.bottomMargin() + 25f; // Position for the line above the bottom margin + canvas.moveTo(0, lineYPos); + canvas.lineTo(document.getPageSize().getWidth(), lineYPos); + canvas.stroke(); + } + + public void setTotalPages(int totalPages) { + this.totalPages = totalPages; + } +} diff --git a/src/main/java/net/gepafin/tendermanagement/model/request/FieldLabelValuePairRequest.java b/src/main/java/net/gepafin/tendermanagement/model/request/FieldLabelValuePairRequest.java new file mode 100644 index 00000000..8eb5f7b5 --- /dev/null +++ b/src/main/java/net/gepafin/tendermanagement/model/request/FieldLabelValuePairRequest.java @@ -0,0 +1,14 @@ +package net.gepafin.tendermanagement.model.request; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +public class FieldLabelValuePairRequest { + + private String label; + private Object value; +} + diff --git a/src/main/java/net/gepafin/tendermanagement/model/response/ApplicationSignedDocumentResponse.java b/src/main/java/net/gepafin/tendermanagement/model/response/ApplicationSignedDocumentResponse.java new file mode 100644 index 00000000..64b380f5 --- /dev/null +++ b/src/main/java/net/gepafin/tendermanagement/model/response/ApplicationSignedDocumentResponse.java @@ -0,0 +1,14 @@ +package net.gepafin.tendermanagement.model.response; + +import lombok.Data; +import net.gepafin.tendermanagement.enums.ApplicationSignedDocumentStatusEnum; +import net.gepafin.tendermanagement.model.BaseBean; + +@Data +public class ApplicationSignedDocumentResponse extends BaseBean{ + + private Long applicationId; + private String fileName; + private String filePath; + private ApplicationSignedDocumentStatusEnum status; +} diff --git a/src/main/java/net/gepafin/tendermanagement/model/response/CompanyDelegationResponse.java b/src/main/java/net/gepafin/tendermanagement/model/response/CompanyDelegationResponse.java index 7c23b3b8..f1b69b33 100644 --- a/src/main/java/net/gepafin/tendermanagement/model/response/CompanyDelegationResponse.java +++ b/src/main/java/net/gepafin/tendermanagement/model/response/CompanyDelegationResponse.java @@ -3,9 +3,10 @@ package net.gepafin.tendermanagement.model.response; import lombok.Data; import net.gepafin.tendermanagement.entities.BaseEntity; import net.gepafin.tendermanagement.enums.UserCompanyDelegationStatusEnum; +import net.gepafin.tendermanagement.model.BaseBean; @Data -public class CompanyDelegationResponse extends BaseEntity{ +public class CompanyDelegationResponse extends BaseBean{ private Long userId; private Long companyId; private Long beneficiaryId; diff --git a/src/main/java/net/gepafin/tendermanagement/model/response/UploadFileOnAmazonS3Response.java b/src/main/java/net/gepafin/tendermanagement/model/response/UploadFileOnAmazonS3Response.java new file mode 100644 index 00000000..d0d78428 --- /dev/null +++ b/src/main/java/net/gepafin/tendermanagement/model/response/UploadFileOnAmazonS3Response.java @@ -0,0 +1,14 @@ +package net.gepafin.tendermanagement.model.response; + +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class UploadFileOnAmazonS3Response { + + private String fileName; + + private String filePath; + +} diff --git a/src/main/java/net/gepafin/tendermanagement/repositories/ApplicationSignedDocumentRepository.java b/src/main/java/net/gepafin/tendermanagement/repositories/ApplicationSignedDocumentRepository.java new file mode 100644 index 00000000..35322a6a --- /dev/null +++ b/src/main/java/net/gepafin/tendermanagement/repositories/ApplicationSignedDocumentRepository.java @@ -0,0 +1,13 @@ +package net.gepafin.tendermanagement.repositories; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import net.gepafin.tendermanagement.entities.ApplicationSignedDocumentEntity; + +@Repository +public interface ApplicationSignedDocumentRepository extends JpaRepository { + + ApplicationSignedDocumentEntity findByApplicationIdAndStatus(Long applicationId, String status); + +} diff --git a/src/main/java/net/gepafin/tendermanagement/repositories/DocumentRepository.java b/src/main/java/net/gepafin/tendermanagement/repositories/DocumentRepository.java index 339efd13..1543def3 100644 --- a/src/main/java/net/gepafin/tendermanagement/repositories/DocumentRepository.java +++ b/src/main/java/net/gepafin/tendermanagement/repositories/DocumentRepository.java @@ -20,6 +20,7 @@ public interface DocumentRepository extends JpaRepository Optional findByIdAndSourceIdAndIsDeletedFalse(Long id, Long sourceId); List findBySource(String source); + List findBySourceIdAndSourceAndTypeAndIsDeletedFalse(Long sourceId, String source, String type); } diff --git a/src/main/java/net/gepafin/tendermanagement/service/AmazonS3Service.java b/src/main/java/net/gepafin/tendermanagement/service/AmazonS3Service.java index ce34cd7d..ea3937a3 100644 --- a/src/main/java/net/gepafin/tendermanagement/service/AmazonS3Service.java +++ b/src/main/java/net/gepafin/tendermanagement/service/AmazonS3Service.java @@ -3,6 +3,8 @@ package net.gepafin.tendermanagement.service; import org.springframework.stereotype.Component; import org.springframework.web.multipart.MultipartFile; +import net.gepafin.tendermanagement.model.response.UploadFileOnAmazonS3Response; + import java.io.IOException; import java.io.InputStream; @@ -14,4 +16,6 @@ public interface AmazonS3Service { public Boolean delete(String s3Folder, String fileName); InputStream getFile(String s3Folder, String filePath) throws IOException; + + public UploadFileOnAmazonS3Response uploadFileOnAmazonS3(String s3Folder, MultipartFile file); } \ No newline at end of file diff --git a/src/main/java/net/gepafin/tendermanagement/service/ApplicationService.java b/src/main/java/net/gepafin/tendermanagement/service/ApplicationService.java index edb62c93..2d914b64 100644 --- a/src/main/java/net/gepafin/tendermanagement/service/ApplicationService.java +++ b/src/main/java/net/gepafin/tendermanagement/service/ApplicationService.java @@ -9,10 +9,13 @@ import net.gepafin.tendermanagement.model.request.ApplicationRequestBean; import net.gepafin.tendermanagement.model.response.ApplicationGetResponseBean; import net.gepafin.tendermanagement.model.response.ApplicationResponse; import net.gepafin.tendermanagement.model.response.ApplicationResponseBean; +import net.gepafin.tendermanagement.model.response.ApplicationSignedDocumentResponse; import net.gepafin.tendermanagement.model.response.NextOrPreviousFormResponse; import java.util.List; +import org.springframework.web.multipart.MultipartFile; + public interface ApplicationService { public ApplicationResponseBean createApplication(HttpServletRequest request, ApplicationRequestBean applicationRequestBean,Long applicationId, Long formId); @@ -31,4 +34,10 @@ public interface ApplicationService { public ApplicationResponse updateApplicationStatus(HttpServletRequest request, Long applicationId, ApplicationStatusTypeEnum status); + public ApplicationSignedDocumentResponse uploadSignedDocument(HttpServletRequest request, Long applicationId, MultipartFile file); + + public ApplicationSignedDocumentResponse getSignedDocument(HttpServletRequest request, Long applicationId); + + public void deleteSignedDocument(HttpServletRequest request, Long applicationId); + } diff --git a/src/main/java/net/gepafin/tendermanagement/service/CallService.java b/src/main/java/net/gepafin/tendermanagement/service/CallService.java index aa6ea872..9a62d89d 100644 --- a/src/main/java/net/gepafin/tendermanagement/service/CallService.java +++ b/src/main/java/net/gepafin/tendermanagement/service/CallService.java @@ -32,5 +32,5 @@ public interface CallService { CallEntity validateCall(Long callId); CallEntity validatePublishedCall(Long callId); - + byte[] downloadCallDocumentsAsZip(Long callId); } diff --git a/src/main/java/net/gepafin/tendermanagement/service/PdfService.java b/src/main/java/net/gepafin/tendermanagement/service/PdfService.java new file mode 100644 index 00000000..e4e6b528 --- /dev/null +++ b/src/main/java/net/gepafin/tendermanagement/service/PdfService.java @@ -0,0 +1,9 @@ +package net.gepafin.tendermanagement.service; + +import jakarta.servlet.http.HttpServletRequest; + + +public interface PdfService { + + public byte[] generatePdf(HttpServletRequest request, Long applicationId); +} diff --git a/src/main/java/net/gepafin/tendermanagement/service/impl/AmazonS3ServiceImpl.java b/src/main/java/net/gepafin/tendermanagement/service/impl/AmazonS3ServiceImpl.java index 756e4360..a0470c15 100644 --- a/src/main/java/net/gepafin/tendermanagement/service/impl/AmazonS3ServiceImpl.java +++ b/src/main/java/net/gepafin/tendermanagement/service/impl/AmazonS3ServiceImpl.java @@ -2,7 +2,16 @@ package net.gepafin.tendermanagement.service.impl; import com.amazonaws.services.s3.AmazonS3; import com.amazonaws.services.s3.model.*; + +import net.gepafin.tendermanagement.config.Translator; +import net.gepafin.tendermanagement.constants.GepafinConstant; +import net.gepafin.tendermanagement.model.response.UploadFileOnAmazonS3Response; import net.gepafin.tendermanagement.service.AmazonS3Service; +import net.gepafin.tendermanagement.util.Utils; +import net.gepafin.tendermanagement.web.rest.api.errors.CustomValidationException; +import net.gepafin.tendermanagement.web.rest.api.errors.Status; + +import org.apache.commons.io.FilenameUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.core.env.Environment; @@ -86,4 +95,20 @@ public class AmazonS3ServiceImpl implements AmazonS3Service { throw new IOException("Error getting file from Amazon S3", e); } } + + @Override + public UploadFileOnAmazonS3Response uploadFileOnAmazonS3(String s3Folder, MultipartFile file) { + String extension = FilenameUtils.getExtension(file.getOriginalFilename()); + String fileName = org.springframework.util.StringUtils.cleanPath(file.getOriginalFilename()); + String firstNameContain = fileName.substring(0, fileName.lastIndexOf('.')); + firstNameContain+=Utils.randomKey(5); + fileName = (firstNameContain + "." + extension); + try { + String filepath = upload(fileName, s3Folder, file); + return UploadFileOnAmazonS3Response.builder().fileName(fileName).filePath(filepath).build(); + } catch (Exception e) { + throw new CustomValidationException(Status.VALIDATION_ERROR, + Translator.toLocale(GepafinConstant.UPLOAD_ERROR_S3)); + } + } } \ No newline at end of file diff --git a/src/main/java/net/gepafin/tendermanagement/service/impl/ApplicationServiceImpl.java b/src/main/java/net/gepafin/tendermanagement/service/impl/ApplicationServiceImpl.java index bd6341de..1ea6e7ea 100644 --- a/src/main/java/net/gepafin/tendermanagement/service/impl/ApplicationServiceImpl.java +++ b/src/main/java/net/gepafin/tendermanagement/service/impl/ApplicationServiceImpl.java @@ -13,12 +13,14 @@ import net.gepafin.tendermanagement.model.request.ApplicationRequestBean; import net.gepafin.tendermanagement.model.response.ApplicationGetResponseBean; import net.gepafin.tendermanagement.model.response.ApplicationResponse; import net.gepafin.tendermanagement.model.response.ApplicationResponseBean; +import net.gepafin.tendermanagement.model.response.ApplicationSignedDocumentResponse; import net.gepafin.tendermanagement.model.response.NextOrPreviousFormResponse; import net.gepafin.tendermanagement.service.ApplicationService; import net.gepafin.tendermanagement.util.Validator; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; import java.util.List; @@ -64,7 +66,7 @@ public class ApplicationServiceImpl implements ApplicationService { @Transactional(rollbackFor = Exception.class) public ApplicationResponse createApplication(HttpServletRequest request, Long companyId, ApplicationRequest applicationRequest, Long callId) { UserEntity userEntity = validator.validateUser(request); - CompanyEntity companyEntity = validator.validateUSerWithCompany(request, companyId); + CompanyEntity companyEntity = validator.validateUserWithCompany(request, companyId); return applicationDao.createApplicationByCallId(companyEntity, applicationRequest, callId, userEntity); } @@ -88,8 +90,26 @@ public class ApplicationServiceImpl implements ApplicationService { public List getAllApplications(HttpServletRequest request, Long callId, Long companyId) { UserEntity userEntity = validator.validateUser(request); if (companyId != null) { - validator.validateUSerWithCompany(request, companyId); + validator.validateUserWithCompany(request, companyId); } return applicationDao.getAllApplications(userEntity, callId, companyId); } + @Override + @Transactional(rollbackFor = Exception.class) + public ApplicationSignedDocumentResponse uploadSignedDocument(HttpServletRequest request, Long applicationId, MultipartFile file) { + return applicationDao.uploadSignedDocument(request, applicationId, file); + } + + @Override + @Transactional(readOnly = true) + public ApplicationSignedDocumentResponse getSignedDocument(HttpServletRequest request, Long applicationId) { + return applicationDao.getSignedDocument(request, applicationId); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void deleteSignedDocument(HttpServletRequest request, Long applicationId) { + applicationDao.deleteSignedDocument(request, applicationId); + } + } diff --git a/src/main/java/net/gepafin/tendermanagement/service/impl/CallServiceImpl.java b/src/main/java/net/gepafin/tendermanagement/service/impl/CallServiceImpl.java index a6a6a901..f6ca0b66 100644 --- a/src/main/java/net/gepafin/tendermanagement/service/impl/CallServiceImpl.java +++ b/src/main/java/net/gepafin/tendermanagement/service/impl/CallServiceImpl.java @@ -92,4 +92,9 @@ public class CallServiceImpl implements CallService { public CallEntity validatePublishedCall(Long callId) { return callDao.validatePublishedCall(callId); } + @Override + @Transactional(readOnly = true) + public byte[] downloadCallDocumentsAsZip(Long callId) { + return callDao.downloadCallDocumentsAsZip(callId); + } } diff --git a/src/main/java/net/gepafin/tendermanagement/service/impl/CompanyServiceImpl.java b/src/main/java/net/gepafin/tendermanagement/service/impl/CompanyServiceImpl.java index 1f80b223..033ddba3 100644 --- a/src/main/java/net/gepafin/tendermanagement/service/impl/CompanyServiceImpl.java +++ b/src/main/java/net/gepafin/tendermanagement/service/impl/CompanyServiceImpl.java @@ -98,7 +98,8 @@ public class CompanyServiceImpl implements CompanyService { @Override @Transactional(rollbackFor = Exception.class) public CompanyDelegationResponse uploadCompanyDelegation(HttpServletRequest request, Long companyId, MultipartFile file) { - UserEntity userEntity =validator.validateUser(request); + UserEntity userEntity = validator.validateUser(request); + validator.validateUserWithCompany(request, companyId); return delegationDao.uploadCompanyDelegation(userEntity, companyId, file); } diff --git a/src/main/java/net/gepafin/tendermanagement/service/impl/PdfServiceImpl.java b/src/main/java/net/gepafin/tendermanagement/service/impl/PdfServiceImpl.java new file mode 100644 index 00000000..b075584d --- /dev/null +++ b/src/main/java/net/gepafin/tendermanagement/service/impl/PdfServiceImpl.java @@ -0,0 +1,24 @@ +package net.gepafin.tendermanagement.service.impl; + +import jakarta.servlet.http.HttpServletRequest; +import net.gepafin.tendermanagement.dao.PdfDao; +import net.gepafin.tendermanagement.entities.UserEntity; +import net.gepafin.tendermanagement.service.PdfService; +import net.gepafin.tendermanagement.util.Validator; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +public class PdfServiceImpl implements PdfService { + + @Autowired + private PdfDao pdfDao; + + @Autowired + private Validator validator; + + @Override + public byte[] generatePdf(HttpServletRequest request, Long applicationId) { + return pdfDao.generatePdf(request, applicationId); + } +} diff --git a/src/main/java/net/gepafin/tendermanagement/util/Validator.java b/src/main/java/net/gepafin/tendermanagement/util/Validator.java index e123988a..bb7d4053 100644 --- a/src/main/java/net/gepafin/tendermanagement/util/Validator.java +++ b/src/main/java/net/gepafin/tendermanagement/util/Validator.java @@ -9,6 +9,7 @@ import net.gepafin.tendermanagement.entities.UserEntity; import net.gepafin.tendermanagement.enums.RoleStatusEnum; import net.gepafin.tendermanagement.service.CompanyService; import net.gepafin.tendermanagement.service.UserService; +import net.gepafin.tendermanagement.web.rest.api.errors.ForbiddenAccessException; import net.gepafin.tendermanagement.web.rest.api.errors.Status; import net.gepafin.tendermanagement.web.rest.api.errors.UnauthorizedAccessException; @@ -60,7 +61,7 @@ public class Validator { } } - public CompanyEntity validateUSerWithCompany(HttpServletRequest request, Long companyId) { + public CompanyEntity validateUserWithCompany(HttpServletRequest request, Long companyId) { if (checkIsSuperAdmin()) { return companyService.validateCompany(companyId); } @@ -89,7 +90,7 @@ public class Validator { public UserEntity validateUserId(HttpServletRequest request, Long userId) { UserEntity user = validateUser(request); if(user.getRoleEntity().getRoleType().equals(RoleStatusEnum.ROLE_BENEFICIARY.getValue()) && Boolean.FALSE.equals(user.getId().equals(userId))) { - throw new UnauthorizedAccessException(Status.UNAUTHORIZED, Translator.toLocale(GepafinConstant.INVALID_REQUEST)); + throw new ForbiddenAccessException(Status.FORBIDDEN, Translator.toLocale(GepafinConstant.PERMISSION_DENIED)); } return userService.validateUser(userId); } diff --git a/src/main/java/net/gepafin/tendermanagement/web/rest/api/ApplicationApi.java b/src/main/java/net/gepafin/tendermanagement/web/rest/api/ApplicationApi.java index 1869655c..e3be06c7 100644 --- a/src/main/java/net/gepafin/tendermanagement/web/rest/api/ApplicationApi.java +++ b/src/main/java/net/gepafin/tendermanagement/web/rest/api/ApplicationApi.java @@ -6,6 +6,7 @@ import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; @@ -21,6 +22,8 @@ import net.gepafin.tendermanagement.model.request.ApplicationRequestBean; import net.gepafin.tendermanagement.model.response.ApplicationGetResponseBean; import net.gepafin.tendermanagement.model.response.ApplicationResponse; import net.gepafin.tendermanagement.model.response.ApplicationResponseBean; +import net.gepafin.tendermanagement.model.response.ApplicationSignedDocumentResponse; +import net.gepafin.tendermanagement.model.response.CompanyDelegationResponse; import net.gepafin.tendermanagement.model.response.NextOrPreviousFormResponse; import net.gepafin.tendermanagement.model.util.Response; import net.gepafin.tendermanagement.web.rest.api.errors.ErrorConstants; @@ -130,6 +133,58 @@ public interface ApplicationApi { ResponseEntity> updateApplicationStatus(HttpServletRequest request, @Parameter(description = "The application id", required = true) @PathVariable("applicationId") Long applicationId, @Parameter(description = "status", required = true)@RequestParam(value = "status", required = true) ApplicationStatusTypeEnum status); + + @Operation(summary = "API to generate PDF for an application", + responses = { + @ApiResponse(responseCode = "200", description = "OK", content = @Content(mediaType = "application/pdf")), + @ApiResponse(responseCode = "404", description = "Not Found", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, examples = { + @ExampleObject(value = ErrorConstants.NOTFOUND_ERROR_EXAMPLE) })), + @ApiResponse(responseCode = "401", description = "Unauthorized", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, examples = { + @ExampleObject(value = ErrorConstants.UNAUTHORIZED_ERROR_EXAMPLE) })), + @ApiResponse(responseCode = "400", description = "Bad Request", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, examples = { + @ExampleObject(value = ErrorConstants.BADREQUEST_ERROR_EXAMPLE) })) + }) + @PostMapping(value = "/{applicationId}/download-pdf", + produces = { "application/pdf" }) + public ResponseEntity generateApplicationPdf( + HttpServletRequest request, + @Parameter(description = "The application id", required = true) + @PathVariable(value = "applicationId", required = true) Long applicationId); + + @Operation(summary = "Api to upload signed document (only p7m file format is supported)", responses = { @ApiResponse(responseCode = "200", description = "OK"), + @ApiResponse(responseCode = "404", description = "Not Found", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, examples = { + @ExampleObject(value = ErrorConstants.NOTFOUND_ERROR_EXAMPLE) })), + @ApiResponse(responseCode = "401", description = "Unauthorized", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, examples = { + @ExampleObject(value = ErrorConstants.UNAUTHORIZED_ERROR_EXAMPLE) })), + @ApiResponse(responseCode = "400", description = "Bad Request", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, examples = { + @ExampleObject(value = ErrorConstants.BADREQUEST_ERROR_EXAMPLE) })) }) + @PostMapping(value = "{applicationId}/signedDocument/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + ResponseEntity> uploadSignedDocument(HttpServletRequest request, + @Parameter(description = "The applicationId id", required = true) @PathVariable("applicationId") Long applicationId, + @Parameter(description = "The signed document", required = true) @RequestParam("file") MultipartFile file); + + + @Operation(summary = "Api to get signed document", responses = { @ApiResponse(responseCode = "200", description = "OK"), + @ApiResponse(responseCode = "404", description = "Not Found", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, examples = { + @ExampleObject(value = ErrorConstants.NOTFOUND_ERROR_EXAMPLE) })), + @ApiResponse(responseCode = "401", description = "Unauthorized", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, examples = { + @ExampleObject(value = ErrorConstants.UNAUTHORIZED_ERROR_EXAMPLE) })), + @ApiResponse(responseCode = "400", description = "Bad Request", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, examples = { + @ExampleObject(value = ErrorConstants.BADREQUEST_ERROR_EXAMPLE) })) }) + @GetMapping(value = "{applicationId}/signedDocument", produces = "application/json") + ResponseEntity> getSignedDocument(HttpServletRequest request, + @Parameter(description = "The applicationId id", required = true) @PathVariable("applicationId") Long applicationId); + + @Operation(summary = "Api to delete signed document", responses = { @ApiResponse(responseCode = "200", description = "OK"), + @ApiResponse(responseCode = "404", description = "Not Found", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, examples = { + @ExampleObject(value = ErrorConstants.NOTFOUND_ERROR_EXAMPLE) })), + @ApiResponse(responseCode = "401", description = "Unauthorized", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, examples = { + @ExampleObject(value = ErrorConstants.UNAUTHORIZED_ERROR_EXAMPLE) })), + @ApiResponse(responseCode = "400", description = "Bad Request", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, examples = { + @ExampleObject(value = ErrorConstants.BADREQUEST_ERROR_EXAMPLE) })) }) + @DeleteMapping(value = "{applicationId}/signedDocument", produces = "application/json") + ResponseEntity> deleteSignedDocument(HttpServletRequest request, + @Parameter(description = "The applicationId id", required = true) @PathVariable("applicationId") Long applicationId); } diff --git a/src/main/java/net/gepafin/tendermanagement/web/rest/api/CallApi.java b/src/main/java/net/gepafin/tendermanagement/web/rest/api/CallApi.java index cc7f985a..3e2861ff 100644 --- a/src/main/java/net/gepafin/tendermanagement/web/rest/api/CallApi.java +++ b/src/main/java/net/gepafin/tendermanagement/web/rest/api/CallApi.java @@ -134,4 +134,19 @@ public interface CallApi { public ResponseEntity> updateCallStatus(HttpServletRequest request, @Parameter(description = "The call id", required = true) @PathVariable("callId") Long callId, @Parameter(description = "status", required = true)@RequestParam(value = "status", required = true) CallStatusEnum status); + + @Operation(summary = "Api to download call documents as a ZIP file", + responses = { + @ApiResponse(responseCode = "200", description = "OK"), + @ApiResponse(responseCode = "404", description = "Not Found", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, examples = { + @ExampleObject(value = ErrorConstants.NOTFOUND_ERROR_EXAMPLE)})), + @ApiResponse(responseCode = "401", description = "Unauthorized", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, examples = { + @ExampleObject(value = ErrorConstants.UNAUTHORIZED_ERROR_EXAMPLE)})), + @ApiResponse(responseCode = "400", description = "Bad Request", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, examples = { + @ExampleObject(value = ErrorConstants.BADREQUEST_ERROR_EXAMPLE)})) + }) + @GetMapping(value = "/{callId}/documents/zip") + ResponseEntity downloadCallDocumentsAsZip(HttpServletRequest httpServletRequest, + @Parameter(description = "The call ID", required = true) @PathVariable("callId") Long callId); + } diff --git a/src/main/java/net/gepafin/tendermanagement/web/rest/api/PdfApi.java b/src/main/java/net/gepafin/tendermanagement/web/rest/api/PdfApi.java new file mode 100644 index 00000000..a8f7ea48 --- /dev/null +++ b/src/main/java/net/gepafin/tendermanagement/web/rest/api/PdfApi.java @@ -0,0 +1,35 @@ +package net.gepafin.tendermanagement.web.rest.api; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.ExampleObject; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import jakarta.servlet.http.HttpServletRequest; +import net.gepafin.tendermanagement.web.rest.api.errors.ErrorConstants; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; + +@Validated +public interface PdfApi { + + @Operation(summary = "API to generate PDF for an application", + responses = { + @ApiResponse(responseCode = "200", description = "OK", content = @Content(mediaType = "application/pdf")), + @ApiResponse(responseCode = "404", description = "Not Found", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, examples = { + @ExampleObject(value = ErrorConstants.NOTFOUND_ERROR_EXAMPLE) })), + @ApiResponse(responseCode = "401", description = "Unauthorized", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, examples = { + @ExampleObject(value = ErrorConstants.UNAUTHORIZED_ERROR_EXAMPLE) })), + @ApiResponse(responseCode = "400", description = "Bad Request", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, examples = { + @ExampleObject(value = ErrorConstants.BADREQUEST_ERROR_EXAMPLE) })) + }) + @PostMapping(value = "/{applicationId}/generate-pdf", + produces = { "application/pdf" }) + public ResponseEntity generateApplicationPdf( + HttpServletRequest request, + @Parameter(description = "The application id", required = true) + @PathVariable(value = "applicationId", required = true) Long applicationId); +} diff --git a/src/main/java/net/gepafin/tendermanagement/web/rest/api/UserApi.java b/src/main/java/net/gepafin/tendermanagement/web/rest/api/UserApi.java index 18281f96..6bb6a388 100644 --- a/src/main/java/net/gepafin/tendermanagement/web/rest/api/UserApi.java +++ b/src/main/java/net/gepafin/tendermanagement/web/rest/api/UserApi.java @@ -218,6 +218,12 @@ public interface UserApi { produces = { "application/json" }) ResponseEntity> validateNewUserToken(HttpServletRequest request, @Parameter(description = "The spid token", required = true) @PathVariable("token") String token); + + + + @RequestMapping("favicon.ico") + @ResponseBody + void returnNoFavicon(); diff --git a/src/main/java/net/gepafin/tendermanagement/web/rest/api/impl/ApplicationApiController.java b/src/main/java/net/gepafin/tendermanagement/web/rest/api/impl/ApplicationApiController.java index bcbf8e80..f6e6d1e5 100644 --- a/src/main/java/net/gepafin/tendermanagement/web/rest/api/impl/ApplicationApiController.java +++ b/src/main/java/net/gepafin/tendermanagement/web/rest/api/impl/ApplicationApiController.java @@ -10,17 +10,22 @@ import net.gepafin.tendermanagement.model.request.ApplicationRequestBean; import net.gepafin.tendermanagement.model.response.ApplicationGetResponseBean; import net.gepafin.tendermanagement.model.response.ApplicationResponse; import net.gepafin.tendermanagement.model.response.ApplicationResponseBean; +import net.gepafin.tendermanagement.model.response.ApplicationSignedDocumentResponse; import net.gepafin.tendermanagement.model.response.NextOrPreviousFormResponse; import net.gepafin.tendermanagement.model.util.Response; import net.gepafin.tendermanagement.service.ApplicationService; +import net.gepafin.tendermanagement.service.PdfService; import net.gepafin.tendermanagement.web.rest.api.ApplicationApi; import net.gepafin.tendermanagement.web.rest.api.errors.Status; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; import org.slf4j.Logger; import java.util.List; @@ -35,17 +40,21 @@ public class ApplicationApiController implements ApplicationApi { @Autowired private ApplicationService applicationService; + @Autowired + private PdfService pdfService; + @Override - public ResponseEntity> createApplication(HttpServletRequest request, ApplicationRequestBean applicationRequestBean,Long applicationId, Long formId) { - ApplicationResponseBean applicationResponseBean= applicationService.createApplication(request,applicationRequestBean,applicationId,formId); + public ResponseEntity> createApplication(HttpServletRequest request, ApplicationRequestBean applicationRequestBean, Long applicationId, Long formId) { + ApplicationResponseBean applicationResponseBean = applicationService.createApplication(request, applicationRequestBean, applicationId, formId); return ResponseEntity.status(HttpStatus.CREATED) - .body(new Response<>(applicationResponseBean, Status.SUCCESS, Translator.toLocale(GepafinConstant.APPLICATION_CREATED_SUCCESS_MSG))); } + .body(new Response<>(applicationResponseBean, Status.SUCCESS, Translator.toLocale(GepafinConstant.APPLICATION_CREATED_SUCCESS_MSG))); + } @Override public ResponseEntity> getApplicationByFormId(HttpServletRequest request - , Long applicationId,Long formId) { + , Long applicationId, Long formId) { log.info("Get Application by ID - Application ID: {}", applicationId); - ApplicationGetResponseBean application = applicationService.getApplicationByFormId(request,applicationId,formId); + ApplicationGetResponseBean application = applicationService.getApplicationByFormId(request, applicationId, formId); return ResponseEntity.status(HttpStatus.OK) .body(new Response<>(application, Status.SUCCESS, Translator.toLocale(GepafinConstant.GET_APPLICATION_SUCCESS_MSG))); } @@ -54,39 +63,83 @@ public class ApplicationApiController implements ApplicationApi { public ResponseEntity> deleteApplication(HttpServletRequest request, Long applicationId) { log.info("Delete Application - Application ID: {}", applicationId); - applicationService.deleteApplication(request,applicationId); + applicationService.deleteApplication(request, applicationId); return ResponseEntity.status(HttpStatus.OK) .body(new Response<>(null, Status.SUCCESS, Translator.toLocale(GepafinConstant.DELETE_APPLICATION_SUCCESS_MSG))); } @Override public ResponseEntity> createApplicationByCallId(HttpServletRequest request, Long companyId, ApplicationRequest applicationRequest, Long callId) { - ApplicationResponse applicationResponseBean=applicationService.createApplication(request, companyId, applicationRequest, callId); + ApplicationResponse applicationResponseBean = applicationService.createApplication(request, companyId, applicationRequest, callId); return ResponseEntity.status(HttpStatus.CREATED) .body(new Response<>(applicationResponseBean, Status.SUCCESS, Translator.toLocale(GepafinConstant.APPLICATION_CREATED_SUCCESS_MSG))); } + @Override - public ResponseEntity>> getAllApplications(HttpServletRequest request,Long callId,Long companyId) { - List applications = applicationService.getAllApplications(request,callId,companyId); + public ResponseEntity>> getAllApplications(HttpServletRequest request, Long callId, Long companyId) { + List applications = applicationService.getAllApplications(request, callId, companyId); log.info("Get All Applications"); return ResponseEntity.status(HttpStatus.OK) .body(new Response<>(applications, Status.SUCCESS, Translator.toLocale(GepafinConstant.GET_APPLICATION_SUCCESS_MSG))); } - + @Override - public ResponseEntity> getNextOrPreviousForm(HttpServletRequest request,Long applicationId, - Long formId, FormActionEnum action) { - NextOrPreviousFormResponse data = applicationService.getNextOrPreviousForm(request, applicationId, formId, action); - log.info("Get Next Or Previous Form "); + public ResponseEntity> getNextOrPreviousForm(HttpServletRequest request, Long applicationId, + Long formId, FormActionEnum action) { + NextOrPreviousFormResponse data = applicationService.getNextOrPreviousForm(request, applicationId, formId, action); + log.info("Get Next Or Previous Form "); return ResponseEntity.status(HttpStatus.OK) .body(new Response<>(data, Status.SUCCESS, Translator.toLocale(GepafinConstant.GET_APPLICATION_SUCCESS_MSG))); } - + @Override public ResponseEntity> updateApplicationStatus(HttpServletRequest request, Long applicationId, - ApplicationStatusTypeEnum status) { + ApplicationStatusTypeEnum status) { ApplicationResponse applicationResponse = applicationService.updateApplicationStatus(request, applicationId, status); - return ResponseEntity.status(HttpStatus.OK) - .body(new Response<>(applicationResponse, Status.SUCCESS, Translator.toLocale(GepafinConstant.APPLICATION_STATUS_UPDATED_SUCCESSFULLY))); + return ResponseEntity.status(HttpStatus.OK) + .body(new Response<>(applicationResponse, Status.SUCCESS, Translator.toLocale(GepafinConstant.APPLICATION_STATUS_UPDATED_SUCCESSFULLY))); } + + @Override + public ResponseEntity generateApplicationPdf(HttpServletRequest request, Long applicationId) { + byte[] pdfBytes = pdfService.generatePdf(request, applicationId); + + // Prepare headers for downloading the PDF + HttpHeaders headers = new HttpHeaders(); + headers.add("Content-Disposition", "attachment; filename=bando-preview.pdf"); + + // Return the PDF as a response + return ResponseEntity.ok() + .headers(headers) + .contentType(MediaType.APPLICATION_PDF) + .body(pdfBytes); + } + + @Override + public ResponseEntity> uploadSignedDocument(HttpServletRequest request, + Long applicationId, MultipartFile file) { + log.info("upload signed document applicationId: {}", applicationId); + ApplicationSignedDocumentResponse response = applicationService.uploadSignedDocument(request, applicationId, file); + return ResponseEntity.status(HttpStatus.OK) + .body(new Response<>(response, Status.SUCCESS, Translator.toLocale(GepafinConstant.SIGNED_DOCUMENT_FILE_UPLOAD_SUCCESS))); + } + + @Override + public ResponseEntity> getSignedDocument(HttpServletRequest request, + Long applicationId) { + ApplicationSignedDocumentResponse response = applicationService.getSignedDocument(request, applicationId); + log.info("get signed document applicationId: {}", applicationId); + return ResponseEntity.status(HttpStatus.OK) + .body(new Response<>(response, Status.SUCCESS, Translator.toLocale(GepafinConstant.GET_SIGNED_DOCUMENT_FILE_SUCCESS))); + } + + @Override + public ResponseEntity> deleteSignedDocument(HttpServletRequest request, + Long applicationId) { + applicationService.deleteSignedDocument(request, applicationId); + log.info("delete signed document applicationId: {}", applicationId); + return ResponseEntity.status(HttpStatus.OK) + .body(new Response<>(null, Status.SUCCESS, Translator.toLocale(GepafinConstant.DELETE_SIGNED_DOCUMENT_FILE_SUCCESS))); + } + } diff --git a/src/main/java/net/gepafin/tendermanagement/web/rest/api/impl/CallApiController.java b/src/main/java/net/gepafin/tendermanagement/web/rest/api/impl/CallApiController.java index e4680428..926dccab 100644 --- a/src/main/java/net/gepafin/tendermanagement/web/rest/api/impl/CallApiController.java +++ b/src/main/java/net/gepafin/tendermanagement/web/rest/api/impl/CallApiController.java @@ -4,7 +4,9 @@ import java.util.List; import net.gepafin.tendermanagement.enums.CallStatusEnum; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.RequestMapping; @@ -84,4 +86,22 @@ public class CallApiController implements CallApi { CallResponse updateCall = callService.updateCallStatus(request, callId, status); return ResponseEntity.ok(new Response<>(updateCall, Status.SUCCESS, Translator.toLocale(GepafinConstant.UPDATE_CALL_STATUS_SUCCESS_MSG))); } + @Override + public ResponseEntity downloadCallDocumentsAsZip(HttpServletRequest request, Long callId) { + byte[] zipFile = callService.downloadCallDocumentsAsZip(callId); + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_OCTET_STREAM); + headers.setContentDispositionFormData("attachment", "documents.zip"); + + if (zipFile == null || zipFile.length == 0) { + String notFoundMessage = Translator.toLocale(GepafinConstant.CALL_DOCUMENTS_NOT_FOUND_MSG); + return ResponseEntity.status(HttpStatus.NOT_FOUND) + .body(notFoundMessage.getBytes()); + } + + return new ResponseEntity<>(zipFile, headers, HttpStatus.OK); + } + + } \ No newline at end of file diff --git a/src/main/java/net/gepafin/tendermanagement/web/rest/api/impl/PdfController.java b/src/main/java/net/gepafin/tendermanagement/web/rest/api/impl/PdfController.java new file mode 100644 index 00000000..f14451ed --- /dev/null +++ b/src/main/java/net/gepafin/tendermanagement/web/rest/api/impl/PdfController.java @@ -0,0 +1,40 @@ +package net.gepafin.tendermanagement.web.rest.api.impl; + +import com.itextpdf.text.*; +import com.itextpdf.text.pdf.*; +import jakarta.servlet.http.HttpServletRequest; +import net.gepafin.tendermanagement.dao.RoundedCorners; +import net.gepafin.tendermanagement.service.PdfService; +import net.gepafin.tendermanagement.web.rest.api.PdfApi; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; + +@RestController +public class PdfController implements PdfApi { + + @Autowired + private PdfService pdfService; + + @Override + public ResponseEntity generateApplicationPdf(HttpServletRequest request, Long applicationId) { + byte[] pdfBytes =pdfService.generatePdf(request,applicationId); + + // Prepare headers for downloading the PDF + HttpHeaders headers = new HttpHeaders(); + headers.add("Content-Disposition", "attachment; filename=bando-preview.pdf"); + + // Return the PDF as a response + return ResponseEntity.ok() + .headers(headers) + .contentType(MediaType.APPLICATION_PDF) + .body(pdfBytes); + } +} + diff --git a/src/main/java/net/gepafin/tendermanagement/web/rest/api/impl/UserApiController.java b/src/main/java/net/gepafin/tendermanagement/web/rest/api/impl/UserApiController.java index ca13268a..4ffd3e87 100644 --- a/src/main/java/net/gepafin/tendermanagement/web/rest/api/impl/UserApiController.java +++ b/src/main/java/net/gepafin/tendermanagement/web/rest/api/impl/UserApiController.java @@ -140,4 +140,10 @@ public class UserApiController implements UserApi { return ResponseEntity.ok(new Response<>(data, Status.SUCCESS, Translator.toLocale(GepafinConstant.TOKEN_VALIDATE_SUCCESS_MSE))); } + + @Override + public void returnNoFavicon() { + // Do nothing + } + } \ No newline at end of file diff --git a/src/main/resources/application-production.properties b/src/main/resources/application-production.properties index 986dd51b..91fb1979 100644 --- a/src/main/resources/application-production.properties +++ b/src/main/resources/application-production.properties @@ -6,12 +6,12 @@ spring.datasource.driver-class-name=org.postgresql.Driver # JPA Configuration spring.h2.console.enabled=true -base-url=http://bandi-api.gepafin.it +base-url=https://bandi-api.gepafin.it isVatCheckGloballyDisabled = false #fe.base.url=http://gepafin-production-fe.s3-website.eu-central-1.amazonaws.com -fe.base.url=http://bandi.gepafin.it +fe.base.url=https://bandi.gepafin.it #SPID configuration spid.ipd.base.url=https://login.regione.umbria.it active.profile.folder=production -isMailSendingEnabled = true \ No newline at end of file +isMailSendingEnabled = true diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index f50d4088..11d23d5d 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -32,6 +32,7 @@ aws.s3.bucket.name=mementoresources aws.s3.url = https://mementoresources.s3.eu-west-1.amazonaws.com/ aws.s3.url.folder=gepafin aws.s3.url.folder.delegation=gepafin/delegation +aws.s3.url.folder.signed.document=gepafin/signed-document # JWT configuration # Ensure these values match your expectations security.authentication.jwt.secret=my-secret-token-to-change-in-prod-environment-your-super-secure-randomly-generated-key diff --git a/src/main/resources/db/changelog/db.changelog-1.0.0.xml b/src/main/resources/db/changelog/db.changelog-1.0.0.xml index 446334d6..86f1097e 100644 --- a/src/main/resources/db/changelog/db.changelog-1.0.0.xml +++ b/src/main/resources/db/changelog/db.changelog-1.0.0.xml @@ -997,5 +997,29 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/message_en.properties b/src/main/resources/message_en.properties index 0d68c6d7..d09b9d29 100644 --- a/src/main/resources/message_en.properties +++ b/src/main/resources/message_en.properties @@ -226,5 +226,13 @@ user.company.relation.not.found=User with the specified company relation not fou delegation.delete.success=Delegation deleted successfully. user.not.authorized.create.application=User must be a legal representative or have delegation. application.submitted.cannot.change=The submitted application cannot be changed. +# Call Document Messages +call.documents.fetch.success=Documents fetched successfully. +call.documents.not.found=No documents found for the specified call. +permission.denied=You are not authorized to access this data. +signed.document.file.upload.success=Signed document file uploaded successfully. +get.signed.document.file.success=Signed document file retrieved successfully. +application.signed.document.not.found=Signed document for the application not found. +delete.signed.document.file.success=Signed document deleted successfully. diff --git a/src/main/resources/message_it.properties b/src/main/resources/message_it.properties index 3e665e82..1b9b3203 100644 --- a/src/main/resources/message_it.properties +++ b/src/main/resources/message_it.properties @@ -222,5 +222,13 @@ delegation.delete.success=Delega eliminata con successo. user.not.authorized.create.application=L'utente deve essere un rappresentante legale o avere una delega. application.submitted.cannot.change=La domanda inviata non pu essere modificata. +# Call Document Messages +call.documents.fetch.success=Documenti recuperati con successo. +call.documents.not.found=Nessun documento trovato per la chiamata specificata. +permission.denied=Non sei autorizzato ad accedere a questi dati. +signed.document.file.upload.success=File del documento firmato caricato con successo. +get.signed.document.file.success=File del documento firmato recuperato con successo. +application.signed.document.not.found=Documento firmato per l'applicazione non trovato. +delete.signed.document.file.success=Documento firmato eliminato con successo.