From 3f6e11e7c4c58f13cbcecc8cdc6ece2dc57e869c Mon Sep 17 00:00:00 2001 From: rajesh Date: Fri, 23 May 2025 15:17:07 +0530 Subject: [PATCH 01/35] Added APOINTMENT status to update application status API --- .../tendermanagement/dao/ApplicationDao.java | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java index f4656197..3a80c27d 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java @@ -208,6 +208,7 @@ public class ApplicationDao { @Autowired private ApplicationEvaluationDao applicationEvaluationDao; + public final Random random = new Random(); public ApplicationResponseBean createApplication(HttpServletRequest request, ApplicationRequestBean applicationRequestBean, Long formId, Long applicationId) { FormEntity formEntity = formService.validateForm(formId); @@ -934,12 +935,16 @@ public class ApplicationDao { } } + public String generateRandomFiveDigitNumber() { + int number = 10000 + random.nextInt(90000); // Generates a number from 10000 to 99999 + return String.valueOf(number); + } public ApplicationResponse updateApplicationStatus(HttpServletRequest request, Long applicationId, ApplicationStatusTypeEnum status) { log.info("Updating status for Application id : " + applicationId); ApplicationEntity applicationEntity = validateApplication(applicationId); - checkCallEndDate(applicationEntity.getCall()); + log.info("Call end date verified successfully | callId: {}", applicationEntity.getCall().getId()); //cloned entity for old application data ApplicationEntity oldApplicationEntity = Utils.getClonedEntityForData(applicationEntity); @@ -953,8 +958,16 @@ public class ApplicationDao { if (Boolean.TRUE.equals(applicationEntity.getStatus().equals(status.getValue()))) { throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.APPLICATION_ALREADY_IN_PREVIOUS_STATUS)); } + + if (status.equals(ApplicationStatusTypeEnum.APPOINTMENT) && Boolean.TRUE.equals(applicationEntity.getStatus().equals(ApplicationStatusTypeEnum.NDG.getValue()))){ + String appointmentId = generateRandomFiveDigitNumber(); + applicationEntity.setAppointmentId(appointmentId); + applicationEntity.setStatus(ApplicationStatusTypeEnum.APPOINTMENT.getValue()); + } + if (status.equals(ApplicationStatusTypeEnum.SUBMIT) && Boolean.TRUE.equals(applicationEntity.getStatus().equals(ApplicationStatusTypeEnum.READY.getValue()))) { // callService.validatePublishedCall(applicationEntity.getCall().getId(), userEntity.getHub().getId()); + checkCallEndDate(applicationEntity.getCall()); Long protocolNumber = protocolDao.getProtocolNumber(userEntity.getHub()); ProtocolEntity protocolEntity = protocolDao.createProtocolEntity(applicationEntity, protocolNumber, userEntity.getHub().getId(),true); applicationEntity.setProtocol(protocolEntity); @@ -974,10 +987,12 @@ public class ApplicationDao { log.info("Status updated to SUBMIT for applicationId: " + applicationId); } if (status.equals(ApplicationStatusTypeEnum.DRAFT) && Boolean.TRUE.equals(applicationEntity.getStatus().equals(ApplicationStatusTypeEnum.AWAITING.getValue()))) { + checkCallEndDate(applicationEntity.getCall()); applicationEntity.setStatus(status.getValue()); log.info("Status updated to DRAFT for applicationId: " + applicationId); } if (status.equals(ApplicationStatusTypeEnum.AWAITING) && Boolean.TRUE.equals(applicationEntity.getStatus().equals(ApplicationStatusTypeEnum.READY.getValue()))) { + checkCallEndDate(applicationEntity.getCall()); ApplicationSignedDocumentEntity applicationSignedDocument = applicationSignedDocumentRepository.findByApplicationIdAndStatus(applicationId, ApplicationSignedDocumentStatusEnum.ACTIVE.getValue()); deleteSignedDocumentFromS3(applicationSignedDocument); From ab30f36f5121c89fddbe2ec7074474058985be9f Mon Sep 17 00:00:00 2001 From: rajesh Date: Mon, 26 May 2025 12:46:18 +0530 Subject: [PATCH 02/35] Fixed close amendment issue --- .../dao/ApplicationAmendmentRequestDao.java | 10 +++++++++- .../ApplicationAmendmentRequestRepository.java | 2 ++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationAmendmentRequestDao.java b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationAmendmentRequestDao.java index 76ce5d3d..02a92762 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationAmendmentRequestDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationAmendmentRequestDao.java @@ -1050,7 +1050,7 @@ public class ApplicationAmendmentRequestDao { public ApplicationAmendmentRequestResponse closeAmendmentRequest(Long id, CloseAmendmentRequest closeAmendmentRequest) { log.info("Closing application amendement with ID: {}", id); - ApplicationAmendmentRequestEntity existingApplicationAmendment = validatApplicationAmendmentRequestByStatus(id,ApplicationAmendmentRequestEnum.AWAITING.getValue()); + ApplicationAmendmentRequestEntity existingApplicationAmendment = validatApplicationAmendmentRequestByListStatus(id,List.of(ApplicationAmendmentRequestEnum.AWAITING.getValue(),ApplicationAmendmentRequestEnum.RESPONSE_RECEIVED.getValue())); //cloned entity for old data and versioning ApplicationAmendmentRequestEntity oldApplicationAmendmentEntity = Utils.getClonedEntityForData(existingApplicationAmendment); List amendmentRequestList = applicationAmendmentRequestRepository.findAllByApplicationEvaluationIdAndIsDeletedFalse( @@ -1670,4 +1670,12 @@ public class ApplicationAmendmentRequestDao { amendment.setEmailSendResponse(mergedResponses); applicationAmendmentRequestRepository.save(amendment); } + public ApplicationAmendmentRequestEntity validatApplicationAmendmentRequestByListStatus(Long id,List status) { + ApplicationAmendmentRequestEntity applicationAmendmentRequestEntity = applicationAmendmentRequestRepository.findByIdAndIsDeletedFalseAndStatusIn(id, status); + if (applicationAmendmentRequestEntity == null) { + throw new ResourceNotFoundException(Status.NOT_FOUND, + Translator.toLocale(GepafinConstant.APPLICATION_AMENDMENT_NOT_FOUND_MSG)); + } + return applicationAmendmentRequestEntity; + } } diff --git a/src/main/java/net/gepafin/tendermanagement/repositories/ApplicationAmendmentRequestRepository.java b/src/main/java/net/gepafin/tendermanagement/repositories/ApplicationAmendmentRequestRepository.java index 4cd6d413..d063eb37 100644 --- a/src/main/java/net/gepafin/tendermanagement/repositories/ApplicationAmendmentRequestRepository.java +++ b/src/main/java/net/gepafin/tendermanagement/repositories/ApplicationAmendmentRequestRepository.java @@ -152,4 +152,6 @@ public interface ApplicationAmendmentRequestRepository extends JpaRepository applicationIds, @Param("statuses") List statuses); ApplicationAmendmentRequestEntity findByIdAndIsDeletedFalseAndStatus(Long id,String status); + + ApplicationAmendmentRequestEntity findByIdAndIsDeletedFalseAndStatusIn(Long id, List statusList); } From 34f26fe44657e7deac38ebdf5255e4320a3b921a Mon Sep 17 00:00:00 2001 From: nisha Date: Wed, 28 May 2025 14:02:00 +0530 Subject: [PATCH 03/35] Done ticket GEPAFINBE-221 --- .../dao/ApplicationEvaluationDao.java | 17 ++++++++++------- .../gepafin/tendermanagement/dao/CallDao.java | 2 +- .../dao/EvaluationCriteriaDao.java | 3 ++- .../entities/EvaluationCriteriaEntity.java | 4 +++- .../model/request/CriteriaRequest.java | 4 +++- .../model/request/EvaluationCriteriaReq.java | 4 +++- .../request/EvaluationCriteriaRequest.java | 4 +++- .../model/response/CriteriaResponse.java | 5 +++-- .../EvaluationCriteriaResponseBean.java | 4 +++- 9 files changed, 31 insertions(+), 16 deletions(-) diff --git a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationEvaluationDao.java b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationEvaluationDao.java index 8d4ce08e..b9782af0 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationEvaluationDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationEvaluationDao.java @@ -2510,8 +2510,8 @@ public class ApplicationEvaluationDao { ApplicationEvaluationEntity evaluationEntity = evaluationEntityOpt.get(); String criteriaJson = evaluationEntity.getCriteria(); if (criteriaJson != null){ - Integer totalScore = calculateTotalScore(evaluationEntity.getCriteria()); - if (totalScore > 40) { + BigDecimal totalScore = calculateTotalScore(evaluationEntity.getCriteria()); + if (totalScore.compareTo(new BigDecimal("40")) > 0) { applicationEntity.setStatus(status.getValue()); log.info("Status updated to TECHNICAL_EVALUATION for applicationId: " + applicationId); } @@ -2522,7 +2522,7 @@ public class ApplicationEvaluationDao { } } - private Integer calculateTotalScore(String criteriaJson){ + private BigDecimal calculateTotalScore(String criteriaJson){ try { ObjectMapper objectMapper = new ObjectMapper(); // Convert JSON string to List of Maps @@ -2530,15 +2530,18 @@ public class ApplicationEvaluationDao { }); // Sum all scores (ignoring null scores) - Integer totalScore = criteriaList.stream() - .mapToInt(obj -> obj.get("score") != null ? ((Number) obj.get("score")).intValue() : 0) - .sum(); + BigDecimal totalScore = criteriaList.stream() + .map(obj -> { + Object score = obj.get("score"); + return score != null ? new BigDecimal(score.toString()) : BigDecimal.ZERO; + }) + .reduce(BigDecimal.ZERO, BigDecimal::add); return totalScore; } catch (Exception e) { log.error(" Error parsing criteria JSON: {}", e.getMessage()); - return 0; + return BigDecimal.ZERO; } } diff --git a/src/main/java/net/gepafin/tendermanagement/dao/CallDao.java b/src/main/java/net/gepafin/tendermanagement/dao/CallDao.java index 3cb86a50..7fc9663a 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/CallDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/CallDao.java @@ -277,7 +277,7 @@ public class CallDao { criteriaEntity = new EvaluationCriteriaEntity(); criteriaEntity.setCall(callEntity); criteriaEntity.setLookupData(lookupDataEntity); - criteriaEntity.setScore(0L); + criteriaEntity.setScore(BigDecimal.ZERO); criteriaEntity.setIsDeleted(false); actionType = VersionActionTypeEnum.INSERT; } diff --git a/src/main/java/net/gepafin/tendermanagement/dao/EvaluationCriteriaDao.java b/src/main/java/net/gepafin/tendermanagement/dao/EvaluationCriteriaDao.java index 8f7897c4..fa59c0ab 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/EvaluationCriteriaDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/EvaluationCriteriaDao.java @@ -20,6 +20,7 @@ import net.gepafin.tendermanagement.util.Utils; import net.gepafin.tendermanagement.web.rest.api.errors.ResourceNotFoundException; import net.gepafin.tendermanagement.web.rest.api.errors.Status; +import java.math.BigDecimal; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; @@ -68,7 +69,7 @@ public class EvaluationCriteriaDao { LookUpDataEntity lookupDataEntity = lookUpDataService.getOrCreateLookUpDataEntity(evaluationCriteriaRequest, LookUpDataEntity.LookUpDataTypeEnum.EVALUATION_CRITERIA); entity.setCall(callEntity); entity.setLookupData(lookupDataEntity); - entity.setScore(0L); + entity.setScore(BigDecimal.ZERO); if (evaluationCriteriaRequest.getScore() != null) { entity.setScore(evaluationCriteriaRequest.getScore()); } diff --git a/src/main/java/net/gepafin/tendermanagement/entities/EvaluationCriteriaEntity.java b/src/main/java/net/gepafin/tendermanagement/entities/EvaluationCriteriaEntity.java index 39b3eb05..8d1c0c69 100644 --- a/src/main/java/net/gepafin/tendermanagement/entities/EvaluationCriteriaEntity.java +++ b/src/main/java/net/gepafin/tendermanagement/entities/EvaluationCriteriaEntity.java @@ -6,6 +6,8 @@ import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; import lombok.Data; +import java.math.BigDecimal; + @Entity @Table(name = "EVALUATION_CRITERIA") @@ -21,7 +23,7 @@ public class EvaluationCriteriaEntity extends BaseEntity { private LookUpDataEntity lookupData; @Column(name = "SCORE", nullable = false) - private Long score; + private BigDecimal score; @Column(name ="IS_DELETED", nullable = false) private Boolean isDeleted = false; diff --git a/src/main/java/net/gepafin/tendermanagement/model/request/CriteriaRequest.java b/src/main/java/net/gepafin/tendermanagement/model/request/CriteriaRequest.java index ada0d83c..6ed6dfa7 100644 --- a/src/main/java/net/gepafin/tendermanagement/model/request/CriteriaRequest.java +++ b/src/main/java/net/gepafin/tendermanagement/model/request/CriteriaRequest.java @@ -2,9 +2,11 @@ package net.gepafin.tendermanagement.model.request; import lombok.Data; +import java.math.BigDecimal; + @Data public class CriteriaRequest { private Long id; - private Long score; + private BigDecimal score; private Boolean valid; } diff --git a/src/main/java/net/gepafin/tendermanagement/model/request/EvaluationCriteriaReq.java b/src/main/java/net/gepafin/tendermanagement/model/request/EvaluationCriteriaReq.java index 7d504f7f..3d1c46c1 100644 --- a/src/main/java/net/gepafin/tendermanagement/model/request/EvaluationCriteriaReq.java +++ b/src/main/java/net/gepafin/tendermanagement/model/request/EvaluationCriteriaReq.java @@ -3,9 +3,11 @@ package net.gepafin.tendermanagement.model.request; import lombok.Data; import lombok.NoArgsConstructor; +import java.math.BigDecimal; + @NoArgsConstructor @Data public class EvaluationCriteriaReq extends LookUpDataReq{ - private Long score; + private BigDecimal score; } diff --git a/src/main/java/net/gepafin/tendermanagement/model/request/EvaluationCriteriaRequest.java b/src/main/java/net/gepafin/tendermanagement/model/request/EvaluationCriteriaRequest.java index a2e5a92b..1426748c 100644 --- a/src/main/java/net/gepafin/tendermanagement/model/request/EvaluationCriteriaRequest.java +++ b/src/main/java/net/gepafin/tendermanagement/model/request/EvaluationCriteriaRequest.java @@ -3,11 +3,13 @@ package net.gepafin.tendermanagement.model.request; import lombok.Data; import lombok.NoArgsConstructor; +import java.math.BigDecimal; + @NoArgsConstructor @Data public class EvaluationCriteriaRequest extends LookUpDataReq { private Long callId; - private Long score; + private BigDecimal score; } diff --git a/src/main/java/net/gepafin/tendermanagement/model/response/CriteriaResponse.java b/src/main/java/net/gepafin/tendermanagement/model/response/CriteriaResponse.java index ebbb2f2c..9d239a44 100644 --- a/src/main/java/net/gepafin/tendermanagement/model/response/CriteriaResponse.java +++ b/src/main/java/net/gepafin/tendermanagement/model/response/CriteriaResponse.java @@ -2,14 +2,15 @@ package net.gepafin.tendermanagement.model.response; import lombok.Data; +import java.math.BigDecimal; import java.util.List; @Data public class CriteriaResponse { private Long id; private String label; - private Long score; - private Long maxScore; + private BigDecimal score; + private BigDecimal maxScore; private List criteriaMappedFields; private Boolean valid; } \ No newline at end of file diff --git a/src/main/java/net/gepafin/tendermanagement/model/response/EvaluationCriteriaResponseBean.java b/src/main/java/net/gepafin/tendermanagement/model/response/EvaluationCriteriaResponseBean.java index 50a984cc..4e1d564e 100644 --- a/src/main/java/net/gepafin/tendermanagement/model/response/EvaluationCriteriaResponseBean.java +++ b/src/main/java/net/gepafin/tendermanagement/model/response/EvaluationCriteriaResponseBean.java @@ -3,9 +3,11 @@ package net.gepafin.tendermanagement.model.response; import lombok.Data; import net.gepafin.tendermanagement.model.BaseBean; +import java.math.BigDecimal; + @Data public class EvaluationCriteriaResponseBean extends LookUpDataResponse{ - private Long score; + private BigDecimal score; } From eafd47ba5f32063b62de17c647ea34be91c36aee Mon Sep 17 00:00:00 2001 From: Piyush Date: Wed, 28 May 2025 11:56:17 +0530 Subject: [PATCH 04/35] Resolved conflicts --- .../dao/ApplicationAmendmentRequestDao.java | 45 +++++++++-- .../tendermanagement/dao/ApplicationDao.java | 77 ++++++++++++++++--- .../dao/ApplicationEvaluationDao.java | 60 +++++++++++++-- .../tendermanagement/dao/AppointmentDao.java | 42 +++++++--- .../dao/AssignedApplicationsDao.java | 32 ++++++-- .../gepafin/tendermanagement/dao/CallDao.java | 77 ++++++++++++++++--- .../tendermanagement/dao/CompanyDao.java | 17 +++- .../dao/CompanyDocumentDao.java | 30 ++++++-- .../tendermanagement/dao/DocumentDao.java | 30 +++++++- .../gepafin/tendermanagement/dao/PdfDao.java | 2 + 10 files changed, 351 insertions(+), 61 deletions(-) diff --git a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationAmendmentRequestDao.java b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationAmendmentRequestDao.java index 02a92762..f6e87839 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationAmendmentRequestDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationAmendmentRequestDao.java @@ -159,7 +159,8 @@ public class ApplicationAmendmentRequestDao { String file=applicationEvaluationEntity.getFile(); String checkList=applicationEvaluationEntity.getChecklist(); if(file != null){ - evaluationFileRequests=Utils.convertJsonStringToList(file,FieldRequest.class); + log.info("Parsing evaluation file data for evaluationId={}", applicationEvaluationId); + evaluationFileRequests=Utils.convertJsonStringToList(file,FieldRequest.class); } if(applicationEvaluationEntity.getEvaluationVersion().equals(EvaluationVersionEnum.V1.getValue())) { checklistValidationForEvaluationV1(evaluationFileRequests, checkList, checklistRequests); @@ -326,6 +327,7 @@ public class ApplicationAmendmentRequestDao { applicationAmendmentRequestEntity.setNote(applicationAmendmentRequest.getNote()); applicationAmendmentRequestEntity.setResponseDays(applicationAmendmentRequest.getResponseDays()); if(applicationAmendmentRequest.getResponseDays()==null || applicationAmendmentRequest.getResponseDays() < 0){ + log.warn("Invalid responseDays received: {}", applicationAmendmentRequest.getResponseDays()); throw new CustomValidationException(Status.BAD_REQUEST,Translator.toLocale(GepafinConstant.RESPONSE_DAYS_NOT_NULL)); } applicationAmendmentRequestEntity.setEndDate(DateTimeUtil.DateServerToUTC(LocalDateTime.now()).plusDays(applicationAmendmentRequest.getResponseDays())); @@ -378,6 +380,8 @@ public class ApplicationAmendmentRequestDao { userEntity.getHub().getId(),false); applicationAmendmentRequestEntity.setProtocol(protocolEntity); ApplicationAmendmentRequestEntity applicationAmendment = saveApplicationAmendmentRequestEntity(applicationAmendmentRequestEntity, null, VersionActionTypeEnum.INSERT); + log.info("Amendment request saved with ID={}", applicationAmendment.getId()); + String evaluationStatusType = applicationEvaluationEntity.getStatus(); if (Boolean.FALSE.equals(evaluationStatusType.equals((ApplicationEvaluationStatusTypeEnum.SOCCORSO.getValue())))){ // applicationEvaluationEntity.setStatus(ApplicationEvaluationStatusTypeEnum.SOCCORSO.getValue()); @@ -416,7 +420,7 @@ public class ApplicationAmendmentRequestDao { loggingUtil.addVersionHistory(VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldAssignedApplication).newData(assignedApplicationsEntity).build()); } - + log.info("Amendment creation process completed successfully for applicationId={}, evaluationId={}", applicationId, applicationEvaluationId); return applicationAmendment; } private void setAmendmentDocuments(String amendmentNotes, String amendmentFieldRequest, @@ -507,7 +511,7 @@ public class ApplicationAmendmentRequestDao { } public DocumentResponseBean createDocumentResponseBean(String documentId) { - + log.info("Initiating document response creation for documentId={}", documentId); if (!StringUtils.isEmpty(documentId)) { Optional documentEntity = documentRepository.findByIdAndNotDeleted(Long.valueOf(documentId)); if(documentEntity.isPresent()){ @@ -671,7 +675,9 @@ public class ApplicationAmendmentRequestDao { } public List getAllApplicationAmendmentRequest(HttpServletRequest request, Long userId) { + log.info("Entering getAllApplicationAmendmentRequest with userId={}", userId); if (validator.checkIsPreInstructor() && userId == null) { + log.warn("Access denied: Pre-instructor must provide userId for amendment request retrieval."); throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.USER_ID_NOT_NULL_MSG)); } if (userId != null) { @@ -714,9 +720,11 @@ public class ApplicationAmendmentRequestDao { isBeneficiary=true; } if(Boolean.TRUE.equals(isBeneficiary) && existingApplicationAmendment.getStatus().equals(ApplicationAmendmentRequestEnum.RESPONSE_RECEIVED.getValue())){ - throw new CustomValidationException(Status.VALIDATION_ERROR,Translator.toLocale(GepafinConstant.PERMISSION_DENIED)); + log.warn("Permission denied: Beneficiary tried to update amendment ID {} with status RESPONSE_RECEIVED", id); + throw new CustomValidationException(Status.VALIDATION_ERROR,Translator.toLocale(GepafinConstant.PERMISSION_DENIED)); } if(Boolean.FALSE.equals(isBeneficiary) && existingApplicationAmendment.getStatus().equals(ApplicationAmendmentRequestEnum.AWAITING.getValue())){ + log.warn("Permission denied: Non-beneficiary tried to update amendment ID {} with status AWAITING", id); throw new CustomValidationException(Status.VALIDATION_ERROR,Translator.toLocale(GepafinConstant.PERMISSION_DENIED)); } @@ -739,10 +747,12 @@ public class ApplicationAmendmentRequestDao { } existingApplicationAmendment.setUpdatedDate(DateTimeUtil.DateServerToUTC(LocalDateTime.now())); if(updateRequest.getAmendmentDocuments()!=null && Boolean.FALSE.equals(updateRequest.getAmendmentDocuments().isEmpty())) { + log.debug("Setting amendment documents for amendment ID: {}", id); setAmendmentDocuments(updateRequest.getAmendmentNotes(),updateRequest.getAmendmentDocuments(), existingApplicationAmendment); } ApplicationAmendmentRequestEntity updatedApplicationAmendment = saveApplicationAmendmentRequestEntity(existingApplicationAmendment,oldApplicationAmendmentEntity,VersionActionTypeEnum.UPDATE); ApplicationAmendmentRequestResponse response = convertEntityToResponse(updatedApplicationAmendment,false); + log.info("Successfully updated ApplicationAmendmentRequest with ID: {}", id); log.info("Application Amendment updated successfully: {}", response); return response; } @@ -1038,6 +1048,7 @@ public class ApplicationAmendmentRequestDao { public List getAllAmendmentRequestByBeneficiaryId(Long beneficiaryUserId) { + log.info("Fetching all amendment requests for beneficiaryUserId={}", beneficiaryUserId); UserEntity userEntity = userService.validateUser(beneficiaryUserId); List entities = applicationAmendmentRequestRepository.findByUserId(beneficiaryUserId); @@ -1057,6 +1068,8 @@ public class ApplicationAmendmentRequestDao { existingApplicationAmendment.getApplicationEvaluationEntity().getId() ); + log.debug("Found {} amendment requests for ApplicationEvaluationId={}", amendmentRequestList.size(), existingApplicationAmendment.getApplicationEvaluationEntity().getId()); + // Check if this is the last amendment being closed boolean isLastRemaining = amendmentRequestList.stream() .filter(amendment -> !amendment.getId().equals(id)) // Exclude the current amendment @@ -1074,6 +1087,8 @@ public class ApplicationAmendmentRequestDao { } ApplicationAmendmentRequestResponse response = convertEntityToResponse(updatedApplicationAmendment,false); + log.info("Updated amendment status to CLOSE for amendment ID: {}", id); + List amendmentRequests = applicationAmendmentRequestRepository.findAllByApplicationEvaluationIdAndIsDeletedFalse( existingApplicationAmendment.getApplicationEvaluationEntity().getId()); Boolean allClosed = amendmentRequests.stream().allMatch(amendment -> (amendment.getStatus().equals(ApplicationAmendmentRequestEnum.CLOSE.getValue()) || amendment.getStatus().equals(ApplicationAmendmentRequestEnum.EXPIRED.getValue()))); @@ -1081,16 +1096,17 @@ public class ApplicationAmendmentRequestDao { ApplicationEntity oldApplicationEntityData = Utils.getClonedEntityForData(application); if (Boolean.TRUE.equals(allClosed)) { existingApplicationAmendment.getApplicationEvaluationEntity().setStatus(ApplicationEvaluationStatusTypeEnum.OPEN.getValue()); - if (allClosed) { ApplicationEvaluationEntity existingApplicationEvaluationEntity = existingApplicationAmendment.getApplicationEvaluationEntity(); ApplicationEvaluationEntity oldApplicationEvaluationEntity = Utils.getClonedEntityForData(existingApplicationEvaluationEntity); existingApplicationEvaluationEntity.setStatus(ApplicationEvaluationStatusTypeEnum.OPEN.getValue()); applicationEvaluationRepository.save(existingApplicationAmendment.getApplicationEvaluationEntity()); + log.info("Updated ApplicationEvaluation status to OPEN for ID: {}", existingApplicationEvaluationEntity.getId()); application.setStatus(ApplicationStatusTypeEnum.EVALUATION.getValue()); applicationRepository.save(application); + log.info("Updated Application status to EVALUATION for Application ID: {}", application.getId()); AssignedApplicationsEntity assignedApplicationsEntity = assignedApplicationsDao.validateAssignedApplication( existingApplicationAmendment.getApplicationEvaluationEntity().getAssignedApplicationsEntity().getId()); @@ -1098,7 +1114,7 @@ public class ApplicationAmendmentRequestDao { existingApplicationAmendment.getApplicationEvaluationEntity().getAssignedApplicationsEntity().setStatus(AssignedApplicationEnum.OPEN.getValue()); assignedApplicationsEntity = assignedApplicationsRepository.save(existingApplicationAmendment.getApplicationEvaluationEntity().getAssignedApplicationsEntity()); - + log.info("Updated AssignedApplication status to OPEN for ID: {}", assignedApplicationsEntity.getId()); Map placeHolders = notificationDao.sendNotificationToBeneficiary(application, NotificationTypeEnum.AMENDMENT_CLOSED); @@ -1125,6 +1141,7 @@ public class ApplicationAmendmentRequestDao { public ApplicationAmendmentRequestResponse extendResponseDays(Long id, Long newResponseDays) { ApplicationAmendmentRequestEntity applicationAmendmentRequestEntity = validatApplicationAmendmentRequestByStatus(id,ApplicationAmendmentRequestEnum.EXPIRED.getValue()); + log.info("Extending response days for Application Amendment ID: {}, Additional Days: {}", id, newResponseDays); if (newResponseDays != null && newResponseDays > 0) { ApplicationAmendmentRequestEntity oldApplicationAmendmentEntity = Utils.getClonedEntityForData(applicationAmendmentRequestEntity); @@ -1177,6 +1194,7 @@ public class ApplicationAmendmentRequestDao { ApplicationAmendmentRequestEntity existingApplicationAmendment = validateApplicationAmendmentRequest(id); ApplicationAmendmentRequestEntity oldApplicationAmendmentEntity = Utils.getClonedEntityForData(existingApplicationAmendment); if (Boolean.TRUE.equals(existingApplicationAmendment.getStatus().equals(ApplicationAmendmentRequestEnum.AWAITING.getValue())) && Boolean.TRUE.equals(statusTypeEnum.equals(ApplicationAmendmentRequestEnum.RESPONSE_RECEIVED))) { + log.info("Updating amendment ID {} status from {} to {}", id, existingApplicationAmendment.getStatus(), statusTypeEnum); existingApplicationAmendment.setStatus(ApplicationAmendmentRequestEnum.RESPONSE_RECEIVED.getValue()); existingApplicationAmendment.setUpdatedDate(DateTimeUtil.DateServerToUTC(LocalDateTime.now())); applicationAmendmentRequestRepository.save(existingApplicationAmendment); @@ -1191,8 +1209,12 @@ public class ApplicationAmendmentRequestDao { public EmailReminderResponse sendReminderEmail(Long amendmentId) { + log.info("Initiating reminder email process for Amendment ID: {}", amendmentId); + ApplicationAmendmentRequestEntity amendment = applicationAmendmentRequestRepository.findByIdAndIsDeletedFalse(amendmentId) - .orElseThrow(() -> new ResourceNotFoundException(Status.NOT_FOUND, Translator.toLocale(GepafinConstant.APPLICATION_AMENDMENT_NOT_FOUND_MSG))); + .orElseThrow(() -> { log.error("Amendment not found with ID: {}", amendmentId); + return new ResourceNotFoundException(Status.NOT_FOUND, Translator.toLocale(GepafinConstant.APPLICATION_AMENDMENT_NOT_FOUND_MSG)); + }); Optional entityOptional = applicationEvaluationRepository.findByIdAndIsDeletedFalse(amendment.getApplicationEvaluationEntity().getId()); EmailReminderResponse emailReminderResponse = new EmailReminderResponse(); @@ -1205,6 +1227,7 @@ public class ApplicationAmendmentRequestDao { String body = prepareBody(emailTemplate, amendment, beneficiaryUser); String email = beneficiaryUser.getEmail(); if (Boolean.TRUE.equals(amendment.getIsEmail()) && email != null && !email.isEmpty()) { + log.info("Sending reminder email to: {}", email); EmailLogRequest emailLogRequest = emailLogDao.createEmailLogRequest(emailTemplate.getEmailScenario(), RecipientTypeEnum.USER, beneficiaryUser.getId(), email, beneficiaryUser.getId(), applicationEntity.getId(), amendment.getId(), applicationEntity.getCall().getId()); emailNotificationDao.sendMail(hub.getId(), subject, body, List.of(email), emailLogRequest); @@ -1217,7 +1240,9 @@ public class ApplicationAmendmentRequestDao { else{ emailReminderResponse.setEmailSendResponse(Collections.emptyList()); } + log.info("Reminder email sent successfully for Amendment ID: {}", amendmentId); } else { + log.warn("Beneficiary email not found or isEmail flag is false for Amendment ID: {}", amendmentId); throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.BENEFICIARY_EMAIL_NOT_FOUND_MSG)); } } @@ -1303,7 +1328,9 @@ public class ApplicationAmendmentRequestDao { return response; } private void softDeleteDocument(Long documentId) { + log.info("Initiating soft delete for Document ID: {}", documentId); documentService.deleteFile(documentId); + log.info("Document ID: {} soft deleted successfully.", documentId); } // public PageableResponseBean> getApplicationAmendmentByPaginnation(Long userId, ApplicationAmendmentPaginationRequestBean applicationAmendmentPaginationRequestBean) { @@ -1422,6 +1449,7 @@ public class ApplicationAmendmentRequestDao { // } public PageableResponseBean> getApplicationAmendmentByPaginationByView(Long userId, ApplicationAmendmentPaginationRequestBean applicationAmendmentPaginationRequestBean) { + log.info("Fetching paginated application amendments for userId: {}", userId); Integer pageNo = null; Integer pageLimit = null; if (applicationAmendmentPaginationRequestBean.getGlobalFilters() != null) { @@ -1453,6 +1481,7 @@ public class ApplicationAmendmentRequestDao { pageableResponseBean.setTotalRecords(entityPage.getTotalElements()); pageableResponseBean.setPageSize(entityPage.getSize()); + log.info("Paginated response prepared successfully for userId: {}", userId); return pageableResponseBean; } public Specification searchPaginationByView(Long userId,ApplicationAmendmentPaginationRequestBean applicationAmendmentPaginationRequestBean) { @@ -1484,6 +1513,7 @@ public class ApplicationAmendmentRequestDao { private List getPredicatesByView(ApplicationAmendmentPaginationRequestBean amendmentPaginationRequestBean, CriteriaBuilder criteriaBuilder, Root root,Long userId) { + log.info("Building predicates for userId: {}", userId); Integer year = null; String search = null; Map filters = new HashMap<>(); @@ -1503,6 +1533,7 @@ public class ApplicationAmendmentRequestDao { LocalDateTime startOfYear = LocalDateTime.of(filterYear, 1, 1, 0, 0); LocalDateTime endOfYear = LocalDateTime.of(filterYear, 12, 31, 23, 59, 59); + log.debug("Filtering by year between {} and {}", startOfYear, endOfYear); // Add the range comparison to filter records within the year predicates.add(criteriaBuilder.between(root.get(GepafinConstant.CREATED_DATE), startOfYear, endOfYear)); diff --git a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java index 3a80c27d..b0f09777 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java @@ -211,13 +211,16 @@ public class ApplicationDao { public final Random random = new Random(); public ApplicationResponseBean createApplication(HttpServletRequest request, ApplicationRequestBean applicationRequestBean, Long formId, Long applicationId) { + log.info("Starting createApplication: formId={}, applicationId={}", formId, applicationId); FormEntity formEntity = formService.validateForm(formId); // callService.validatePublishedCall(formEntity.getCall().getId()); validateFormFields(applicationRequestBean,formEntity); ApplicationEntity applicationEntity = validateApplication(applicationId); checkCallEndDate(applicationEntity.getCall()); validator.validateUserWithCompany(request, applicationEntity.getCompanyId()); + log.info("Validated user-company association for company ID: {}", applicationEntity.getCompanyId()); if(Boolean.FALSE.equals(applicationEntity.getStatus().equals(ApplicationStatusTypeEnum.DRAFT.getValue()))) { + log.warn("Application ID {} is not in DRAFT status", applicationId); throw new CustomValidationException(Status.BAD_REQUEST,Translator.toLocale(GepafinConstant.APPLICATION_NOT_IN_DRAFT_STATUS)); } formService.validateFormField(applicationRequestBean.getFormFields(),applicationEntity,formEntity); @@ -244,10 +247,12 @@ public class ApplicationDao { } public ApplicationFormEntity createApplicationFormEntity(ApplicationEntity application, FormEntity formEntity) { + log.info("Creating ApplicationFormEntity for applicationId: {}, formId: {}", application.getId(), formEntity.getId()); ApplicationFormEntity applicationFormEntity = new ApplicationFormEntity(); applicationFormEntity.setApplication(application); applicationFormEntity.setForm(formEntity); applicationFormEntity = saveApplicationFormEntity(applicationFormEntity); + log.info("Created ApplicationFormEntity with id: {}", applicationFormEntity.getId()); return applicationFormEntity; } @@ -299,6 +304,7 @@ public class ApplicationDao { List documentResponseBeans = new ArrayList<>(); if (fileUploadContent.isPresent()) { String documentId = applicationFormFieldEntity.getFieldValue(); + log.debug("Field is file upload/select type. Document IDs: {}", documentId); if (documentId != null && !documentId.isEmpty()) { documentResponseBeans = Arrays.stream(documentId.split(",")) .map(String::trim) @@ -306,6 +312,7 @@ public class ApplicationDao { .map(docId -> { DocumentEntity documentEntity = documentService.validateDocument(docId); if (Boolean.FALSE.equals(DocumentSourceTypeEnum.APPLICATION.getValue().equals(documentEntity.getSource()))) { + log.warn("Document {} source type invalid: {}", docId, documentEntity.getSource()); throw new CustomValidationException(Status.NOT_FOUND,Translator.toLocale(GepafinConstant.DOCUMENT_NOT_FOUND)); } return documentEntity; @@ -331,6 +338,7 @@ public class ApplicationDao { ApplicationEntity applicationEntity= validateApplication(id); if (Boolean.FALSE.equals(ApplicationStatusTypeEnum.DRAFT.getValue().equals(applicationEntity.getStatus()))) { + log.warn("Application with ID: {} is not in DRAFT status, cannot delete. Current status: {}", id, applicationEntity.getStatus()); throw new CustomValidationException( Status.VALIDATION_ERROR, Translator.toLocale(GepafinConstant.APPLICATION_NOT_IN_DRAFT_STATUS) @@ -341,6 +349,7 @@ public class ApplicationDao { validator.validateUserWithCompany(request, applicationEntity.getCompanyId()); applicationEntity.setIsDeleted(true); applicationEntity = applicationRepository.save(applicationEntity); + log.info("Marked application as deleted and saved for ID: {}", id); /** This code is responsible for adding a version history log for the "Delete application" operation. **/ loggingUtil.addVersionHistory( @@ -428,6 +437,7 @@ public class ApplicationDao { } private ApplicationResponse getApplicationResponse(ApplicationEntity applicationEntity) { + log.info("Generating ApplicationResponse for application ID: {}", applicationEntity.getId()); ApplicationResponse responseBean = new ApplicationResponse(); List flowEdgesList = flowEdgesRepository.findByCallId(applicationEntity.getCall().getId()); Long totalFormSteps = flowFormDao.calculateTotalSteps(flowEdgesList); @@ -468,9 +478,13 @@ public class ApplicationDao { } public ApplicationEntity validateApplication(Long id) { + log.info("Validating existence of Application with ID: {}", id); ApplicationEntity applicationEntity = applicationRepository.findById(id) - .orElseThrow(() -> new ResourceNotFoundException(Status.NOT_FOUND, - Translator.toLocale(GepafinConstant.APPLICATION_NOT_FOUND_MSG))); + .orElseThrow(() -> { + log.warn("Application not found for ID: {}", id); + return new ResourceNotFoundException(Status.NOT_FOUND, + Translator.toLocale(GepafinConstant.APPLICATION_NOT_FOUND_MSG)); + }); return applicationEntity; } @@ -494,10 +508,12 @@ public class ApplicationDao { } private ApplicationFormEntity getApplicationFormOrCreate(FormEntity formEntity, ApplicationEntity applicationEntity) { + log.info("Fetching ApplicationForm for Application ID: {} and Form ID: {}", applicationEntity.getId(), formEntity.getId()); ApplicationFormEntity applicationFormEntity = applicationFormRepository.findByApplicationIdAndFormId(applicationEntity.getId(), formEntity.getId()); ApplicationFormEntity oldApplicationFormEntity = Utils.getClonedEntityForData(applicationFormEntity); if (applicationFormEntity == null) { + log.info("No existing ApplicationForm found. Creating new ApplicationForm for Application ID: {}, Form ID: {}", applicationEntity.getId(), formEntity.getId()); applicationFormEntity = createApplicationFormEntity(applicationEntity, formEntity); /** This code is responsible for adding a version history log for the "Create application form" operation. **/ @@ -523,6 +539,8 @@ public class ApplicationDao { public ApplicationFormFieldEntity createOrUpdateApplicationFormField(ApplicationFormFieldRequestBean applicationFormFieldRequestBean, ApplicationFormEntity applicationFormEntity, List applicationFormFieldEntities, FormEntity formEntity,FieldValidator fieldValidator) { + log.info("Starting createOrUpdateApplicationFormField for ApplicationForm ID: {}", applicationFormEntity.getId()); + ApplicationFormFieldEntity applicationFormFieldEntity = new ApplicationFormFieldEntity(); List newDocumentIds = validateFileUploadDocuments(applicationFormFieldRequestBean, formEntity); @@ -542,7 +560,9 @@ public class ApplicationDao { try { BigDecimal amountRequested = new BigDecimal(fieldValue.toString()); applicationFormEntity.getApplication().setAmountRequested(amountRequested); + log.info("Set amountRequested to {} for Application ID: {}", amountRequested, applicationFormEntity.getApplication().getId()); } catch (NumberFormatException e) { + log.error("Invalid number format for requested amount: {}", fieldValue, e); throw new IllegalArgumentException("Field value is not a valid number: " + fieldValue, e); } } @@ -683,6 +703,8 @@ public class ApplicationDao { List documentIds=null; // List contentResponseBeans=Utils.convertJsonStringToList(formEntity.getContent(),ContentResponseBean.class); List contentResponseBeans=formDao.convertFormEntityToFormResponseBean(formEntity).getContent(); + log.debug("Validating file upload documents for field ID: {} in form ID: {}", applicationFormFieldRequestBean.getFieldId(), formEntity.getId()); + for (ContentResponseBean contentResponseBean:contentResponseBeans){ if(Boolean.TRUE.equals(contentResponseBean.getName().equals("fileupload")) || Boolean.TRUE.equals(contentResponseBean.getName().equals("fileselect"))) { if (contentResponseBean.getId().equals(applicationFormFieldRequestBean.getFieldId())) { @@ -692,6 +714,7 @@ public class ApplicationDao { String documentId = (String) fieldValueObject; // Now you can use documentId as needed documentIds = validateDocumentIds(documentId); + log.info("Validated document IDs: {}", documentIds); } } } @@ -701,6 +724,7 @@ public class ApplicationDao { public List validateDocumentIds(String documentId) { if (documentId != null && !documentId.isEmpty()) { + log.info("Validating document IDs: {}", documentId); return Arrays.stream(documentId.split(",")) .map(Long::parseLong) .peek(docId -> documentService.validateDocument(docId)) @@ -753,6 +777,7 @@ public class ApplicationDao { } public ApplicationGetResponseBean getApplicationByFormId(HttpServletRequest request, Long applicationId, Long formId) { + log.info("Received request to get application by formId. ApplicationId: {}, FormId: {}", applicationId, formId); List formApplicationResponses = new ArrayList<>(); List formEntities = new ArrayList<>(); UserEntity userEntity = validator.validateUser(request); @@ -903,6 +928,7 @@ public class ApplicationDao { public ApplicationResponse createApplicationByCallId(CompanyEntity companyEntity, ApplicationRequest applicationRequest, Long callId, UserEntity userEntity) { + log.info("Start creating application for CallId: {}, UserId: {}, CompanyId: {}", callId, userEntity.getId(), companyEntity.getId()); CallEntity call = callService.validateCall(callId); UserWithCompanyEntity userWithCompanyEntity=companyService.getUserWithCompany(userEntity.getId(),companyEntity.getId()); checkCallEndDate(call); @@ -926,11 +952,15 @@ public class ApplicationDao { public void checkIfApplicationExists(CallEntity call, UserWithCompanyEntity userWithCompanyEntity, UserEntity userEntity){ + log.info("Checking existing applications for UserId: {}, UserWithCompanyId: {}, CallId: {}", + userEntity.getId(), userWithCompanyEntity.getId(), call.getId()); List applications = applicationRepository.findByUserIdAndUserWithCompany_IdAndCall_IdAndIsDeletedFalseAndStatusNot( userEntity.getId(), userWithCompanyEntity.getId(), call.getId(), ApplicationStatusTypeEnum.REJECTED.name() ); if (!applications.isEmpty()) { + log.warn("Application already exists for UserId: {}, UserWithCompanyId: {}, CallId: {}. Applications found: {}", + userEntity.getId(), userWithCompanyEntity.getId(), call.getId(), applications.size()); throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.APPLICATION_ALREADY_EXISTS)); } } @@ -952,10 +982,12 @@ public class ApplicationDao { UserEntity userEntity = userService.validateUser(applicationEntity.getUserId()); validator.validateUserWithCompany(request, applicationEntity.getCompanyId()); if (ApplicationStatusTypeEnum.SUBMIT.getValue().equals(applicationEntity.getStatus())) { + log.warn("Attempt to change status after submission denied | applicationId: {}", applicationId); throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.APPLICATION_SUBMITTED_CANNOT_CHANGE)); } if (Boolean.TRUE.equals(applicationEntity.getStatus().equals(status.getValue()))) { + log.warn("Requested status is the same as current status | applicationId: {}, status: {}", applicationId, status); throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.APPLICATION_ALREADY_IN_PREVIOUS_STATUS)); } @@ -1095,6 +1127,7 @@ public class ApplicationDao { } private void sendMailToUserAndCompany(UserEntity userEntity, ApplicationEntity applicationEntity) { + log.info("Preparing to send submission email | applicationId: {}, userId: {}", applicationEntity.getId(), userEntity.getId()); CallEntity call =applicationEntity.getCall(); CompanyEntity company=companyService.validateCompany(applicationEntity.getCompanyId()); UserWithCompanyEntity userWithCompany=companyService.getUserWithCompany(userEntity.getId(),company.getId()); @@ -1185,6 +1218,7 @@ public class ApplicationDao { } public ApplicationSignedDocumentResponse uploadSignedDocument(HttpServletRequest request, Long applicationId, MultipartFile file) { + log.info("Received request to upload signed document | applicationId: {}, fileName: {}", applicationId, file.getOriginalFilename()); ApplicationEntity applicationEntity = validateApplication(applicationId); checkCallEndDate(applicationEntity.getCall()); //cloned entity for old data @@ -1197,9 +1231,11 @@ public class ApplicationDao { ApplicationSignedDocumentEntity oldApplicationSingedDocumentData = Utils.getClonedEntityForData(applicationSignedDocument); if (applicationSignedDocument != null) { + log.info("Existing active signed document found and will be deleted | applicationId: {}, fileName: {}", applicationId, applicationSignedDocument.getFileName()); deleteSignedDocumentFromS3(applicationSignedDocument); } UploadFileOnAmazonS3Response uploadFileOnAmazonS3 = uploadFileOnAmazonS3ForUserSignedDocument(file, applicationEntity.getCall().getId(), applicationId); + log.info("File uploaded to S3 successfully | applicationId: {}", applicationId); applicationSignedDocument = new ApplicationSignedDocumentEntity(); applicationSignedDocument.setApplication(applicationEntity); applicationSignedDocument.setFileName(uploadFileOnAmazonS3.getFileName()); @@ -1213,6 +1249,8 @@ public class ApplicationDao { applicationEntity.setStatus(ApplicationStatusTypeEnum.READY.getValue()); applicationEntity = applicationRepository.save(applicationEntity); + log.info("Application status updated to READY | applicationId: {}", applicationEntity.getId()); + /** This code is responsible for adding a version history log for the "Create Call" operation. **/ loggingUtil.addVersionHistory( @@ -1221,16 +1259,22 @@ public class ApplicationDao { return convertApplicationSignedDocumentToApplicationSignedDocumentResponse(applicationSignedDocument); } public void deleteSignedDocumentFromS3(ApplicationSignedDocumentEntity applicationSignedDocumentEntity){ + log.info("Starting soft delete of signed document | applicationSignedDocumentId: {}, fileName: {}", + applicationSignedDocumentEntity.getId(), applicationSignedDocumentEntity.getFileName()); ApplicationSignedDocumentEntity oldApplicationSignedDocument = Utils.getClonedEntityForData(applicationSignedDocumentEntity); String oldS3Path = applicationSignedDocumentEntity.getFilePath(); + log.debug("Old S3 path: {} ", oldS3Path); String newS3Path = s3ConfigBean.generateDocumentPathForOther(DocOtherSourceTypeEnum.DELETED_USER_SIGNED_DOCUMENT,applicationSignedDocumentEntity.getApplication().getCall().getId(),applicationSignedDocumentEntity.getApplication().getId(),0L); + log.debug("Generated new S3 path for deleted document: {}", newS3Path); UploadFileOnAmazonS3Response response = amazonS3Service.moveFile(applicationSignedDocumentEntity.getFileName(), oldS3Path, newS3Path); + log.info("Moved file in S3 from {} to {} | fileName: {}", oldS3Path, newS3Path, response.getFileName()); applicationSignedDocumentEntity.setStatus(ApplicationSignedDocumentStatusEnum.INACTIVE.getValue()); applicationSignedDocumentEntity.setFileName(response.getFileName()); applicationSignedDocumentEntity.setFilePath(response.getFilePath()); applicationSignedDocumentRepository.save(applicationSignedDocumentEntity); + log.info("Updated signed document entity status to INACTIVE and saved | applicationSignedDocumentId: {}", applicationSignedDocumentEntity.getId()); loggingUtil.addVersionHistory(VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.SOFT_DELETE).oldData(oldApplicationSignedDocument).newData(applicationSignedDocumentEntity).build()); } @@ -1249,6 +1293,7 @@ public class ApplicationDao { log.info("S3 Path {}", s3Path); return amazonS3Service.uploadFileOnAmazonS3(s3Path, file); } catch (Exception e) { + log.error("Failed to upload user signed document | callId: {}, applicationId: {}, error: {}", callId, applicationId, e.getMessage(), e); throw new CustomValidationException(Status.VALIDATION_ERROR, Translator.toLocale(GepafinConstant.UPLOAD_ERROR_S3)); } } @@ -1256,6 +1301,7 @@ public class ApplicationDao { try { return s3ConfigBean.generateDocumentPathForOther(DocOtherSourceTypeEnum.USER_SIGNED_DOCUMENT, callId, applicationId,0L); } catch (IllegalArgumentException e) { + log.error("Failed to generate S3 path for delegation | callId: {}, applicationId: {}, error: {}", callId, applicationId, e.getMessage(), e); throw new CustomValidationException(Status.VALIDATION_ERROR, Translator.toLocale(GepafinConstant.S3_PATH_GENERATION_ERROR_MSG)); } } @@ -1280,13 +1326,14 @@ public class ApplicationDao { } String filename = file.getOriginalFilename(); if (filename == null || !filename.endsWith(".p7m")) { + log.warn("Invalid file type detected | filename: {}", filename); throw new CustomValidationException(Status.VALIDATION_ERROR, Translator.toLocale(GepafinConstant.VALIDATION_ERROR_FILE_INVALIDTYPE)); } } public ApplicationSignedDocumentResponse getSignedDocument(HttpServletRequest request, Long applicationId) { - + log.info("Fetching signed document for applicationId: {}", applicationId); ApplicationEntity applicationEntity = validateApplication(applicationId); // validator.validateUserWithCompany(request, applicationEntity.getCompanyId()); @@ -1300,6 +1347,7 @@ public class ApplicationDao { ApplicationSignedDocumentEntity applicationSignedDocument = applicationSignedDocumentRepository .findByApplicationIdAndStatus(applicationId, ApplicationSignedDocumentStatusEnum.ACTIVE.getValue()); if(applicationSignedDocument == null) { + log.warn("No active signed document found for applicationId: {}", applicationId); throw new ResourceNotFoundException(Status.NOT_FOUND, Translator.toLocale(GepafinConstant.APPLICATION_SIGNED_DOCUMENT_NOT_FOUND)); } @@ -1307,6 +1355,7 @@ public class ApplicationDao { } public void deleteSignedDocument(HttpServletRequest request, Long applicationId) { + log.info("Initiating deletion of signed document for applicationId: {}", applicationId); ApplicationEntity applicationEntity = validateApplication(applicationId); validator.validateUserWithCompany(request, applicationEntity.getCompanyId()); @@ -1315,6 +1364,7 @@ public class ApplicationDao { //cloned entity for old data ApplicationSignedDocumentEntity oldApplicationSignedDocument = Utils.getClonedEntityForData(applicationSignedDocument); if(applicationSignedDocument == null) { + log.warn("No active signed document found to delete for applicationId: {}", applicationId); throw new ResourceNotFoundException(Status.NOT_FOUND, Translator.toLocale(GepafinConstant.APPLICATION_SIGNED_DOCUMENT_NOT_FOUND)); } @@ -1327,7 +1377,7 @@ public class ApplicationDao { } public ApplicationResponse validateApplication(HttpServletRequest request, Long applicationId) { - + log.info("Starting application validation process | applicationId: {}", applicationId); ApplicationEntity applicationEntity = validateApplication(applicationId); ApplicationEntity oldApplicationEntity = Utils.getClonedEntityForData(applicationEntity); checkCallEndDate(applicationEntity.getCall()); @@ -1335,15 +1385,18 @@ public class ApplicationDao { UserEntity userEntity = userService.validateUser(applicationEntity.getUserId()); validator.validateUserWithCompany(request, applicationEntity.getCompanyId()); if (Boolean.FALSE.equals(ApplicationStatusTypeEnum.DRAFT.getValue().equals(applicationEntity.getStatus()))) { + log.warn("Application not in draft status | applicationId: {}, status: {}", applicationId, applicationEntity.getStatus()); throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.APPLICATION_NOT_IN_DRAFT_STATUS)); } if (applicationEntity.getAmountRequested() == null || applicationEntity.getAmountRequested().compareTo(BigDecimal.ZERO) <= 0 ) { + log.warn("Invalid amount requested | amount: {}", applicationEntity.getAmountRequested()); throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.AMOUNT_REQUEST_SHOULD_GREATED_THEN_ZERO)); } List flowEdgesList = flowEdgesRepository.findByCallId(applicationEntity.getCall().getId()); Long totalSteps = flowFormDao.calculateTotalSteps(flowEdgesList); Integer completedSteps = flowFormDao.getCompletedSteps(applicationEntity, true); if (totalSteps.intValue() != completedSteps) { + log.warn("Application incomplete | applicationId: {}", applicationId); throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.APPLICATION_IS_INCOMPLETE_MSG)); } @@ -1358,7 +1411,7 @@ public class ApplicationDao { } public byte[] downloadApplicationDocumentsAsZip(HttpServletRequest request, Long applicationId) { - + log.info("Starting ZIP download process for applicationId: {}", applicationId); ApplicationEntity applicationEntity = validateApplication(applicationId); validateAssignedUser(request, applicationId); Set documentIds = extractDocumentIdsFromApplicationForms(applicationId); @@ -1368,13 +1421,14 @@ public class ApplicationDao { List amendmentDocuments = fetchAmendmentDocuments(applicationId); List evaluationDocuments = fetchEvaluationDocuments(applicationId); if (documents.isEmpty() && signedDocument == null && amendmentDocuments.isEmpty() && evaluationDocuments.isEmpty()) { + log.warn("No documents found for applicationId: {}", applicationId); throw new ResourceNotFoundException(Status.NOT_FOUND, Translator.toLocale(GepafinConstant.DOCUMENT_NOT_FOUND)); } return createZipWithDocuments(applicationEntity, documents, signedDocument, amendmentDocuments, evaluationDocuments, applicationId); } private void validateAssignedUser(HttpServletRequest request, Long applicationId) { - + log.info("Validating assigned user for applicationId: {}", applicationId); AssignedApplicationsEntity assignedApplications = assignedApplicationsRepository.findByApplicationIdAndIsDeletedFalse(applicationId).orElse(null); if (assignedApplications != null) { validator.validatePreInstructor(request, assignedApplications.getUserId()); @@ -1382,7 +1436,7 @@ public class ApplicationDao { } private Set extractDocumentIdsFromApplicationForms(Long applicationId) { - + log.info("Extracting document IDs from application forms | applicationId: {}", applicationId); Set documentIds = new HashSet<>(); List applicationForms = applicationFormRepository.findByApplicationId(applicationId); applicationForms.forEach(applicationForm -> { @@ -1404,16 +1458,17 @@ public class ApplicationDao { } private List fetchAmendmentDocuments(Long applicationId) { - + log.info("Fetching amendment documents for applicationId: {}", applicationId); List amendmentRequests = applicationAmendmentRequestRepository.findByApplicationIdAndIsDeletedFalse(applicationId); Set amendmentIds = amendmentRequests.stream().map(ApplicationAmendmentRequestEntity::getId).collect(Collectors.toSet()); return documentRepository.findBySourceIdInAndSourceAndIsDeletedFalse(amendmentIds, DocumentSourceTypeEnum.AMENDMENT.getValue()); } private List fetchEvaluationDocuments(Long applicationId) { - + log.info("Fetching evaluation documents for applicationId: {}", applicationId); Optional evaluationEntity = applicationEvaluationRepository.findByApplicationIdAndIsDeletedFalse(applicationId); if (evaluationEntity.isPresent()) { Long evaluationId = evaluationEntity.get().getId(); + log.debug("Found evaluation entity with id: {}", evaluationId); return documentRepository.findBySourceIdInAndSourceAndIsDeletedFalse(Collections.singleton(evaluationId), DocumentSourceTypeEnum.EVALUATION.getValue()); } return Collections.emptyList(); @@ -1427,12 +1482,14 @@ public class ApplicationDao { return "unknown"; } private void addDocumentToZip(ZipOutputStream zos, String s3Folder, String filePath, String fullPath) { - + log.info("Attempting to add file to ZIP. S3 folder: {}, file path: {}", s3Folder, filePath); try (InputStream fileInputStream = amazonS3Service.getFile(s3Folder, filePath)) { zos.putNextEntry(new ZipEntry(fullPath)); IOUtils.copy(fileInputStream, zos); zos.closeEntry(); } catch (IOException e) { + log.error("Failed to add file to ZIP. S3 folder: {}, file path: {}, error: {}", + s3Folder, filePath, e.getMessage(), e); throw new RuntimeException("Error downloading or adding document to ZIP: " + fullPath, e); } } diff --git a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationEvaluationDao.java b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationEvaluationDao.java index b9782af0..57f54964 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationEvaluationDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationEvaluationDao.java @@ -655,6 +655,7 @@ public class ApplicationEvaluationDao { ApplicationEvaluationRequest req, Long assignedApplicationId) { + log.info("Start createOrUpdateApplicationEvaluation: assignedApplicationId={}, userId={}", assignedApplicationId, user.getId()); Optional existingEntityOptional = applicationEvaluationRepository.findByAssignedApplicationsEntity_IdAndIsDeletedFalse(assignedApplicationId); ApplicationEvaluationEntity entity = null; @@ -665,6 +666,7 @@ public class ApplicationEvaluationDao { VersionActionTypeEnum actionType = VersionActionTypeEnum.INSERT; validateApplicationEvaluationRequest(req, application); if (existingEntityOptional.isPresent()) { + log.info("Updating existing application evaluation for assignedApplicationId={}", assignedApplicationId); entity = existingEntityOptional.get(); oldApplicationEvaluation = Utils.getClonedEntityForData(entity); if(req.getCriteria()!=null) { @@ -690,6 +692,7 @@ public class ApplicationEvaluationDao { entity = applicationEvaluationRepository.save(entity); } else { + log.info("Creating new application evaluation for assignedApplicationId={}", assignedApplicationId); AssignedApplicationsEntity assignedApplicationsEntity = assignedApplicationsService.validateAssignedApplication(assignedApplicationId); entity = convertToEntity(user, req, assignedApplicationId); actionType = VersionActionTypeEnum.INSERT; @@ -722,12 +725,13 @@ public class ApplicationEvaluationDao { List applicationAmendmentRequestEntities = applicationAmendmentRequestRepository.findAllByApplicationEvaluationIdAndIsDeletedFalse(entity.getId()); if(req.getEvaluationDocument()!=null) { + log.info("Updating evaluation document for assignedApplicationId={}", assignedApplicationId); updateApplicationEvaluation(assignedApplicationId, req.getEvaluationDocument()); } // Fetch amendment details from the request if(req.getAmendmentDetails()!=null) { + log.debug("Processing amendment details for evaluationId={}", entity.getId()); List amendmentDetailsRequests = req.getAmendmentDetails(); - updateAmendmentDocumentsAndFormFields(applicationAmendmentRequestEntities, amendmentDetailsRequests); } @@ -740,7 +744,10 @@ public class ApplicationEvaluationDao { } private void validateApplicationEvaluationRequest(ApplicationEvaluationRequest req, ApplicationEntity application) { + log.debug("Validating evaluation request for applicationId={}, evaluationVersion={}", + application.getId(), application.getEvaluationVersion()); if(EvaluationVersionEnum.V2.getValue().equals(application.getEvaluationVersion())) { + log.info("Evaluation version is V2 for applicationId={}; setting checklist and criteria to null", application.getId()); req.setChecklist(null); req.setCriteria(null); } @@ -1065,15 +1072,18 @@ public class ApplicationEvaluationDao { } public ApplicationEvaluationEntity validateApplicationEvaluation(Long id) { - + log.debug("Validating existence of ApplicationEvaluationEntity with ID: {}", id); Optional entityOptional = applicationEvaluationRepository.findByIdAndIsDeletedFalse(id); if (entityOptional.isEmpty()) { + log.warn("ApplicationEvaluationEntity not found or marked as deleted for ID: {}", id); throw new ResourceNotFoundException(Status.NOT_FOUND, Translator.toLocale(GepafinConstant.APPLICATION_EVALUATION_NOT_FOUND, id)); } + log.info("Successfully validated ApplicationEvaluationEntity with ID: {}", id); return entityOptional.get(); } public void validatePreinstructor(HttpServletRequest request,Long applicationId,Long assignedApplicationId){ + log.debug("Validating preinstructor access: applicationId={}, assignedApplicationId={}", applicationId, assignedApplicationId); if (applicationId == null && assignedApplicationId == null) { throw new CustomValidationException( Status.BAD_REQUEST, @@ -1097,12 +1107,14 @@ public class ApplicationEvaluationDao { validator.validatePreInstructor(request, assignedApplications.getUserId()); } public ApplicationEvaluationResponse getApplicationEvaluationByApplicationId(HttpServletRequest request, UserEntity user, Long applicationID, Long assignedApplicationID) { + log.info("Entering getApplicationEvaluationByApplicationId: applicationID={}, assignedApplicationID={}", applicationID, assignedApplicationID); Long applicationId; Long assignedApplicationId; validatePreinstructor(request, applicationID, assignedApplicationID); if (applicationID == null && assignedApplicationID != null) { assignedApplicationId = assignedApplicationID; + log.debug("applicationID is null, fetching from assignedApplicationID={}", assignedApplicationId); Optional assignedApplicationsOptional = assignedApplicationsRepository.findByIdAndIsDeletedFalse(assignedApplicationId); @@ -1150,6 +1162,7 @@ public class ApplicationEvaluationDao { public ApplicationEvaluationResponse getEvaluationResponseByApplicationid(UserEntity user, Long applicationId, Long assignedApplicationId) { + log.debug("Entering getEvaluationResponseByApplicationid with applicationId={}, assignedApplicationId={}, userId={}", applicationId, assignedApplicationId, user.getId()); ApplicationEvaluationEntity entity = new ApplicationEvaluationEntity(); ApplicationEvaluationResponse response = new ApplicationEvaluationResponse(); CallEntity call = null; @@ -1203,6 +1216,7 @@ public class ApplicationEvaluationDao { ApplicationEvaluationResponse response, List evaluationCriterias) { + log.info("Setting criteria responses for applicationId: {}", applicationId); List criteriaResponses = getInitialCriteriaResponses(entity, applicationId); criteriaResponses.forEach(criteriaResponse -> { @@ -1398,6 +1412,7 @@ public class ApplicationEvaluationDao { private void setChecklistResponses(ApplicationEvaluationEntity entity, Long applicationId, ApplicationEvaluationResponse response, List checklistEntities) { + log.info("Setting checklist responses for applicationId: {}", applicationId); List checklistResponses = entity.getChecklist() != null ? Utils.convertJsonToList(entity.getChecklist(), new TypeReference>() { }) : getChecklistResponse(applicationId); @@ -1516,6 +1531,7 @@ public class ApplicationEvaluationDao { } List getCriteriaResponse(Long applicationId) { + log.info("Getting criteria response for applicationId: {}", applicationId); CallEntity call = getCallEntityByApplicationId(applicationId); List evaluationCriterias = getEvaluationCriterias(call); @@ -1525,10 +1541,12 @@ public class ApplicationEvaluationDao { } private CallEntity getCallEntityByApplicationId(Long applicationId) { + log.info("Fetching CallEntity for applicationId: {}", applicationId); return callRepository.findCallEntityByApplicationId(applicationId); } private List getEvaluationCriterias(CallEntity call) { + log.info("Fetching evaluation criterias for callId: {}", call.getId()); return evaluationCriteriaRepository .findByCallIdAndLookupDataTypeAndIsDeletedFalse(call.getId(), LookUpDataEntity.LookUpDataTypeEnum.EVALUATION_CRITERIA.getValue()); } @@ -1775,6 +1793,7 @@ public class ApplicationEvaluationDao { List getChecklistResponse(Long applicationId) { + log.info("Fetching checklist responses for applicationId: {}", applicationId); CallEntity call = callRepository.findCallEntityByApplicationId(applicationId); List checklistEntities = callTargetAudienceChecklistRepository .findByCallIdAndLookupDataTypeAndIsDeletedFalse(call.getId(), LookUpDataEntity.LookUpDataTypeEnum.CHECKLIST.getValue()); @@ -1860,7 +1879,7 @@ public class ApplicationEvaluationDao { } public void deleteById(Long id) { - + log.info("Starting soft delete for ApplicationEvaluation with id: {}", id); ApplicationEvaluationEntity applicationEvaluationEntity = validateApplicationEvaluation(id); ApplicationEvaluationEntity oldApplicationEvaluation = Utils.getClonedEntityForData(applicationEvaluationEntity); applicationEvaluationEntity.setIsDeleted(true); @@ -1882,6 +1901,8 @@ public class ApplicationEvaluationDao { public ApplicationEvaluationResponse updateApplicationEvaluationStatus(ApplicationEntity application, AssignedApplicationsEntity assignedApplicationsEntity, ApplicationStatusForEvaluation newStatus) { + log.info("Starting updateApplicationEvaluationStatus for applicationId: {}, assignedApplicationId: {}, newStatus: {}", + application.getId(), assignedApplicationsEntity.getId(), newStatus); Optional existingEntityOptional = applicationEvaluationRepository.findByAssignedApplicationsEntity_IdAndIsDeletedFalse( assignedApplicationsEntity.getId()); ApplicationEvaluationEntity entity; @@ -1907,11 +1928,13 @@ public class ApplicationEvaluationDao { } if(newStatus.equals(ApplicationStatusForEvaluation.TECHNICAL_EVALUATION) && Boolean.TRUE.equals(application.getStatus().equals(ApplicationStatusTypeEnum.ADMISSIBLE.getValue()))){ + log.info("Processing technical evaluation for applicationId: {}", application.getId()); processTechnicalEvaluation(application.getId(), application, newStatus); } if((newStatus.equals(ApplicationStatusForEvaluation.APPROVED) || newStatus.equals(ApplicationStatusForEvaluation.REJECTED)) && application.getStatus().equals(ApplicationStatusTypeEnum.EVALUATION.getValue())) { application.setStatus(newStatus.getValue()); + log.info("Application status updated to {} for applicationId: {}", newStatus, application.getId()); } application = applicationRepository.save(application); @@ -1923,6 +1946,7 @@ public class ApplicationEvaluationDao { List amendmentRequest = applicationAmendmentRequestRepository.findAllByApplicationEvaluationIdAndStatusAndIsDeletedFalse(existingEntity.getId(),List.of(ApplicationAmendmentRequestEnum.AWAITING.getValue(),ApplicationAmendmentRequestEnum.RESPONSE_RECEIVED.getValue())); if(amendmentRequest !=null && Boolean.FALSE.equals(amendmentRequest.isEmpty())){ + log.warn("Application cannot be approved or rejected due to pending amendment requests. applicationEvaluationId: {}", existingEntity.getId()); throw new CustomValidationException(Status.BAD_REQUEST,Translator.toLocale(GepafinConstant.APPLICATION_CANNOT_APPROVED_OR_REJECTED)); } String statusType = application.getStatus(); @@ -1930,11 +1954,13 @@ public class ApplicationEvaluationDao { existingEntity.setStatus(ApplicationEvaluationStatusTypeEnum.CLOSE.getValue()); existingEntity.setClosingDate(DateTimeUtil.DateServerToUTC(LocalDateTime.now())); assignedApplicationsEntity.setStatus(AssignedApplicationEnum.CLOSE.getValue()); + log.info("Closing ApplicationEvaluation and AssignedApplication for applicationId: {}", application.getId()); } if (existingEntity.getStartDate() != null && existingEntity.getClosingDate() != null) { long activeDays = ChronoUnit.DAYS.between(existingEntity.getStartDate(), existingEntity.getClosingDate()); activeDays -= existingEntity.getSuspendedDays() != null ? existingEntity.getSuspendedDays() : 0; existingEntity.setActiveDays(activeDays); + log.debug("Calculated active days for ApplicationEvaluationEntity id {}: {}", existingEntity.getId(), activeDays); } entity = applicationEvaluationRepository.save(existingEntity); assignedApplicationsRepository.save(assignedApplicationsEntity); @@ -1993,15 +2019,20 @@ public class ApplicationEvaluationDao { } public ApplicationEvaluationEntity validateApplicationEvaluationByApplicationId(Long applicationId) { + log.info("Validating ApplicationEvaluation for applicationId: {}", applicationId); return applicationEvaluationRepository .findByApplicationIdAndIsDeletedFalse(applicationId) - .orElseThrow(() -> new ResourceNotFoundException(Status.NOT_FOUND, - Translator.toLocale(GepafinConstant.APPLICATION_EVALUATION_NOT_FOUND))); + .orElseThrow(() -> { + log.error("ApplicationEvaluation not found for applicationId: {}", applicationId); + return new ResourceNotFoundException(Status.NOT_FOUND, + Translator.toLocale(GepafinConstant.APPLICATION_EVALUATION_NOT_FOUND)); + }); } public ApplicationEvaluationResponse updateApplicationEvaluation( Long assignedApplicationId, List docRequest) { + log.info("Starting updateApplicationEvaluation for assignedApplicationId: {}", assignedApplicationId); Optional entityOptional=applicationEvaluationRepository.findByAssignedApplicationsEntity_IdAndIsDeletedFalse(assignedApplicationId); ApplicationEvaluationEntity applicationEvaluationEntity =null; ApplicationEvaluationEntity oldApplicationEvaluation = Utils.getClonedEntityForData(entityOptional.get()); @@ -2023,14 +2054,17 @@ public class ApplicationEvaluationDao { applicationEvaluationEntity.setEvaluationDocument(updatedEvaluationDocJson); } ApplicationEvaluationEntity savedEntity = applicationEvaluationRepository.save(applicationEvaluationEntity); + log.info("Saved ApplicationEvaluationEntity with id: {}", savedEntity.getId()); /** This code is responsible for adding a version history log for the "Upload Document in Application Evaluation" operation. **/ loggingUtil.addVersionHistory(VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldApplicationEvaluation).newData(savedEntity).build()); + log.info("Version history logged for update on ApplicationEvaluationEntity id: {}", savedEntity.getId()); return convertToResponse(savedEntity); } public ApplicationEvaluationFormResponse createApplicationEvaluation(HttpServletRequest request, ApplicationEvaluationFormRequestBean applicationEvaluationFormRequestBean, Long evaluationFormId, Long assignedApplicationId){ - + + log.info("Start createApplicationEvaluation - assignedApplicationId: {}, evaluationFormId: {}", assignedApplicationId, evaluationFormId); UserEntity user = validator.validateUser(request); AssignedApplicationsEntity assignedApplicationsEntity = assignedApplicationsService.validateAssignedApplication(assignedApplicationId); ApplicationEntity application = applicationService.validateApplication(assignedApplicationsEntity.getApplication().getId()); @@ -2094,6 +2128,7 @@ public class ApplicationEvaluationDao { String fieldId = requestField.getFieldId(); if (!contentMap.containsKey(fieldId)) { + log.warn("Field ID not found in evaluation form: {}", fieldId); validator.addError(MessageFormat.format(Translator.toLocale(GepafinConstant.FIELD_ID_NOT_FOUND), fieldId)); } @@ -2121,6 +2156,7 @@ public class ApplicationEvaluationDao { ApplicationEvaluationFormEntity applicationEvaluationFormEntity, List applicationEvaluationFormFieldEntities, EvaluationFormEntity evaluationFormEntity,FieldValidator fieldValidator){ + log.debug("Starting createOrUpdateApplicationEvaluationFormField for fieldId: {}", applicationFormFieldRequestBean.getFieldId()); ApplicationEvaluationFormFieldEntity applicationEvaluationFormFieldEntity = new ApplicationEvaluationFormFieldEntity(); validateFileUploadDocuments(applicationFormFieldRequestBean, evaluationFormEntity); VersionActionTypeEnum actionType = VersionActionTypeEnum.INSERT; @@ -2161,6 +2197,7 @@ public class ApplicationEvaluationDao { } private List validateFileUploadDocuments(ApplicationFormFieldRequestBean applicationFormFieldRequestBean, EvaluationFormEntity evaluationFormEntity) { + log.debug("Validating file upload documents for fieldId: {}", applicationFormFieldRequestBean.getFieldId()); List documentIds=null; List contentResponseBeans=evaluationFormDao.convertEvaluationFormEntityToEvaluationFormResponseBean(evaluationFormEntity).getContent(); @@ -2185,6 +2222,9 @@ public class ApplicationEvaluationDao { private List createEvaluationFormFieldResponse( List evaluationFormFieldEntities, ApplicationEvaluationFormEntity applicationEvaluationFormEntity){ + log.info("Starting to create evaluation form field response for EvaluationFormEntity ID: {}", + applicationEvaluationFormEntity.getEvaluationForm().getId()); + List evaluationFormFieldResponseBeans = new ArrayList<>(); List contentResponseBeans =evaluationFormDao.convertEvaluationFormEntityToEvaluationFormResponseBean(applicationEvaluationFormEntity.getEvaluationForm()).getContent(); @@ -2276,6 +2316,7 @@ public class ApplicationEvaluationDao { public ApplicationEvaluationFormResponse getApplicationEvaluationForm(HttpServletRequest request, Long applicationId, Long assignedApplicationId ){ + log.debug("Fetching evaluation form. applicationId: {}, assignedApplicationId: {}", applicationId, assignedApplicationId); if (applicationId == null && assignedApplicationId == null) { throw new CustomValidationException(Status.BAD_REQUEST,Translator.toLocale(GepafinConstant.EITHER_APPLICATION_ID_OR_ASSIGNED_APPLICATION_ID_MUST_BE_PROVIDED)); } @@ -2505,6 +2546,7 @@ public class ApplicationEvaluationDao { return false; } private void processTechnicalEvaluation(Long applicationId, ApplicationEntity applicationEntity, ApplicationStatusForEvaluation status){ + log.info("Starting technical evaluation processing for applicationId: {}", applicationId); Optional evaluationEntityOpt = applicationEvaluationRepository.findByApplicationIdAndIsDeletedFalse(applicationId); if (evaluationEntityOpt.isPresent()){ ApplicationEvaluationEntity evaluationEntity = evaluationEntityOpt.get(); @@ -2513,9 +2555,10 @@ public class ApplicationEvaluationDao { BigDecimal totalScore = calculateTotalScore(evaluationEntity.getCriteria()); if (totalScore.compareTo(new BigDecimal("40")) > 0) { applicationEntity.setStatus(status.getValue()); - log.info("Status updated to TECHNICAL_EVALUATION for applicationId: " + applicationId); + log.info("Status updated to TECHNICAL_EVALUATION for applicationId: {}", applicationId); } else{ + log.warn("Insufficient score ({}) for applicationId: {}. Throwing validation exception.", totalScore, applicationId); throw new CustomValidationException(Status.BAD_REQUEST,Translator.toLocale(GepafinConstant.INSUFFICIENT_SCORE_MESSAGE)); } } @@ -2537,10 +2580,11 @@ public class ApplicationEvaluationDao { }) .reduce(BigDecimal.ZERO, BigDecimal::add); + log.info("Total score calculated successfully: {}", totalScore); return totalScore; } catch (Exception e) { - log.error(" Error parsing criteria JSON: {}", e.getMessage()); + log.error("Error parsing criteria JSON. Input: {}. Exception: {}", criteriaJson, e.getMessage(), e); return BigDecimal.ZERO; } } diff --git a/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java b/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java index b1166689..6b57af23 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java @@ -157,10 +157,11 @@ public class AppointmentDao { private static final ThreadLocal threadLocalHubId = new ThreadLocal<>(); public NdgResponse checkNdgForAppointment(Long applicationId) { - + log.info("Starting NDG check for appointment. applicationId: {}", applicationId); ApplicationEntity application = applicationService.validateApplication(applicationId); NdgResponse ndgResponse = new NdgResponse(); if (application.getNdgStatus() != null && application.getNdgStatus().equalsIgnoreCase(GepafinConstant.NDG_IN_PROGRESS)) { + log.warn("NDG generation already in progress. applicationId: {}", applicationId); throw new CustomValidationException(Status.SUCCESS, Translator.toLocale(GepafinConstant.NDG_GENERATION_IS_IN_PROGRESS)); } @@ -170,6 +171,7 @@ public class AppointmentDao { } // Update application status + log.info("Updating NDG status to IN_PROGRESS. applicationId: {}", applicationId); application.setNdgStatus(GepafinConstant.NDG_IN_PROGRESS); applicationRepository.save(application); @@ -177,6 +179,7 @@ public class AppointmentDao { HubEntity hub = hubRepository.findByHubId(application.getHubId()); loginToOdessa(hub, application); startAsyncNdgProcessing(applicationId); + log.info("NDG check initiation completed. applicationId: {}", applicationId); return ndgResponse; } @@ -340,7 +343,7 @@ public class AppointmentDao { // } private void loginToOdessa(HubEntity hub, ApplicationEntity application) { - + log.info("Starting login to Odessa. HubId: {}, ApplicationId: {}", hub.getId(), application.getId()); performOdessaLogin(hub, application); } @@ -363,7 +366,7 @@ public class AppointmentDao { Map body = Collections.emptyMap(); ResponseEntity responseLogin = appointmentApiService.loginWithOdessa(authJwtToken, source, context, user, password, body); if (responseLogin.getStatusCode() == HttpStatus.OK) { - log.info("Login successful to odessa. Parsing response."); + log.info("Login to Odessa successful. Parsing response. HubId: {}", hub.getId()); String loginResponseJson = Utils.convertObjectToJson(responseLogin.getBody()); AppointmentLoginResponse parsedResponse = parseLoginResponse(loginResponseJson); @@ -374,6 +377,7 @@ public class AppointmentDao { log.info("Saved new authToken and areaCode for Hub."); return hub; } else { + log.error("Login response from Odessa missing tokenId. HubId: {}", hub.getId()); throw new RuntimeException("Login response is missing a valid tokenId for login to odessa system, please try again."); } } @@ -424,6 +428,9 @@ public class AppointmentDao { } catch (IOException e) { log.error("Error parsing JSON response: {}", e.getMessage()); } + catch (Exception e) { + throw new RuntimeException("Authentication failed on Odessa. try again", e); + } } private void startAsyncNdgProcessing(Long applicationId) { @@ -460,6 +467,7 @@ public class AppointmentDao { private void processNdgGeneration(Long applicationId) { // Validate application, company, and hub + log.info("Starting NDG generation process for applicationId: {}", applicationId); ApplicationEntity application = applicationService.validateApplication(applicationId); CompanyEntity company = companyService.validateCompany(application.getCompanyId()); HubEntity hub = hubRepository.findByHubId(application.getHubId()); @@ -487,14 +495,15 @@ public class AppointmentDao { handleNdgPolling(application, company, hub, authorizationToken); } } catch (Exception e) { - log.error("Error during NDG generation for applicationId: {}", applicationId, e); + log.error("Exception occurred during NDG generation. ApplicationId: {}, CompanyId: {}, HubId: {}, Error: {}", + applicationId, company.getId(), hub.getId(), e.getMessage(), e); } } private void handleNdgPolling(ApplicationEntity application, CompanyEntity company, HubEntity hub, String authorizationToken) { try { - log.info("Starting NDG polling for applicationId: {}", application.getId()); + log.info("Starting NDG polling for applicationId: {}, CompanyId: {}, HubId: {}", application.getId(),company.getId(), hub.getId()); long startTime = System.currentTimeMillis(); while (true) { @@ -506,12 +515,13 @@ public class AppointmentDao { try { // Fetch Visura list and attempt to parse NDG String visuraListJson = getVisuraList(application.getIdVisura(), authorizationToken, application, hub); + log.debug("Parsing NDG from visura list response | ApplicationId: {}", application.getId()); String ndg = parseNdgFromVisuraListResponse(visuraListJson); - if (isNdgValid(ndg)) { // CompanyEntity oldCompanyData = Utils.getClonedEntityForData(company); // ApplicationEntity oldApplicationData = Utils.getClonedEntityForData(application); + log.info("Valid NDG retrieved: {} | ApplicationId: {}", ndg, application.getId()); company.setNdg(ndg); application.setNdg(ndg); application.setNdgStatus(GepafinConstant.NDG_GENERATED); @@ -584,6 +594,7 @@ public class AppointmentDao { private String getVisuraList(String idVisura, String authorizationToken, ApplicationEntity application, HubEntity hub) { + log.info("Initiating Visura list retrieval | ApplicationId: {}, HubId: {}, IdVisura: {}", application.getId(), hub.getId(), idVisura); AppointmentVisuraListRequest visuraListRequest = new AppointmentVisuraListRequest(); AppointmentVisuraListRequest.VisuraFilter filter = new AppointmentVisuraListRequest.VisuraFilter(); filter.setIdVisura(idVisura); @@ -594,12 +605,12 @@ public class AppointmentDao { ResponseEntity response = appointmentApiService.getVisuraList(requestJson, authorizationToken); return Utils.convertObjectToJson(response.getBody()); } catch (FeignException.Forbidden forbiddenException) { - log.error("403 Forbidden received while getting visuraList for Ndg code. Regenerating token..."); + log.warn("403 Forbidden while fetching Visura list. Attempting token regeneration | ApplicationId: {}, HubId: {}", application.getId(), hub.getId()); // Regenerate the token and retry String newAuthorizationToken = regenerateTokenAndSave(hub, application); return getVisuraList(idVisura, newAuthorizationToken, application, hub); } catch (Exception e) { - log.error("Failed to fetch Ndg code: {}", e.getMessage(), e); + log.error("Error while fetching Visura list | ApplicationId: {}, HubId: {}, Error: {}", application.getId(), hub.getId(), e.getMessage(), e); throw new RuntimeException("Error fetching Ndg List", e); } } @@ -607,6 +618,7 @@ public class AppointmentDao { private AppointmentLoginResponse retrieveNdgByVatNumber(String vatNumber, String authorizationToken, HubEntity hub, ApplicationEntity application) { try { + log.info("Initiating NDG retrieval by VAT number | ApplicationId: {}, HubId: {}, VAT: {}", application.getId(), hub.getId(), vatNumber); // Prepare the NDG request AppointmentNdgRequest ndgRequest = getAppointmentNdgRequest(vatNumber); // Call the API to retrieve NDG @@ -615,12 +627,13 @@ public class AppointmentDao { // Parse and return the NDG response return parseNdgResponse(responseJson); } catch (FeignException.Forbidden forbiddenException) { + log.error("403 Forbidden during NDG retrieval | ApplicationId: {}, HubId: {}", application.getId(), hub.getId()); logForbiddenError(); // Regenerate the token and retry String newAuthorizationToken = regenerateTokenAndSave(hub, application); return retrieveNdgByVatNumber(vatNumber, newAuthorizationToken, hub, application); } catch (Exception e) { - log.error("Failed to retrieve NDG by VAT number: {}", e.getMessage(), e); + log.error("Error during NDG retrieval | ApplicationId: {}, HubId: {}, Message: {}", application.getId(), hub.getId(), e.getMessage(), e); throw new RuntimeException("NDG retrieval failed.", e); } } @@ -656,6 +669,7 @@ public class AppointmentDao { private static AppointmentNdgRequest getAppointmentNdgRequest(String vatNumber) { + log.info("Creating Appointment NDG Request | VAT Number: {}", vatNumber); AppointmentNdgRequest request = new AppointmentNdgRequest(); AppointmentNdgRequest.Filter filter = new AppointmentNdgRequest.Filter(); filter.setPartitaIva(vatNumber); @@ -786,6 +800,7 @@ public class AppointmentDao { public AppointmentCreationResponse createAppointment(Long applicationId, CreateAppointmentRequest createAppointmentRequest) { // Validate the application + log.info("Starting appointment creation for applicationId: {}", applicationId); ApplicationEntity application = applicationService.validateApplication(applicationId); AppointmentCreationResponse appointmentCreationResponse = new AppointmentCreationResponse(); @@ -808,6 +823,7 @@ public class AppointmentDao { } if (application.getNdg() == null && Objects.equals(application.getNdgStatus(), GepafinConstant.NDG_IN_PROGRESS)) { + log.warn("NDG in progress but not available for applicationId: {}", applicationId); throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.NDG_NOT_FOUND_FOR_APPLICATION)); } @@ -815,6 +831,7 @@ public class AppointmentDao { String authorizationToken = regenerateTokenAndSave(hub, application); Long appointmentTemplateId = application.getCall().getAppointmentTemplateId(); if (appointmentTemplateId == null) { + log.error("Missing appointment template ID for applicationId: {}", applicationId); throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.APPOINTMENT_CANNOT_BE_CREATED)); } ResponseEntity response = appointmentApiService.getAppointmentTemplateForTemplateCreation(authorizationToken, appointmentTemplateId); @@ -840,6 +857,7 @@ public class AppointmentDao { String appointmentId = extractAppointmentIdFromResponse(appointmentResponse); if (appointmentId == null) { + log.error("Failed to extract appointment ID from response for applicationId: {}", applicationId); throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.APPOINTMENT_NOT_CREATED)); } // Update application with the appointment ID @@ -855,7 +873,7 @@ public class AppointmentDao { return appointmentCreationResponse; } catch (FeignException.Forbidden forbiddenException) { - log.error("403 Forbidden received while retrieving template. Regenerating token..."); + log.error("403 Forbidden received while retrieving template. Attempting to regenerate token and retry. Application ID: {}", applicationId); regenerateTokenAndSave(hub, application); return createAppointment(applicationId, createAppointmentRequest); } @@ -990,6 +1008,7 @@ public class AppointmentDao { } public DocumentUploadResponse uploadDocumentToExternalSystem(Long documentId, UploadDocToExternalSystemRequest docToExternalSystemRequest) { + log.info("Initiating upload to external system for documentId: {}", documentId); // Check if the document is already being processed DocumentEntity systemDoc = documentDao.validateDocument(documentId); @@ -1098,6 +1117,7 @@ public class AppointmentDao { DocumentUploadResponse parsedResponse = parseDocumentUploadResponse(responseData); if (parsedResponse == null) { + log.error("Upload failed: parsed response is null for documentId: {}", documentId); throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.ERROR_UPLOADING_DOCUMENT)); } @@ -1107,7 +1127,7 @@ public class AppointmentDao { log.info("Document uploaded successfully to external system: {}", parsedResponse); } catch (FeignException.Forbidden forbiddenException) { - log.error("403 Forbidden received while uploading document. Regenerating token..."); + log.error("403 Forbidden from external system during upload for documentId: {}. Retrying with new token...", documentId); regenerateTokenAndSave(hub, application); uploadDocumentToExternalSystemSync(documentId, docToExternalSystemRequest, application); } catch (Exception e) { diff --git a/src/main/java/net/gepafin/tendermanagement/dao/AssignedApplicationsDao.java b/src/main/java/net/gepafin/tendermanagement/dao/AssignedApplicationsDao.java index 7339a7c5..c05b04c2 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/AssignedApplicationsDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/AssignedApplicationsDao.java @@ -87,8 +87,10 @@ public class AssignedApplicationsDao { AssignedApplicationsEntity assignedApplications = assignedApplicationsRepository.findByApplicationIdAndIsDeletedFalse(applicationId).orElse(null); if (assignedApplications != null && assignedApplications.getUserId().equals(userId)) { + log.warn("Application ID={} is already assigned to User ID={}", applicationId, userId); throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.APPLICATION_ALREADY_ASSIGNED)); } else if(assignedApplications != null) { + log.info("Reassigning Application ID={} from User ID={} to User ID={}", applicationId, assignedApplications.getUserId(), userId); assignedApplications = reassignApplication(userId, assignedByUser, assignedApplications); AssignedApplicationsResponse assignApplicationToInstructorResponse = convertEntityToResponse(assignedApplications); log.info("Application re-assigned succesfully {}", assignApplicationToInstructorResponse); @@ -98,6 +100,7 @@ public class AssignedApplicationsDao { if (Boolean.FALSE.equals(ApplicationStatusTypeEnum.SUBMIT.getValue().equals(application.getStatus()))) { + log.warn("Invalid application status for assignment. Application ID={}, Current Status={}", applicationId, application.getStatus()); throw new CustomValidationException( Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.INVALID_APPLICATION_STATUS) @@ -106,6 +109,7 @@ public class AssignedApplicationsDao { ApplicationEntity oldApplicationEntity = Utils.getClonedEntityForData(application); application.setStatus(ApplicationStatusTypeEnum.EVALUATION.getValue()); applicationRepository.save(application); + log.info("Application status updated to EVALUATION for Application ID={}", applicationId); /** This code is responsible for adding a version history log for the "Update Application" operation. **/ loggingUtil.addVersionHistory(VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldApplicationEntity).newData(application).build()); @@ -120,7 +124,10 @@ public class AssignedApplicationsDao { private AssignedApplicationsEntity reassignApplication(Long userId, UserEntity assignedByUser, AssignedApplicationsEntity assignedApplication) { - + + log.info("Starting reassignment for AssignedApplication ID={}, from User ID={} to User ID={}, Assigned By User ID={}", + assignedApplication.getId(), assignedApplication.getUserId(), userId, assignedByUser.getId()); + AssignedApplicationsEntity oldAssignedApplicationEntity = Utils.getClonedEntityForData(assignedApplication); setIfUpdated(assignedApplication::getAssignedBy, assignedApplication::setAssignedBy, assignedByUser.getId()); @@ -135,11 +142,13 @@ public class AssignedApplicationsDao { /** This code is responsible for adding a version history log for the "Create Application" operation. **/ loggingUtil.addVersionHistory(VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldApplicationEvaluationEntity).newData(entityOptional.get()).build()); + log.info("Updated ApplicationEvaluationEntity for AssignedApplication ID={}", assignedApplication.getId()); }; assignedApplication = assignedApplicationsRepository.save(assignedApplication); /** This code is responsible for adding a version history log for the "Create Application" operation. **/ loggingUtil.addVersionHistory(VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldAssignedApplicationEntity).newData(assignedApplication).build()); + log.info("Reassignment completed for AssignedApplication ID={}, new User ID={}", assignedApplication.getId(), userId); return assignedApplication; } @@ -220,8 +229,13 @@ public class AssignedApplicationsDao { } public AssignedApplicationsEntity validateAssignedApplication(Long id) { - AssignedApplicationsEntity assignedApplication = assignedApplicationsRepository.findByIdAndIsDeletedFalse(id).orElseThrow(() -> - new ResourceNotFoundException(Status.NOT_FOUND, Translator.toLocale(GepafinConstant.ASSIGNED_APPLICATION_NOT_FOUND_MSG))); + AssignedApplicationsEntity assignedApplication = assignedApplicationsRepository.findByIdAndIsDeletedFalse(id) .orElseThrow(() -> { + log.warn("AssignedApplication not found or deleted for ID: {}", id); + return new ResourceNotFoundException( + Status.NOT_FOUND, + Translator.toLocale(GepafinConstant.ASSIGNED_APPLICATION_NOT_FOUND_MSG) + ); + }); return assignedApplication; } @@ -232,11 +246,12 @@ public class AssignedApplicationsDao { AssignedApplicationsEntity oldAssignedApplicationEntity = Utils.getClonedEntityForData(assignedApplicationsEntity); assignedApplicationsEntity.setIsDeleted(true); assignedApplicationsEntity = saveAssignedApplication(assignedApplicationsEntity, oldAssignedApplicationEntity, VersionActionTypeEnum.SOFT_DELETE); - log.info("Assigned Application deleted with ID: {}", id); + log.info("Soft-delete completed for AssignedApplication ID: {} by User ID: {}", id, assignedApplicationsEntity.getUserId()); } public List getAllAssignedApplications(HttpServletRequest request, Long userId,List statusList) { - UserEntity user = validator.validateUser(request); + log.info("Fetching all assigned applications. Filtered target userId: {}", userId); + UserEntity user = validator.validateUser(request); if(validator.checkIsPreInstructor() && userId == null) { throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.USER_ID_NOT_NULL_MSG)); } @@ -303,6 +318,8 @@ public class AssignedApplicationsDao { return response; } public PageableResponseBean> getAllAssignedApplicationsByPagination(UserEntity user, AssignedApplicationPageableRequestBean assignedApplicationPageableRequestBean,Long userId) { + log.info("Fetching assigned applications. Requestor: {}, Target User: {}", user.getId(), userId); + Integer pageNo = null; Integer pageLimit = null; if (assignedApplicationPageableRequestBean.getGlobalFilters() != null) { @@ -478,13 +495,16 @@ public class AssignedApplicationsDao { } public AssignedApplicationsResponse updateAssignedApplicationStatus(HttpServletRequest request, Long assignedApplicationId, AssignedApplicationEnum status) { - + log.info("Request received to update status of assigned application. AssignedApplicationId: {}, NewStatus: {}", assignedApplicationId, status); AssignedApplicationsEntity assignedApplication = validateAssignedApplication(assignedApplicationId); validator.validatePreInstructor(request, assignedApplication.getUserId()); AssignedApplicationsEntity oldAssignedApplicationEntity = Utils.getClonedEntityForData(assignedApplication); assignedApplication.setStatus(status.getValue()); AssignedApplicationsEntity updatedAssignment = saveAssignedApplication(assignedApplication, oldAssignedApplicationEntity, VersionActionTypeEnum.UPDATE); + log.info("Assigned application status updated successfully. AssignedApplicationId: {}, OldStatus: {}, NewStatus: {}", + assignedApplicationId, oldAssignedApplicationEntity.getStatus(), status.getValue()); + return convertEntityToResponse(updatedAssignment); } private void applyFilters(Root root, CriteriaBuilder criteriaBuilder, List predicates, Map filters) { diff --git a/src/main/java/net/gepafin/tendermanagement/dao/CallDao.java b/src/main/java/net/gepafin/tendermanagement/dao/CallDao.java index 7fc9663a..94c6f912 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/CallDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/CallDao.java @@ -50,6 +50,7 @@ import net.gepafin.tendermanagement.web.rest.api.errors.ResourceNotFoundExceptio import net.gepafin.tendermanagement.web.rest.api.errors.Status; import static net.gepafin.tendermanagement.enums.RoleStatusEnum.ROLE_SUPER_ADMIN; +import static net.gepafin.tendermanagement.util.Utils.log; import static net.gepafin.tendermanagement.util.Utils.setIfUpdated; import static org.apache.commons.lang3.StringUtils.isEmpty; import static org.hibernate.internal.util.collections.CollectionHelper.listOf; @@ -128,6 +129,7 @@ public class CallDao { private ApplicationRepository applicationRepository; public CallResponse createCallStep1(CreateCallRequestStep1 createCallRequest, UserEntity userEntity) { + log.info("Starting Call creation - Step 1 by userId: {}", userEntity.getId()); createCallRequest.setRegionId(userEntity.getRoleEntity().getRegion().getId()); CallEntity callEntity = convertToCallEntity(createCallRequest, userEntity); @@ -136,12 +138,15 @@ public class CallDao { CallResponse createCallResponseBean = getCallResponseBean(callEntity); createCallResponseBean.setCurrentStep(GepafinConstant.STEP_1); + log.info("Call creation - Step 1 completed successfully for callId: {}", callEntity.getId()); return createCallResponseBean; } public byte[] downloadCallDocumentsAsZip(Long callId) { + log.info("Starting download of call documents as ZIP for callId: {}", callId); List documents = documentRepository.findBySourceIdAndSourceAndTypeAndIsDeletedFalse(callId, DocumentSourceTypeEnum.CALL.getValue(),DocumentTypeEnum.DOCUMENT.getValue()); if (documents.isEmpty()) { + log.warn("No documents found for callId: {}", callId); throw new ResourceNotFoundException(Status.NOT_FOUND, Translator.toLocale(GepafinConstant.DOCUMENT_NOT_FOUND)); } @@ -149,6 +154,7 @@ public class CallDao { ZipOutputStream zos = new ZipOutputStream(zipOutputStream)) { for (DocumentEntity document : documents) { + log.info("Adding document to ZIP: documentId={}, fileName={}", document.getId(), document.getFileName()); String s3Folder = s3PathConfig.generateDocumentPath(DocumentSourceTypeEnum.CALL, callId, 0L,0L); try (InputStream fileInputStream = amazonS3Service.getFile(s3Folder, document.getFilePath())) { String fileName = Utils.extractFileName(document.getFilePath()); @@ -157,14 +163,17 @@ public class CallDao { IOUtils.copy(fileInputStream, zos); zos.closeEntry(); } catch (IOException e) { + log.error("Error downloading or adding document to ZIP. documentId={}, fileName={}", document.getId(), document.getFileName(), e); throw new RuntimeException("Error downloading or adding document to ZIP: " + document.getFileName(), e); } } zos.finish(); + log.info("Successfully created ZIP file for callId: {}", callId); return zipOutputStream.toByteArray(); } catch (IOException e) { + log.error("Error while creating ZIP file for callId: {}", callId, e); throw new RuntimeException("Error while creating ZIP file", e); } } @@ -175,8 +184,11 @@ public class CallDao { CallEntity callEntity = new CallEntity(); // validateCallEntity(createCallRequest); RegionEntity region = regionRepository.findById(createCallRequest.getRegionId()) - .orElseThrow(() -> new ResourceNotFoundException(Status.NOT_FOUND, - Translator.toLocale(GepafinConstant.REGION_NOT_FOUND))); + .orElseThrow(() -> { + log.error("Region not found for id: {}", createCallRequest.getRegionId()); + return new ResourceNotFoundException(Status.NOT_FOUND, Translator.toLocale(GepafinConstant.REGION_NOT_FOUND)); + }); + callEntity.setRegion(region); callEntity.setName(createCallRequest.getName()); callEntity.setDescriptionShort(createCallRequest.getDescriptionShort()); @@ -198,6 +210,7 @@ public class CallDao { } callEntity.setDocumentationRequested(createCallRequest.getDocumentationRequested()); if (createCallRequest.getAmountMin() != null && createCallRequest.getAmountMin().compareTo(BigDecimal.ZERO) < 0) { + log.error("Invalid minimum amount: {}", createCallRequest.getAmountMin()); throw new CustomValidationException(Status.VALIDATION_ERROR,Translator.toLocale(GepafinConstant.AMOUNT_GREATER_THAN_ZERO_MSG)); } callEntity.setAmountMin(createCallRequest.getAmountMin()); @@ -212,6 +225,7 @@ public class CallDao { callEntity.setNumberOfCheck(createCallRequest.getNumberOfCheck()); callEntity.setAppointmentTemplateId(createCallRequest.getAppointmentTemplateId()); callEntity = callRepository.save(callEntity); + log.info("CallEntity saved with ID: {} for call name: '{}'", callEntity.getId(), callEntity.getName()); /** This code is responsible for adding a version history log for the "Create Call" operation. **/ loggingUtil.addVersionHistory(VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.INSERT).oldData(null).newData(callEntity).build()); @@ -237,9 +251,11 @@ public class CallDao { } private void softDeleteEvaluationCriteria(EvaluationCriteriaEntity evaluationCriteriaEntity) { + log.info("Starting soft delete for EvaluationCriteriaEntity with ID: {}", evaluationCriteriaEntity.getId()); EvaluationCriteriaEntity oldEvaluationCriteriaEntity = Utils.getClonedEntityForData(evaluationCriteriaEntity); evaluationCriteriaEntity.setIsDeleted(true); evaluationCriteriaRepository.save(evaluationCriteriaEntity); + log.info("Soft deleted EvaluationCriteriaEntity with ID: {}", evaluationCriteriaEntity.getId()); /** This code is responsible for adding a version history log for the "soft delete evaluation criteria" operation **/ loggingUtil.addVersionHistory(VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.SOFT_DELETE).oldData(oldEvaluationCriteriaEntity).newData(evaluationCriteriaEntity).build()); @@ -256,8 +272,8 @@ public class CallDao { /** This code is responsible for adding a version history log for the "soft delete criteria form field" operation **/ loggingUtil.addVersionHistory(VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.SOFT_DELETE).oldData(oldCriteriaFormFieldEntity).newData(data).build()); }); - criteriaFormFieldRepository.saveAll(list); - + criteriaFormFieldRepository.saveAll(list); + log.info("Soft deleted all linked CriteriaFormFieldEntity records for EvaluationCriteriaEntity ID: {}", evaluationCriteriaEntity.getId()); } } @@ -327,8 +343,11 @@ public class CallDao { private DocumentEntity convertToDocumentEntity(DocumentReq documentReq,Long sourceId) { validateDocumentEntity(documentReq.getId()); DocumentEntity documentEntity = documentRepository.findByIdAndSourceIdAndSourceAndIsDeletedFalse(documentReq.getId(),sourceId, DocumentSourceTypeEnum.CALL.getValue()) - .orElseThrow(() -> new ResourceNotFoundException(Status.NOT_FOUND, - Translator.toLocale(GepafinConstant.DOCUMENT_NOT_FOUND))); + .orElseThrow(() -> { + log.error("Document not found or already deleted. Document ID: {}, Source ID: {}", documentReq.getId(), sourceId); + return new ResourceNotFoundException(Status.NOT_FOUND, + Translator.toLocale(GepafinConstant.DOCUMENT_NOT_FOUND)); + }); return documentEntity; } @@ -353,6 +372,7 @@ public class CallDao { public void validateDocumentEntity(Long documentId) { if (documentId == null || documentId < 1) { + log.warn("Invalid Document ID provided: {}", documentId); throw new CustomValidationException(Status.VALIDATION_ERROR, Translator.toLocale(GepafinConstant.DOCUMENT_ID_NOT_FOUND)); } @@ -491,13 +511,16 @@ public class CallDao { } public CallEntity validateCall(Long callId) { - return callRepository.findById(callId).orElseThrow(() -> new ResourceNotFoundException(Status.NOT_FOUND, - Translator.toLocale(GepafinConstant.CALL_NOT_FOUND))); + return callRepository.findById(callId).orElseThrow(() -> { log.error("Call not found for ID: {}", callId); + return new ResourceNotFoundException(Status.NOT_FOUND, + Translator.toLocale(GepafinConstant.CALL_NOT_FOUND)); + }); } public CallResponse getCallById(HttpServletRequest request,UserEntity user, CallEntity callEntity, Long companyId) { Long userId = user.getId(); Long callId = callEntity.getId(); + log.info("Fetching Call details for Call ID: {}, User ID: {}, Company ID: {}", callId, userId, companyId); BeneficiaryPreferredCallEntity preferredCall; if (companyId != null) { @@ -523,10 +546,13 @@ public class CallDao { public CallResponse createCallStep2(CallEntity callEntity, CreateCallRequestStep2 createCallRequest, UserEntity user) { // validateUpdate(callEntity); + log.info("Starting Call Step 2 update for Call ID: {}, User ID: {}", callEntity.getId(), user.getId()); if(createCallRequest.getThreshold() != null && Boolean.FALSE.equals(createCallRequest.getThreshold().equals(callEntity.getThreshold()))) { CallEntity oldCallEntity = Utils.getClonedEntityForData(callEntity); setIfUpdated(callEntity::getThreshold, callEntity::setThreshold, createCallRequest.getThreshold()); callEntity = callRepository.save(callEntity); + + log.info("Updated threshold for Call ID: {}", callEntity.getId()); /** This code is responsible for adding a version history log for the "update call step 2" operation **/ loggingUtil.addVersionHistory(VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldCallEntity).newData(callEntity).build()); @@ -546,6 +572,7 @@ public class CallDao { public void validateUpdate(CallEntity callEntity) { if(callEntity.getStatus().equals(CallStatusEnum.PUBLISH.getValue())) { + log.warn("Attempted update on published call. Call ID: {}", callEntity.getId()); throw new CustomValidationException(Status.VALIDATION_ERROR, Translator.toLocale(GepafinConstant.PUBLISHED_CALL_NOT_UPDATE)); } @@ -574,12 +601,14 @@ public class CallDao { } if (Boolean.FALSE.equals(isValid)) { + log.error("Invalid date range detected for Call ID: {}", callEntity.getId()); throw new CustomValidationException(Status.VALIDATION_ERROR, Translator.toLocale(GepafinConstant.INVALID_DATE_MSG)); } } public CallResponse updateCallStep1(HttpServletRequest request,CallEntity callEntity, UpdateCallRequestStep1 updateCallRequest, UserEntity userEntity) { + log.info("Updating Call ID: {}, by User ID: {}", callEntity.getId(),userEntity.getId() ); CallEntity oldCallEntity = Utils.getClonedEntityForData(callEntity); isValidDateRange(updateCallRequest, callEntity); setIfUpdated(callEntity::getName, callEntity::setName, updateCallRequest.getName()); @@ -639,9 +668,11 @@ public class CallDao { updateCallRequest.getDocumentationRequested()); if (updateCallRequest.getAmountMin() != null && updateCallRequest.getAmountMin().compareTo(BigDecimal.ZERO) < 0) { + log.error("Validation failed: Invalid email {} for Call ID: {}", updateCallRequest.getEmail(), callEntity.getId()); throw new CustomValidationException(Status.VALIDATION_ERROR,Translator.toLocale(GepafinConstant.AMOUNT_GREATER_THAN_ZERO_MSG)); } if(updateCallRequest.getEmail()!=null && Boolean.FALSE.equals(Utils.isValidEmail(updateCallRequest.getEmail()))){ + log.error("Validation failed: Invalid email {} for Call ID: {}", updateCallRequest.getEmail(), callEntity.getId()); throw new CustomValidationException(Status.VALIDATION_ERROR,Translator.toLocale(GepafinConstant.VALIDATION_EMAIL,updateCallRequest.getEmail())); } setIfUpdated(callEntity::getAmountMin, callEntity::setAmountMin, updateCallRequest.getAmountMin()); @@ -661,6 +692,7 @@ public class CallDao { updateFaq(updateCallRequest.getFaq(), callEntity, userEntity, LookUpDataTypeEnum.FAQ); CallResponse createCallResponseBean = getCallResponseBean(callEntity); createCallResponseBean.setCurrentStep(GepafinConstant.STEP_1); + log.info("Call Step 1 update completed for Call ID: {}", callEntity.getId()); return createCallResponseBean; } @@ -764,6 +796,7 @@ public class CallDao { } private CallResponse getCallResponseBean(CallEntity callEntity) { + log.info("Building CallResponse for Call ID: {}", callEntity.getId()); List documentEntities = documentRepository.findBySourceIdAndSourceAndTypeAndIsDeletedFalse(callEntity.getId(),DocumentSourceTypeEnum.CALL.getValue() , DocumentTypeEnum.DOCUMENT.getValue()); List imageEntities = documentRepository.findBySourceIdAndSourceAndTypeAndIsDeletedFalse(callEntity.getId(), DocumentSourceTypeEnum.CALL.getValue() @@ -790,6 +823,9 @@ public class CallDao { public List getAllCalls(HttpServletRequest request,UserEntity user, Long companyId,Boolean onlyPreferredCall,Boolean onlyConfidiCall) { String type = user.getRoleEntity().getRoleType(); + log.info("Fetching calls for User ID: {}, Role: {}, Company ID: {}, onlyPreferredCall: {}, onlyConfidiCall: {}", + user.getId(), type, companyId, onlyPreferredCall, onlyConfidiCall); + List callStatusList = CallStatusEnum.getStatusValues(); if (Boolean.FALSE.equals(ROLE_SUPER_ADMIN.getValue().equals(type))) { callStatusList = List.of(CallStatusEnum.PUBLISH.getValue()); @@ -811,6 +847,7 @@ public class CallDao { call.getEndDate() != null && call.getEndTime() != null) { LocalDateTime callEndDateTime = LocalDateTime.of(LocalDate.from(call.getEndDate()), call.getEndTime()); if (callEndDateTime.isBefore(now)) { + log.info("Call ID: {} has expired. Updating status from PUBLISH to EXPIRED.", call.getId()); call.setStatus(CallStatusEnum.EXPIRED.getValue()); callRepository.save(call); } @@ -846,14 +883,17 @@ public class CallDao { } public Map getBeneficiaryPreferredCallsForUser(HttpServletRequest request, UserEntity user, List callIds, Long companyId) { + log.info("Fetching preferred calls for User ID: {}, Company ID: {}, Call IDs: {}", user.getId(), companyId, callIds); List beneficiaryPreferredCalls; if (companyId != null && (Boolean.TRUE.equals(validator.checkIsBeneficiary()) || Boolean.TRUE.equals(validator.checkIsConfidi()))) { + log.info("Validating user with company for preferred calls: User ID: {}, Company ID: {}", user.getId(), companyId); validator.validateUserWithCompany(request, companyId); UserWithCompanyEntity userWithCompanyEntity=companyService.getUserWithCompany(user.getId(),companyId); beneficiaryPreferredCalls = beneficiaryPreferredCallRepository .findByUserIdAndCallIdInAndUserWithCompanyIdAndIsDeletedFalse(user.getId(), callIds, userWithCompanyEntity.getId()); } else { + log.info("Fetching preferred calls without company filtering for User ID: {}", user.getId()); beneficiaryPreferredCalls = beneficiaryPreferredCallRepository .findByUserIdAndCallIdInAndIsDeletedFalse(user.getId(), callIds); beneficiaryPreferredCalls = beneficiaryPreferredCalls.stream() @@ -877,6 +917,7 @@ public class CallDao { public CallResponse validateCallData(CallEntity callEntity) { + log.info("Starting call validation for Call ID: {}, Current Status: {}", callEntity.getId(), callEntity.getStatus()); CallEntity oldCallEntity = Utils.getClonedEntityForData(callEntity); validateUpdate(callEntity); CallResponse callResponseBean = getCallResponseBean(callEntity); @@ -886,6 +927,7 @@ public class CallDao { CallValidatorServiceImpl.validateResponse(callResponseBean,flowResponseBean,formResponseBean,evaluationFormResponseBean); callEntity.setStatus(CallStatusEnum.READY_TO_PUBLISH.getValue()); callEntity = callRepository.save(callEntity); + log.info("Call status updated to READY_TO_PUBLISH for Call ID: {}", callEntity.getId()); /** This code is responsible for adding a version history log for the "validate call" operation **/ loggingUtil.addVersionHistory(VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldCallEntity).newData(callEntity).build()); @@ -903,11 +945,13 @@ public class CallDao { // } public CallResponse updateCallStatus(CallEntity callEntity, CallStatusEnum statusReq) { + log.info("Updating call status for Call ID: {} from {} to {}", callEntity.getId(), callEntity.getStatus(), statusReq); CallEntity oldCallEntity = Utils.getClonedEntityForData(callEntity); CallStatusEnum currentStatus = CallStatusEnum.valueOf(callEntity.getStatus()); validateStatusChange(currentStatus, statusReq, callEntity.getId()); callEntity.setStatus(statusReq.getValue()); callEntity = callRepository.save(callEntity); + log.info("Call status updated in DB for Call ID: {}. New Status: {}", callEntity.getId(), callEntity.getStatus()); //Creating notification. List userIds = beneficiaryRepository.findUserIdsByHubIdAndBeneficiaryId(callEntity.getHub().getId()); @@ -926,27 +970,32 @@ public class CallDao { } private void validateStatusChange(CallStatusEnum currentStatus, CallStatusEnum newStatus, Long callId) { - + log.info("Validating status change for Call ID: {} from '{}' to '{}'", callId, currentStatus, newStatus); if (currentStatus == newStatus) { + log.warn("Validation failed: current status and new status are the same for Call ID: {}", callId); throw new CustomValidationException(Status.VALIDATION_ERROR, Translator.toLocale(GepafinConstant.STATUS_SAME_ERROR)); } switch (currentStatus) { case DRAFT: if (newStatus == CallStatusEnum.READY_TO_PUBLISH || newStatus == CallStatusEnum.PUBLISH) { + log.warn("Invalid status change attempt from DRAFT to {} for Call ID: {}", newStatus, callId); throw new CustomValidationException(Status.VALIDATION_ERROR, Translator.toLocale(GepafinConstant.INVALID_STATUS_CHANGE_FROM_DRAFT)); } break; case PUBLISH: if (newStatus == CallStatusEnum.READY_TO_PUBLISH) { + log.warn("Invalid status change attempt from PUBLISH to READY_TO_PUBLISH for Call ID: {}", callId); throw new CustomValidationException(Status.VALIDATION_ERROR, Translator.toLocale(GepafinConstant.INVALID_STATUS_CHANGE_FROM_PUBLISH)); } if (newStatus == CallStatusEnum.DRAFT && Boolean.TRUE.equals(applicationRepository.existsByCallId(callId))) { + log.warn("Invalid status change attempt from PUBLISH to DRAFT for Call ID: {} due to existing applications", callId); throw new CustomValidationException(Status.VALIDATION_ERROR, Translator.toLocale(GepafinConstant.INVALID_STATUS_CHANGE_FROM_PUBLISH_TO_DRAFT)); } break; case EXPIRED: + log.warn("Attempt to change status from EXPIRED for Call ID: {} which is not allowed", callId); throw new CustomValidationException(Status.VALIDATION_ERROR, Translator.toLocale(GepafinConstant.STATUS_CANNOT_BE_CHANGED)); case READY_TO_PUBLISH: @@ -957,9 +1006,11 @@ public class CallDao { } public CallEntity validatePublishedCall(Long callId, Long hubId) { + log.info("Validating published call for Call ID: {}, Hub ID: {}", callId, hubId); CallEntity callEntity= callRepository .findByIdAndStatusAndHubId(callId, CallStatusEnum.PUBLISH.getValue(), hubId); if(callEntity==null){ + log.warn("No published call found with Call ID: {} and Hub ID: {}", callId, hubId); throw new ResourceNotFoundException( Status.NOT_FOUND, Translator.toLocale(GepafinConstant.CALL_NOT_PUBLISHED)); @@ -969,6 +1020,7 @@ public class CallDao { if (currentDate.isBefore(callEntity.getStartDate().toLocalDate()) || (currentDate.isEqual(callEntity.getStartDate().toLocalDate()) && currentTime.isBefore(callEntity.getStartTime()))) { + log.warn("Call ID: {} has not started yet. Current time is before start time.", callId); throw new CustomValidationException( Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.CALL_NOT_STARTED_YET) @@ -977,6 +1029,7 @@ public class CallDao { if (currentDate.isAfter(callEntity.getEndDate().toLocalDate()) || (currentDate.isEqual(callEntity.getEndDate().toLocalDate()) && currentTime.isAfter(callEntity.getEndTime()))) { + log.warn("Call ID: {} has already ended. Current time is after end time.", callId); throw new CustomValidationException( Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.CALL_ALREADY_ENDED) @@ -987,6 +1040,7 @@ public class CallDao { } public PageableResponseBean> getAllCallsByPagination(HttpServletRequest request,UserEntity user,Long companyId , Boolean onlyPreferredCall, Boolean onlyConfidiCall, CallPageableRequestBean callPageableRequestBean) { + log.info("Fetching paginated calls for userId={}, companyId={}, onlyPreferredCall={}", user.getId(), companyId, onlyPreferredCall); Integer pageNo = null; Integer pageLimit = null; if (callPageableRequestBean.getGlobalFilters() != null) { @@ -1009,6 +1063,7 @@ public class CallDao { Specification spec = search(request,user, callPageableRequestBean,onlyConfidiCall); Page entityPage; if (Boolean.TRUE.equals(onlyPreferredCall)) { + log.debug("Filtering calls for preferred by userId={} and companyId={}", user.getId(), companyId); validator.validateUserWithCompany(request, companyId); UserWithCompanyEntity userWithCompanyEntity = companyService.getUserWithCompany(user.getId(), companyId); List preferredCalls = beneficiaryPreferredCallRepository @@ -1171,6 +1226,8 @@ public class CallDao { LocalDate currentDate = DateTimeUtil.DateServerToUTC(LocalDateTime.now()).toLocalDate(); LocalTime currentTime = DateTimeUtil.LocalTimeServerToEurope(LocalTime.now()); + + log.info("Checking for expired published calls at date={}, time={}", currentDate, currentTime); List expirdedCallList = callRepository.findExpiredCallsWhichIsPublished(CallStatusEnum.PUBLISH.getValue(), currentDate, currentTime); @@ -1321,7 +1378,7 @@ public class CallDao { public CallResponse createCallStep2EvaluationV2(CallEntity callEntity, CreateCallRequestStep2EvaluationV2 createCallRequest, UserEntity user) { - + log.info("Starting Step 2 Evaluation (V2) for Call ID={}, User ID={}", callEntity.getId(), user.getId()); convertToDocumentEntities(createCallRequest.getDocs(), callEntity.getId(), DocumentTypeEnum.DOCUMENT); convertToDocumentEntities(createCallRequest.getImages(), callEntity.getId(), DocumentTypeEnum.IMAGES); diff --git a/src/main/java/net/gepafin/tendermanagement/dao/CompanyDao.java b/src/main/java/net/gepafin/tendermanagement/dao/CompanyDao.java index 91a589e7..9cf2e0e2 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/CompanyDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/CompanyDao.java @@ -73,6 +73,7 @@ public class CompanyDao { public CompanyResponse createCompany(UserEntity userEntity, CompanyRequest companyRequest) { + log.info("Initiating company creation by userId: {}", userEntity.getId()); CompanyEntity existingCompany = companyRepository.findByVatNumberAndHubId(companyRequest.getVatNumber(), userEntity.getHub().getId()); UserWithCompanyEntity userWithCompanyEntity = null; if (existingCompany != null) { @@ -84,6 +85,7 @@ public class CompanyDao { loggingUtil.addVersionHistory( VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.INSERT).oldData(null).newData(userWithCompanyEntity).build()); } else { + log.warn("User already connected to company. userId: {}, companyId: {}", userEntity.getId(), existingCompany.getId()); throw new CustomValidationException(Status.VALIDATION_ERROR, Translator.toLocale(GepafinConstant.USER_ALREADY_CONNECTED_TO_COMPANY)); } return convertCompanyEntityToCompanyResponse(existingCompany, userWithCompanyEntity); @@ -121,6 +123,7 @@ public class CompanyDao { private UserWithCompanyEntity createUserWithCompanyRelation(UserEntity userEntity, CompanyEntity companyEntity, Boolean isLegalRepresentant,CompanyRequest companyRequest) { + log.info("Creating user-company relation. userId: {}, companyId: {}, isLegalRep: {}", userEntity.getId(), companyEntity.getId(), isLegalRepresentant); UserWithCompanyEntity userWithCompanyEntity = new UserWithCompanyEntity(); if (userEntity.getBeneficiary() != null) { userWithCompanyEntity.setBeneficiaryId(userEntity.getBeneficiary().getId()); @@ -140,6 +143,7 @@ public class CompanyDao { companyEntity.setJson(Utils.convertMapIntoJsonString(companyRequest.getVatCheckResponse())); updateCodiceAtecoFieldWithNewJson(companyEntity); companyEntity = companyRepository.save(companyEntity); + log.info("Updated company JSON field and saved. companyId: {}", companyEntity.getId()); /** This code is responsible for adding a version history log for "updating company json field" operation. **/ loggingUtil.addVersionHistory(VersionHistoryRequest.builder() @@ -202,6 +206,7 @@ public class CompanyDao { public CompanyResponse updateCompany(UserEntity userEntity, Long companyId, CompanyRequest companyRequest) { + log.info("Updating company. companyId: {}, userId: {}", companyId, userEntity.getId()); CompanyEntity companyEntity = validateCompany(companyId); //cloned entity for old data CompanyEntity oldCompanyData = Utils.getClonedEntityForData(companyEntity); @@ -226,11 +231,14 @@ public class CompanyDao { // // } companyRepository.save(companyEntity); + log.info("Company updated and saved. companyId: {}", companyEntity.getId()); /** This code is responsible for adding a version history log for the "Update company" operation. **/ loggingUtil.addVersionHistory( VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldCompanyData).newData(companyEntity).build()); + log.info("Logged version history for company update. companyId: {}", companyEntity.getId()); + UserWithCompanyEntity userWithCompanyEntity = getUserWithCompany(userEntity.getId(), companyId); //cloned entity for old data UserWithCompanyEntity oldUserWithCompanyData = Utils.getClonedEntityForData(userWithCompanyEntity); @@ -253,17 +261,19 @@ public class CompanyDao { } public CompanyEntity validateCompany(Long companyId) { + log.info("Validating company. companyId: {}", companyId); return companyRepository.findById(companyId).orElseThrow(() -> new ResourceNotFoundException(Status.NOT_FOUND, Translator.toLocale(GepafinConstant.COMPANY_NOT_FOUND_MSG))); } public CompanyResponse getCompany(UserEntity userEntity, Long companyId) { + log.info("Fetching company details. userId: {}, companyId: {}", userEntity.getId(), companyId); UserWithCompanyEntity userWithCompanyEntity = getUserWithCompany(userEntity.getId(), companyId); return convertCompanyEntityToCompanyResponse(validateCompany(companyId), userWithCompanyEntity); } public void deleteCompany(UserEntity userEntity, Long companyId) { - + log.info("Deleting company. userId: {}, companyId: {}", userEntity.getId(), companyId); CompanyEntity companyEntity = validateCompany(companyId); /** This code is responsible for adding a version history log for the "delete company" operation. **/ @@ -281,6 +291,7 @@ public class CompanyDao { } public List getCompanyByUserId(Long userId) { + log.info("Fetching companies by userId: {}", userId); UserEntity userEntity = userService.validateUser(userId); List activeCompanyIds = userWithCompanyRepository.findActiveCompanyIdsByUserId(userEntity.getId()); List companies = companyRepository.findByIdInAndHubId(activeCompanyIds, userEntity.getHub().getId()); @@ -291,15 +302,18 @@ public class CompanyDao { } public UserWithCompanyEntity validateUserWithCompny(Long userId, Long companyId) { + log.info("Validating user-company access. userId: {}, companyId: {}", userId, companyId); return userWithCompanyRepository.findByUserIdAndCompanyIdAndIsDeletedFalse(userId, companyId).orElseThrow(() -> new ForbiddenAccessException(Status.FORBIDDEN, Translator.toLocale(GepafinConstant.PERMISSION_DENIED))); } public UserWithCompanyEntity getUserWithCompany(Long userId, Long compnayId) { + log.info("Fetching user-company relation. userId: {}, companyId: {}", userId, compnayId); return userWithCompanyRepository.findByUserIdAndCompanyIdAndIsDeletedFalse(userId, compnayId).orElseThrow( () -> new ResourceNotFoundException(Status.NOT_FOUND, Translator.toLocale(GepafinConstant.USER_COMPANY_RELATION_NOT_FOUND))); } public void removeCompanyFromList(UserEntity userEntity, Long companyId) { + log.info("Initiating removal of company from user list. userId: {}, companyId: {}", userEntity.getId(), companyId); CompanyEntity companyEntity = validateCompany(companyId); UserWithCompanyEntity existingRelation=companyService.getUserWithCompany(userEntity.getId(),companyEntity.getId()); List userApplications = applicationRepository.findByUserWithCompanyIdAndUserIdAndIsDeletedFalse(existingRelation.getId(), userEntity.getId()); @@ -314,6 +328,7 @@ public class CompanyDao { boolean notAllowedStatus = userApplications.stream() .anyMatch(application -> !applicationStatusAllowed.contains(application.getStatus())); if (notAllowedStatus) { + log.warn("Cannot remove company. One or more applications in non-removable status. userId: {}, companyId: {}", userEntity.getId(), companyId); throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.CANNOT_DELETE_COMPANY_WITH_APPLICATION_SUBMITT)); } diff --git a/src/main/java/net/gepafin/tendermanagement/dao/CompanyDocumentDao.java b/src/main/java/net/gepafin/tendermanagement/dao/CompanyDocumentDao.java index 53a1f410..acf12515 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/CompanyDocumentDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/CompanyDocumentDao.java @@ -92,16 +92,20 @@ public class CompanyDocumentDao { private AmazonS3 amazonS3; public List uploadFileForCompany(HttpServletRequest request, Long userId, List files, Long companyId, Long documentCategoryId, CompanyDocumentTypeEnum companyDocumentSourceTypeEnum, LocalDateTime expirationDate,String name){ + + log.info("Uploading files for company. userId={}, companyId={}, documentCategoryId={}", userId, companyId, documentCategoryId); DocumentCategoryEntity categoryEntity = categoryDao.validateCategory(documentCategoryId); validator.validateUserWithCompany(request,companyId); UserWithCompanyEntity userWithCompanyEntity=companyService.getUserWithCompany(userId,companyId); LocalDateTime currentDate = LocalDateTime.now(); if (expirationDate.isBefore(currentDate)) { + log.warn("Expiration date {} is before current time {}", expirationDate, currentDate); throw new CustomValidationException(Status.VALIDATION_ERROR, Translator.toLocale(GepafinConstant.INVALID_EXPIRATION_DATE)); } List companyDocumentEntities = new ArrayList<>(); for (MultipartFile file : files){ + log.info("Uploading file '{}' for companyId={}", file.getOriginalFilename(), companyId); UploadFileOnAmazonS3Response uploadFileOnAmazonS3Response = uploadFileOnAmazonS3(file, companyDocumentSourceTypeEnum, companyId); if (uploadFileOnAmazonS3Response != null) { CompanyDocumentEntity companyDocumentEntity = new CompanyDocumentEntity(); @@ -114,8 +118,10 @@ public class CompanyDocumentDao { companyDocumentEntity.setName(name); if (expirationDate.isBefore(currentDate.plusDays(7))) { companyDocumentEntity.setStatus(CompanyDocumentStatusEnum.DUE.getValue()); + log.info("Document '{}' marked as DUE (expires within 7 days).", uploadFileOnAmazonS3Response.getFileName()); } else { companyDocumentEntity.setStatus(CompanyDocumentStatusEnum.VALID.getValue()); + log.info("Document '{}' marked as VALID.", uploadFileOnAmazonS3Response.getFileName()); } companyDocumentEntity.setCategoryEntity(categoryEntity); @@ -125,9 +131,9 @@ public class CompanyDocumentDao { } } companyDocumentRepository.saveAll(companyDocumentEntities); + log.info("Saved {} documents for companyId={}", companyDocumentEntities.size(), companyId); /** This code is responsible for adding a version history log for the "Upload company document" operation. **/ - companyDocumentEntities.forEach(entity -> loggingUtil.addVersionHistory( VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.INSERT).oldData(null).newData(entity).build())); @@ -143,6 +149,7 @@ public class CompanyDocumentDao { log.info("Generated S3 path {}", s3Path); return amazonS3Service.uploadFileOnAmazonS3(s3Path, file); } catch (Exception e) { + log.error("Error occurred while uploading file '{}' for Company ID '{}': {}", file.getOriginalFilename(), companyId, e.getMessage()); throw new CustomValidationException(Status.VALIDATION_ERROR, Translator.toLocale(GepafinConstant.UPLOAD_ERROR_S3)); } } @@ -151,13 +158,17 @@ public class CompanyDocumentDao { try { return s3ConfigBean.generateCompanyDocumentPath(typeOfDocument, companyId); } catch (IllegalArgumentException e) { + log.error("Failed to generate S3 path for Company ID '{}' and Document Type '{}': {}", companyId, typeOfDocument, e.getMessage()); throw new CustomValidationException(Status.VALIDATION_ERROR, Translator.toLocale(GepafinConstant.S3_PATH_GENERATION_ERROR_MSG)); } } public CompanyDocumentEntity validateCompanyDocument(Long id) { - return companyDocumentRepository.findByIdAndNotDeleted(id).orElseThrow(() -> new ResourceNotFoundException(Status.NOT_FOUND, - Translator.toLocale(GepafinConstant.COMPANY_DOCUMENT_NOT_FOUND))); + return companyDocumentRepository.findByIdAndNotDeleted(id).orElseThrow(() -> { + log.warn("Company Document not found with ID '{}'", id); + return new ResourceNotFoundException(Status.NOT_FOUND, + Translator.toLocale(GepafinConstant.COMPANY_DOCUMENT_NOT_FOUND)); + }); } public CompanyDocumentResponseBean convertToCompanyDocumentResponseBean(CompanyDocumentEntity entity) { @@ -182,20 +193,23 @@ public class CompanyDocumentDao { } public CompanyDocumentResponseBean updateCompanyDocument(HttpServletRequest request,Long companyDocumentId, CompanyDocumentRequest companyDocumentRequest){ - + log.info("Start: Updating company document with ID: {}", companyDocumentId); CompanyDocumentEntity companyDocumentEntity = validateCompanyDocument(companyDocumentId); validator.validateUserWithCompany(request,companyDocumentEntity.getCompanyId()); CompanyDocumentEntity oldCompanyDocumentData = Utils.getClonedEntityForData(companyDocumentEntity); LocalDateTime currentDate = LocalDateTime.now(); if (companyDocumentRequest.getExpirationDate() != null) { if (companyDocumentRequest.getExpirationDate().isBefore(currentDate)) { + log.warn("Invalid expiration date: {}", companyDocumentRequest.getExpirationDate()); throw new CustomValidationException(Status.VALIDATION_ERROR, Translator.toLocale(GepafinConstant.INVALID_EXPIRATION_DATE)); } companyDocumentEntity.setExpirationDate(companyDocumentRequest.getExpirationDate()); if (companyDocumentRequest.getExpirationDate().isBefore(currentDate.plusDays(7))) { companyDocumentEntity.setStatus(CompanyDocumentStatusEnum.DUE.getValue()); + log.info("Document '{}' marked as DUE.", companyDocumentEntity.getName()); } else { companyDocumentEntity.setStatus(CompanyDocumentStatusEnum.VALID.getValue()); + log.info("Document '{}' marked as VALID (expiration is beyond 7 days).", companyDocumentEntity.getName()); } } if (companyDocumentRequest.getCategoryId() != null && companyDocumentRequest.getCategoryId() >0) { @@ -204,6 +218,7 @@ public class CompanyDocumentDao { } setIfUpdated(companyDocumentEntity::getName, companyDocumentEntity::setName, companyDocumentRequest.getName()); companyDocumentRepository.save(companyDocumentEntity); + log.info("Saved updates for Company Document ID '{}'", companyDocumentId); /** This code is responsible for adding a version history log for the "updating company document" operation. **/ loggingUtil.addVersionHistory( @@ -213,12 +228,14 @@ public class CompanyDocumentDao { } public CompanyDocumentResponseBean getCompanyDocument(UserEntity user ,Long companyDocumentId) { + log.info("Fetching company document with ID '{}' for user '{}'", companyDocumentId, user.getId()); CompanyDocumentEntity companyDocumentEntity = validateCompanyDocument(companyDocumentId); validator.validateUserWithCompany(request,companyDocumentEntity.getCompanyId()); return convertToCompanyDocumentResponseBean(companyDocumentEntity); } public void deleteCompanyFile(Long companyDocumentId){ + log.info("Deleting file for company document ID '{}'", companyDocumentId); CompanyDocumentEntity companyDocumentEntity = validateCompanyDocument(companyDocumentId); deleteCompanyFileFromS3(companyDocumentEntity); } @@ -259,6 +276,7 @@ public class CompanyDocumentDao { } public DocumentResponseBean createDuplicateCompanyDocument(HttpServletRequest request , Long userId ,Long companyDocumentId , Long applicationId , DocumentTypeEnum documentTypeEnum){ + log.info("Creating duplicate of company document ID '{}' for application ID '{}'", companyDocumentId, applicationId); ApplicationEntity applicationEntity = applicationService.validateApplication(applicationId); CompanyDocumentEntity companyDocumentEntity = validateCompanyDocument(companyDocumentId); validator.validateUserWithCompany(request,companyDocumentEntity.getCompanyId()); @@ -272,7 +290,7 @@ public class CompanyDocumentDao { try { response = amazonS3ServiceImpl.copyFile(companyDocumentEntity.getName(), companyDocumentPath, documentPath); } catch (Exception e) { - log.error("Error occurred while uploading file from Amazon S3: {}", e); + log.error("Error occurred while uploading file from Amazon S3: {} for application ID '{}' and company Document ID '{}' ", e,applicationId,companyDocumentId); throw new CustomValidationException(Status.VALIDATION_ERROR, Translator.toLocale(GepafinConstant.UPLOAD_ERROR_S3)); } @@ -296,12 +314,14 @@ public class CompanyDocumentDao { } public List getAllCompanyDocument(UserEntity user , Long companyId, CompanyDocumentTypeEnum typeEnum){ + log.info("Fetching all company documents for Company ID '{}', User ID '{}', Type '{}'", companyId, user.getId(), typeEnum); validator.validateUserWithCompany(request, companyId); companyService.validateCompany(companyId); Specification spec = filterCompanyDocuments(companyId, user.getId(), typeEnum); List companyDocumentEntities = companyDocumentRepository.findAll(spec); + log.info("Retrieved all documents for Company ID '{}'", companyId); return companyDocumentEntities.stream() .map(this::convertToCompanyDocumentResponseBean) .collect(Collectors.toList()); diff --git a/src/main/java/net/gepafin/tendermanagement/dao/DocumentDao.java b/src/main/java/net/gepafin/tendermanagement/dao/DocumentDao.java index 81400797..3b5b41f6 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/DocumentDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/DocumentDao.java @@ -82,10 +82,11 @@ public class DocumentDao { // private String s3Folder; public List uploadFiles(Long userId,List files, Long sourceId, DocumentSourceTypeEnum sourceType, DocumentTypeEnum fileType) { - + log.info("Uploading files userId={}, sourceType={}, fileType={}", userId,sourceType,fileType); List documentEntities = new ArrayList<>(); Long source = resolveSourceId(sourceId, sourceType); for (MultipartFile file : files) { + log.info("Uploading file '{}'", file.getOriginalFilename()); UploadFileOnAmazonS3Response uploadFileOnAmazonS3Response = uploadFileOnAmazonS3(file, sourceType, sourceId); if (uploadFileOnAmazonS3Response != null) { DocumentEntity documentEntity = new DocumentEntity(); @@ -148,6 +149,7 @@ public class DocumentDao { private UploadFileOnAmazonS3Response uploadFileOnAmazonS3(MultipartFile file, DocumentSourceTypeEnum type, Long sourceId) { + log.info("Starting S3 upload: fileName={}, documentType={}, sourceId={}", file.getOriginalFilename(), type, sourceId); Long applicationId = 0L; Long amendmentId = 0L; Long evaluationId = 0L; @@ -155,26 +157,32 @@ public class DocumentDao { if (type == DocumentSourceTypeEnum.APPLICATION) { applicationId = sourceId; callId = applicationRepository.findCallIdById(applicationId); + log.info("Processing document of type APPLICATION .Resolved applicationId={}, callId={}", applicationId, callId); } else if (type == DocumentSourceTypeEnum.AMENDMENT) { amendmentId = sourceId; ApplicationEntity applicationEntity = applicationAmendmentRequestRepository.findApplicationByAmendmentId(amendmentId); applicationId = applicationEntity.getId(); callId = applicationEntity.getCall().getId(); + log.info("Processing document of type AMENDMENT .Resolved amendmentId={}, applicationId={}, callId={}", amendmentId, applicationId, callId); }else if (type == DocumentSourceTypeEnum.EVALUATION) { evaluationId = sourceId; ApplicationEntity applicationEntity = applicationEvaluationRepository.findApplicationByEvaluationId(evaluationId); applicationId = applicationEntity.getId(); callId = applicationEntity.getCall().getId(); + log.info("Processing document of type EVALUATION .Resolved evaluationId={}, applicationId={}, callId={}", evaluationId, applicationId, callId); } try { String s3Path = generateS3Path(type, callId, applicationId, amendmentId); log.info("Generated S3 path {}", s3Path); return amazonS3Service.uploadFileOnAmazonS3(s3Path, file); } catch (Exception e) { + log.error("Error uploading file to S3: fileName={}, documentType={}, sourceId={}, error={}", + file.getOriginalFilename(), type, sourceId, e.getMessage(), e); throw new CustomValidationException(Status.VALIDATION_ERROR, Translator.toLocale(GepafinConstant.UPLOAD_ERROR_S3)); } } + public String generateS3Path(DocumentSourceTypeEnum typeOfDocument, Long callId, Long applicationId, Long amendmentId) { try { return s3ConfigBean.generateDocumentPath(typeOfDocument, callId, applicationId, amendmentId); @@ -202,6 +210,7 @@ public class DocumentDao { DocumentEntity documentEntity = documentRepository.findById(documentId).orElseThrow(() -> new ResourceNotFoundException(Status.NOT_FOUND, Translator.toLocale(GepafinConstant.DOCUMENT_NOT_FOUND))); if(Boolean.TRUE.equals(documentEntity.getIsDeleted())){ + log.info("Document with id={} is already marked as deleted. Skipping deletion.", documentId); return; } Long callId = null; @@ -211,24 +220,28 @@ public class DocumentDao { if (DocumentSourceTypeEnum.CALL.getValue().equalsIgnoreCase(documentEntity.getSource())) { callId = documentEntity.getSourceId(); + log.info("Processing document of type CALL. Resolved callId={}", callId); } else if (DocumentSourceTypeEnum.APPLICATION.getValue().equalsIgnoreCase(documentEntity.getSource())) { applicationId = documentEntity.getSourceId(); ApplicationEntity applicationEntity = applicationService.validateApplication(applicationId); callId = applicationEntity.getCall().getId(); + log.info("Processing document of type APPLICATION. Resolved applicationId={}, callId={}", applicationId, callId); } else if(DocumentSourceTypeEnum.AMENDMENT.getValue().equalsIgnoreCase(documentEntity.getSource())){ amendmentId = documentEntity.getSourceId(); ApplicationEntity applicationEntity = applicationAmendmentRequestRepository.findApplicationByAmendmentId(amendmentId); applicationId = applicationEntity.getId(); callId = applicationEntity.getCall().getId(); + log.info("Processing document of type AMENDMENT. Resolved amendmentId={}, applicationId={}, callId={}", amendmentId, applicationId, callId); } else if(DocumentSourceTypeEnum.EVALUATION.getValue().equalsIgnoreCase(documentEntity.getSource())){ evaluationId = documentEntity.getSourceId(); ApplicationEntity applicationEntity = applicationEvaluationRepository.findApplicationByEvaluationId(evaluationId); applicationId = applicationEntity.getId(); callId = applicationEntity.getCall().getId(); + log.info("Processing document of type EVALUATION. Resolved evaluationId={}, applicationId={}, callId={}", evaluationId, applicationId, callId); } deleteFileFromS3(documentEntity, callId, applicationId,amendmentId); - + log.info("Successfully deleted file from S3 for documentId={}", documentId); } public DocumentEntity validateDocument(Long id) { @@ -238,6 +251,9 @@ public class DocumentDao { public DocumentResponseBean updateDocument(Long documentId, MultipartFile file, DocumentTypeEnum documentTypeEnum) { + log.info("Starting document update: documentId={} , newDocumentType={}", + documentId , documentTypeEnum); + DocumentEntity documentEntity = validateDocument(documentId); //cloned entity for old data DocumentEntity oldDocumentData = Utils.getClonedEntityForData(documentEntity); @@ -245,12 +261,15 @@ public class DocumentDao { String type = documentEntity.getSource(); UploadFileOnAmazonS3Response uploadFileOnAmazonS3Response = updateFileOnAmazonS3(file, DocumentSourceTypeEnum.valueOf(type), documentEntity.getSourceId()); if (uploadFileOnAmazonS3Response != null) { + log.info("Successfully uploaded new file to S3: fileName={}", + uploadFileOnAmazonS3Response.getFileName()); documentEntity.setFileName(uploadFileOnAmazonS3Response.getFileName()); documentEntity.setFilePath(uploadFileOnAmazonS3Response.getFilePath()); documentEntity.setType(documentTypeEnum.getValue()); documentEntity.setSource(documentEntity.getSource()); documentEntity.setSourceId(documentEntity.getSourceId()); documentRepository.save(documentEntity); + log.info("Document updated in database for documentId={}", documentId); /** This code is responsible for adding a version history log for the "updating doc or image" operation. **/ loggingUtil.addVersionHistory( @@ -260,7 +279,6 @@ public class DocumentDao { } private UploadFileOnAmazonS3Response updateFileOnAmazonS3(MultipartFile file, DocumentSourceTypeEnum type, Long id) { - try { Long callId=null; Long applicationId=null; @@ -269,27 +287,32 @@ public class DocumentDao { if (type.equals(DocumentSourceTypeEnum.APPLICATION)) { callId = applicationRepository.findCallIdById(id); applicationId = id; + log.info("Processing document of type APPLICATION . Resolved applicationId={}, callId={}", applicationId, callId); } else if(type.equals(DocumentSourceTypeEnum.AMENDMENT)){ amendmentId = id; ApplicationEntity applicationEntity = applicationAmendmentRequestRepository.findApplicationByAmendmentId(amendmentId); applicationId = applicationEntity.getId(); callId = applicationEntity.getCall().getId(); + log.info("Processing document of type AMENDMENT . Resolved amendmentId={}, applicationId={}, callId={}", amendmentId, applicationId, callId); }else if(type.equals(DocumentSourceTypeEnum.EVALUATION)){ evaluationId = id; ApplicationEntity applicationEntity = applicationEvaluationRepository.findApplicationByEvaluationId(evaluationId); applicationId = applicationEntity.getId(); callId = applicationEntity.getCall().getId(); + log.info("Processing document of type EVALUATION . Resolved evaluationId={}, applicationId={}, callId={}", evaluationId, applicationId, callId); } else { callId = id; applicationId = 0L; + log.info("Processing document of type CALL . Resolved callId={}", callId); } String s3Path = generateS3Path(type, callId, applicationId,amendmentId); log.info("Generated S3 path {}", s3Path); return amazonS3Service.uploadFileOnAmazonS3(s3Path, file); } catch (Exception e) { + log.error("Error during file update to S3: documentType={}, sourceId={}, error={}",type, id, e.getMessage(), e); throw new CustomValidationException(Status.VALIDATION_ERROR, Translator.toLocale(GepafinConstant.UPLOAD_ERROR_S3)); } } @@ -304,6 +327,7 @@ public class DocumentDao { DocumentEntity oldDocumentEntity = Utils.getClonedEntityForData(documentEntity); String oldS3Path = documentEntity.getFilePath(); String newS3Path = s3ConfigBean.generateDocumentPathForOther(DocOtherSourceTypeEnum.valueOf("DELETED_" + documentEntity.getSource().toUpperCase()), callId, applicationId,amendmentId); + log.info("Moving file to deleted path: oldS3Path={}, newS3Path={}", oldS3Path, newS3Path); UploadFileOnAmazonS3Response response = amazonS3Service.moveFile(documentEntity.getFileName(), oldS3Path, newS3Path); documentEntity.setFileName(response.getFileName()); documentEntity.setFilePath(response.getFilePath()); diff --git a/src/main/java/net/gepafin/tendermanagement/dao/PdfDao.java b/src/main/java/net/gepafin/tendermanagement/dao/PdfDao.java index 46cae083..9eda7968 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/PdfDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/PdfDao.java @@ -62,6 +62,7 @@ public class PdfDao { public byte[] generatePdf(HttpServletRequest request,Long applicationId) { try { + log.info("Start generating PDF for applicationId: {}", applicationId); UserEntity userEntity = validator.validateUser(request); ApplicationEntity applicationEntity = applicationDao.validateApplication(applicationId); validator.validateUserWithCompany(request, applicationEntity.getCompanyId()); @@ -125,6 +126,7 @@ public class PdfDao { return pdfBytes; } catch (Exception e) { + log.error("Error generating PDF for applicationId: {}", applicationId, e); e.printStackTrace(); } return null; From 5df897680a96cbc14c7c851759246b6b2aa60c8e Mon Sep 17 00:00:00 2001 From: rajesh Date: Tue, 3 Jun 2025 13:04:32 +0530 Subject: [PATCH 05/35] Added #noauth in doc url of sviluppUmbria protocol --- .../gepafin/tendermanagement/constants/GepafinConstant.java | 6 +++--- .../java/net/gepafin/tendermanagement/dao/ProtocolDao.java | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java b/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java index 1fe74e81..57139e52 100644 --- a/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java +++ b/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java @@ -568,9 +568,9 @@ public class GepafinConstant { public static final String PROTOCOL_MITTENTE="mittente"; public static final String PROTOCOL_DESTINATARI="destinatari"; public static final String PROTOCOL_EXTERNAL_YEAR="ANNO_PG"; - public static final String PROTOCOL_EXTERNAL_NUMBER=""; - public static final String PROTOCOL_EXTERNAL_DATE=""; - + public static final String PROTOCOL_EXTERNAL_NUMBER="NUM_PG"; + public static final String PROTOCOL_EXTERNAL_DATE="DATA_PG_DT"; + public static final String PROTOCOL_DOC_SUFFIX="#noauth"; } diff --git a/src/main/java/net/gepafin/tendermanagement/dao/ProtocolDao.java b/src/main/java/net/gepafin/tendermanagement/dao/ProtocolDao.java index a9b23cd6..c90ddf30 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/ProtocolDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/ProtocolDao.java @@ -176,7 +176,7 @@ public class ProtocolDao { String callName = application.getCall().getName()+" "+ application.getCall().getId(); String callId = GepafinConstant.PROTOCOL_CALL_NAME+ String.valueOf(application.getCall().getId()); String pecEmail=company.getPec(); - String docUrl=applicationSignedDocumentEntity.getFilePath(); + String docUrl=applicationSignedDocumentEntity.getFilePath()+GepafinConstant.PROTOCOL_DOC_SUFFIX; Map requestBody = new HashMap<>(); From ba261783b5d223446cf0318d9be5dd4993ef1f4c Mon Sep 17 00:00:00 2001 From: rajesh Date: Tue, 3 Jun 2025 16:57:48 +0530 Subject: [PATCH 06/35] Done ticket GEPAFINBE-87 --- .../tendermanagement/dao/ApplicationDao.java | 5 + .../ApplicationAmendmentRequestEntity.java | 6 +- .../entities/ApplicationEntity.java | 5 +- .../entities/ApplicationEvaluationEntity.java | 5 +- .../ApplicationEvaluationFormEntity.java | 5 +- .../ApplicationEvaluationFormFieldEntity.java | 5 +- .../entities/ApplicationFormEntity.java | 3 +- .../entities/ApplicationFormFieldEntity.java | 2 + .../ApplicationSignedDocumentEntity.java | 2 + .../entities/AssignedApplicationsEntity.java | 7 +- .../tendermanagement/entities/BaseEntity.java | 9 +- .../entities/BeneficiaryEntity.java | 2 + .../BeneficiaryPreferredCallEntity.java | 5 +- .../tendermanagement/entities/CallEntity.java | 2 + .../CallTargetAudienceChecklistEntity.java | 6 +- .../entities/CommunicationEntity.java | 5 +- .../entities/CompanyDocumentEntity.java | 5 +- .../entities/CompanyEntity.java | 2 + .../entities/CriteriaFormFieldEntity.java | 5 +- .../entities/DocumentCategoryEntity.java | 5 +- .../entities/DocumentEntity.java | 5 +- .../entities/EmailLogEntity.java | 5 +- .../entities/EvaluationCriteriaEntity.java | 6 +- .../entities/EvaluationFormEntity.java | 5 +- .../entities/ExpirationConfigEntity.java | 5 +- .../tendermanagement/entities/FaqEntity.java | 8 +- .../entities/FlowDataEntity.java | 3 + .../entities/FlowEdgesEntity.java | 2 + .../tendermanagement/entities/FormEntity.java | 2 + .../entities/FormFieldEntity.java | 3 + .../entities/FormTemplateEntity.java | 2 + .../entities/GlobalConfigEntity.java | 3 - .../tendermanagement/entities/HubEntity.java | 3 +- .../entities/HubUserEntity.java | 2 + .../entities/LoginAttemptEntity.java | 2 + .../entities/LookUpDataEntity.java | 2 + .../entities/NotificationEntity.java | 5 +- .../entities/NotificationTypeEntity.java | 5 +- .../entities/ProtocolEntity.java | 2 + .../entities/RegionEntity.java | 2 + .../entities/RoleActionContextEntity.java | 5 +- .../tendermanagement/entities/RoleEntity.java | 2 + .../entities/S3ConfigEntity.java | 3 + .../entities/SamlResponseEntity.java | 3 +- .../entities/SystemEmailTemplatesEntity.java | 5 +- .../entities/UserActionEntity.java | 5 +- .../entities/UserCompanyDelegationEntity.java | 2 + .../tendermanagement/entities/UserEntity.java | 2 + .../entities/UserWithCompanyEntity.java | 7 +- .../entities/VersionHistoryEntity.java | 5 +- .../db/changelog/db.changelog-1.0.0.xml | 135 ++++++++++++++++++ 51 files changed, 249 insertions(+), 88 deletions(-) diff --git a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java index a98b3ff9..66b3e818 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java @@ -11,6 +11,11 @@ import net.gepafin.tendermanagement.entities.*; import net.gepafin.tendermanagement.entities.SystemEmailTemplatesEntity.SystemEmailTemplatesEntityTypeEnum; import net.gepafin.tendermanagement.enums.*; import net.gepafin.tendermanagement.model.request.*; +import net.gepafin.tendermanagement.model.request.ApplicationFormFieldRequestBean; +import net.gepafin.tendermanagement.model.request.ApplicationRequest; +import net.gepafin.tendermanagement.model.request.ApplicationRequestBean; +import net.gepafin.tendermanagement.model.request.EmailLogRequest; +import net.gepafin.tendermanagement.model.request.VersionHistoryRequest; import net.gepafin.tendermanagement.model.response.*; import net.gepafin.tendermanagement.model.util.SortBy; import net.gepafin.tendermanagement.repositories.*; diff --git a/src/main/java/net/gepafin/tendermanagement/entities/ApplicationAmendmentRequestEntity.java b/src/main/java/net/gepafin/tendermanagement/entities/ApplicationAmendmentRequestEntity.java index 840ad050..d1e98d54 100644 --- a/src/main/java/net/gepafin/tendermanagement/entities/ApplicationAmendmentRequestEntity.java +++ b/src/main/java/net/gepafin/tendermanagement/entities/ApplicationAmendmentRequestEntity.java @@ -2,11 +2,14 @@ package net.gepafin.tendermanagement.entities; import jakarta.persistence.*; import lombok.Data; +import org.hibernate.annotations.Where; + import java.time.LocalDateTime; @Entity @Table(name="application_amendment_request") @Data +@Where(clause = "is_deleted = false") public class ApplicationAmendmentRequestEntity extends BaseEntity { @Column(name = "NOTE") @@ -30,9 +33,6 @@ public class ApplicationAmendmentRequestEntity extends BaseEntity { @Column(name = "FORM_FIELDS") private String formFields; - @Column(name="IS_DELETED") - private Boolean isDeleted=false; - @Column(name = "STATUS") private String status; diff --git a/src/main/java/net/gepafin/tendermanagement/entities/ApplicationEntity.java b/src/main/java/net/gepafin/tendermanagement/entities/ApplicationEntity.java index 50d16557..e2208c77 100644 --- a/src/main/java/net/gepafin/tendermanagement/entities/ApplicationEntity.java +++ b/src/main/java/net/gepafin/tendermanagement/entities/ApplicationEntity.java @@ -2,6 +2,7 @@ package net.gepafin.tendermanagement.entities; import jakarta.persistence.*; import lombok.*; +import org.hibernate.annotations.Where; import java.math.BigDecimal; import java.time.LocalDateTime; @@ -9,6 +10,7 @@ import java.time.LocalDateTime; @Entity @Table(name = "APPLICATION") @Data +@Where(clause = "is_deleted = false") public class ApplicationEntity extends BaseEntity { @Column(name = "USER_ID") @@ -29,9 +31,6 @@ public class ApplicationEntity extends BaseEntity { @ManyToOne @JoinColumn(name = "CALL_ID", nullable = false) private CallEntity call; - - @Column(name="IS_DELETED") - private Boolean isDeleted; @OneToOne @JoinColumn(name = "PROTOCOL_NUMBER") diff --git a/src/main/java/net/gepafin/tendermanagement/entities/ApplicationEvaluationEntity.java b/src/main/java/net/gepafin/tendermanagement/entities/ApplicationEvaluationEntity.java index 045a1bdb..25d1cac8 100644 --- a/src/main/java/net/gepafin/tendermanagement/entities/ApplicationEvaluationEntity.java +++ b/src/main/java/net/gepafin/tendermanagement/entities/ApplicationEvaluationEntity.java @@ -2,12 +2,14 @@ package net.gepafin.tendermanagement.entities; import jakarta.persistence.*; import lombok.Data; +import org.hibernate.annotations.Where; import java.time.LocalDateTime; @Data @Entity @Table(name = "application_evaluation") +@Where(clause = "is_deleted = false") public class ApplicationEvaluationEntity extends BaseEntity{ @Column(name = "application_Id") @@ -37,9 +39,6 @@ public class ApplicationEvaluationEntity extends BaseEntity{ @Column(name = "MOTIVATION") private String motivation; - @Column(name="IS_DELETED") - private Boolean isDeleted; - @ManyToOne @JoinColumn(name = "assigned_applications_id", nullable = true) private AssignedApplicationsEntity assignedApplicationsEntity; diff --git a/src/main/java/net/gepafin/tendermanagement/entities/ApplicationEvaluationFormEntity.java b/src/main/java/net/gepafin/tendermanagement/entities/ApplicationEvaluationFormEntity.java index 251fb262..78a881a0 100644 --- a/src/main/java/net/gepafin/tendermanagement/entities/ApplicationEvaluationFormEntity.java +++ b/src/main/java/net/gepafin/tendermanagement/entities/ApplicationEvaluationFormEntity.java @@ -2,16 +2,15 @@ package net.gepafin.tendermanagement.entities; import jakarta.persistence.*; import lombok.Data; +import org.hibernate.annotations.Where; @Entity @Data @Table(name = "APPLICATION_EVALUATION_FORM") +@Where(clause = "is_deleted = false") public class ApplicationEvaluationFormEntity extends BaseEntity{ private Long applicationId; - @Column(name="IS_DELETED") - private Boolean isDeleted; - @ManyToOne @JoinColumn(name = "EVALUATION_ID") private ApplicationEvaluationEntity applicationEvaluation; diff --git a/src/main/java/net/gepafin/tendermanagement/entities/ApplicationEvaluationFormFieldEntity.java b/src/main/java/net/gepafin/tendermanagement/entities/ApplicationEvaluationFormFieldEntity.java index 913d8831..f4324a2a 100644 --- a/src/main/java/net/gepafin/tendermanagement/entities/ApplicationEvaluationFormFieldEntity.java +++ b/src/main/java/net/gepafin/tendermanagement/entities/ApplicationEvaluationFormFieldEntity.java @@ -2,10 +2,12 @@ package net.gepafin.tendermanagement.entities; import jakarta.persistence.*; import lombok.Data; +import org.hibernate.annotations.Where; @Entity @Data @Table(name = "APPLICATION_EVALUATION_FORM_FIELD") +@Where(clause = "is_deleted = false") public class ApplicationEvaluationFormFieldEntity extends BaseEntity { @ManyToOne @@ -18,7 +20,4 @@ public class ApplicationEvaluationFormFieldEntity extends BaseEntity { @Column(name = "FIELD_VALUE") private String fieldValue; - @Column(name="IS_DELETED") - private Boolean isDeleted; - } diff --git a/src/main/java/net/gepafin/tendermanagement/entities/ApplicationFormEntity.java b/src/main/java/net/gepafin/tendermanagement/entities/ApplicationFormEntity.java index a6cc56aa..cf407cda 100644 --- a/src/main/java/net/gepafin/tendermanagement/entities/ApplicationFormEntity.java +++ b/src/main/java/net/gepafin/tendermanagement/entities/ApplicationFormEntity.java @@ -2,11 +2,12 @@ package net.gepafin.tendermanagement.entities; import jakarta.persistence.*; import lombok.*; - +import org.hibernate.annotations.Where; @Entity @Table(name = "APPLICATION_FORM") @Data +@Where(clause = "is_deleted = false") public class ApplicationFormEntity extends BaseEntity { @ManyToOne diff --git a/src/main/java/net/gepafin/tendermanagement/entities/ApplicationFormFieldEntity.java b/src/main/java/net/gepafin/tendermanagement/entities/ApplicationFormFieldEntity.java index fbab986d..206be527 100644 --- a/src/main/java/net/gepafin/tendermanagement/entities/ApplicationFormFieldEntity.java +++ b/src/main/java/net/gepafin/tendermanagement/entities/ApplicationFormFieldEntity.java @@ -2,6 +2,7 @@ package net.gepafin.tendermanagement.entities; import jakarta.persistence.*; import lombok.*; +import org.hibernate.annotations.Where; import java.time.LocalDateTime; @@ -11,6 +12,7 @@ import java.time.LocalDateTime; @NoArgsConstructor @AllArgsConstructor @Builder +@Where(clause = "is_deleted = false") public class ApplicationFormFieldEntity extends BaseEntity { @ManyToOne diff --git a/src/main/java/net/gepafin/tendermanagement/entities/ApplicationSignedDocumentEntity.java b/src/main/java/net/gepafin/tendermanagement/entities/ApplicationSignedDocumentEntity.java index 58975cca..94d4ef6d 100644 --- a/src/main/java/net/gepafin/tendermanagement/entities/ApplicationSignedDocumentEntity.java +++ b/src/main/java/net/gepafin/tendermanagement/entities/ApplicationSignedDocumentEntity.java @@ -6,10 +6,12 @@ import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; import lombok.Data; +import org.hibernate.annotations.Where; @Data @Entity @Table(name = "application_signed_document") +@Where(clause = "is_deleted = false") public class ApplicationSignedDocumentEntity extends BaseEntity { @ManyToOne diff --git a/src/main/java/net/gepafin/tendermanagement/entities/AssignedApplicationsEntity.java b/src/main/java/net/gepafin/tendermanagement/entities/AssignedApplicationsEntity.java index bd26f527..0c80c7af 100644 --- a/src/main/java/net/gepafin/tendermanagement/entities/AssignedApplicationsEntity.java +++ b/src/main/java/net/gepafin/tendermanagement/entities/AssignedApplicationsEntity.java @@ -2,13 +2,14 @@ package net.gepafin.tendermanagement.entities; import jakarta.persistence.*; import lombok.Data; +import org.hibernate.annotations.Where; import java.time.LocalDateTime; @Entity @Data @Table(name = "assigned_applications") - +@Where(clause = "is_deleted = false") public class AssignedApplicationsEntity extends BaseEntity{ @ManyToOne @@ -27,9 +28,7 @@ public class AssignedApplicationsEntity extends BaseEntity{ @Column(name = "NOTE") private String note; - @Column(name="IS_DELETED") - private Boolean isDeleted=false; - @Column(nullable = false) private LocalDateTime assignedAt; + } diff --git a/src/main/java/net/gepafin/tendermanagement/entities/BaseEntity.java b/src/main/java/net/gepafin/tendermanagement/entities/BaseEntity.java index f8427719..16c7e94f 100644 --- a/src/main/java/net/gepafin/tendermanagement/entities/BaseEntity.java +++ b/src/main/java/net/gepafin/tendermanagement/entities/BaseEntity.java @@ -21,8 +21,13 @@ public class BaseEntity { @Column(name = "UPDATED_DATE") LocalDateTime updatedDate; - - @PrePersist + + + @Column(name="IS_DELETED") + private Boolean isDeleted = false; + + + @PrePersist public void setCreatedDate() { this.createdDate = DateTimeUtil.DateServerToUTC(LocalDateTime.now()); this.updatedDate = DateTimeUtil.DateServerToUTC(LocalDateTime.now()); diff --git a/src/main/java/net/gepafin/tendermanagement/entities/BeneficiaryEntity.java b/src/main/java/net/gepafin/tendermanagement/entities/BeneficiaryEntity.java index 2e84dd51..0985c37f 100644 --- a/src/main/java/net/gepafin/tendermanagement/entities/BeneficiaryEntity.java +++ b/src/main/java/net/gepafin/tendermanagement/entities/BeneficiaryEntity.java @@ -7,10 +7,12 @@ import jakarta.persistence.Entity; import jakarta.persistence.Table; import jakarta.validation.constraints.Email; import lombok.Data; +import org.hibernate.annotations.Where; @Entity @Table(name = "BENEFICIARY") @Data +@Where(clause = "is_deleted = false") public class BeneficiaryEntity extends BaseEntity { @Email diff --git a/src/main/java/net/gepafin/tendermanagement/entities/BeneficiaryPreferredCallEntity.java b/src/main/java/net/gepafin/tendermanagement/entities/BeneficiaryPreferredCallEntity.java index 5bbc316d..22333d18 100644 --- a/src/main/java/net/gepafin/tendermanagement/entities/BeneficiaryPreferredCallEntity.java +++ b/src/main/java/net/gepafin/tendermanagement/entities/BeneficiaryPreferredCallEntity.java @@ -5,6 +5,7 @@ import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; +import org.hibernate.annotations.Where; import java.time.LocalDateTime; @@ -14,6 +15,7 @@ import java.time.LocalDateTime; @NoArgsConstructor @AllArgsConstructor @Builder +@Where(clause = "is_deleted = false") public class BeneficiaryPreferredCallEntity extends BaseEntity{ @Id @@ -35,9 +37,6 @@ public class BeneficiaryPreferredCallEntity extends BaseEntity{ @Column(name = "STATUS", length = 255) private String status; - @Column(name="IS_DELETED") - private Boolean isDeleted; - @ManyToOne @JoinColumn(name = "USER_WITH_COMPANY_ID") private UserWithCompanyEntity userWithCompany; diff --git a/src/main/java/net/gepafin/tendermanagement/entities/CallEntity.java b/src/main/java/net/gepafin/tendermanagement/entities/CallEntity.java index 25388138..dc8c05d8 100644 --- a/src/main/java/net/gepafin/tendermanagement/entities/CallEntity.java +++ b/src/main/java/net/gepafin/tendermanagement/entities/CallEntity.java @@ -5,6 +5,7 @@ import lombok.Data; import lombok.NoArgsConstructor; import lombok.AllArgsConstructor; import lombok.Builder; +import org.hibernate.annotations.Where; import java.math.BigDecimal; import java.time.LocalDateTime; @@ -16,6 +17,7 @@ import java.time.LocalTime; @NoArgsConstructor @AllArgsConstructor @Builder +@Where(clause = "is_deleted = false") public class CallEntity extends BaseEntity { @Column(name = "NAME", length = 255) diff --git a/src/main/java/net/gepafin/tendermanagement/entities/CallTargetAudienceChecklistEntity.java b/src/main/java/net/gepafin/tendermanagement/entities/CallTargetAudienceChecklistEntity.java index 6b14df58..465baf67 100644 --- a/src/main/java/net/gepafin/tendermanagement/entities/CallTargetAudienceChecklistEntity.java +++ b/src/main/java/net/gepafin/tendermanagement/entities/CallTargetAudienceChecklistEntity.java @@ -2,10 +2,12 @@ package net.gepafin.tendermanagement.entities; import jakarta.persistence.*; import lombok.Data; +import org.hibernate.annotations.Where; @Entity @Table(name = "CALL_TARGET_AUDIENCE_CHECKLIST") @Data +@Where(clause = "is_deleted = false") public class CallTargetAudienceChecklistEntity extends BaseEntity{ @ManyToOne @@ -18,9 +20,5 @@ public class CallTargetAudienceChecklistEntity extends BaseEntity{ @Column(name = "IS_VALIDATED") private Boolean isValidated; - - @Column(name ="IS_DELETED", nullable = false) - private Boolean isDeleted = false; - } diff --git a/src/main/java/net/gepafin/tendermanagement/entities/CommunicationEntity.java b/src/main/java/net/gepafin/tendermanagement/entities/CommunicationEntity.java index 9029649e..a1c3ff45 100644 --- a/src/main/java/net/gepafin/tendermanagement/entities/CommunicationEntity.java +++ b/src/main/java/net/gepafin/tendermanagement/entities/CommunicationEntity.java @@ -6,12 +6,14 @@ import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; import lombok.Data; +import org.hibernate.annotations.Where; import java.time.LocalDateTime; @Entity @Table(name = "communication") @Data +@Where(clause = "is_deleted = false") public class CommunicationEntity extends BaseEntity { @Column(name = "COMMUNICATION_TITLE") @@ -20,9 +22,6 @@ public class CommunicationEntity extends BaseEntity { @Column(name = "COMMUNICATION_COMMENT") private String communicationComment; - @Column(name = "IS_DELETED") - private Boolean isDeleted = false; - @Column(name = "COMMENTED_DATE") private LocalDateTime commentedDate; diff --git a/src/main/java/net/gepafin/tendermanagement/entities/CompanyDocumentEntity.java b/src/main/java/net/gepafin/tendermanagement/entities/CompanyDocumentEntity.java index 15c3457c..1a08fffc 100644 --- a/src/main/java/net/gepafin/tendermanagement/entities/CompanyDocumentEntity.java +++ b/src/main/java/net/gepafin/tendermanagement/entities/CompanyDocumentEntity.java @@ -2,12 +2,14 @@ package net.gepafin.tendermanagement.entities; import jakarta.persistence.*; import lombok.Data; +import org.hibernate.annotations.Where; import java.time.LocalDateTime; @Entity @Table(name = "COMPANY_DOCUMENT") @Data +@Where(clause = "is_deleted = false") public class CompanyDocumentEntity extends BaseEntity { @Column(name = "FILE_NAME") @@ -25,9 +27,6 @@ public class CompanyDocumentEntity extends BaseEntity { @Column(name="COMPANY_ID") private Long companyId; - @Column(name ="IS_DELETED") - private Boolean isDeleted = false; - @Column(name="UPLOADED_BY") private Long uploadedBy; diff --git a/src/main/java/net/gepafin/tendermanagement/entities/CompanyEntity.java b/src/main/java/net/gepafin/tendermanagement/entities/CompanyEntity.java index 12dc2a15..aa334f11 100644 --- a/src/main/java/net/gepafin/tendermanagement/entities/CompanyEntity.java +++ b/src/main/java/net/gepafin/tendermanagement/entities/CompanyEntity.java @@ -8,10 +8,12 @@ import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; import lombok.Data; +import org.hibernate.annotations.Where; @Entity @Table(name = "COMPANY") @Data +@Where(clause = "is_deleted = false") public class CompanyEntity extends BaseEntity{ @Column(name = "COMPANY_NAME") diff --git a/src/main/java/net/gepafin/tendermanagement/entities/CriteriaFormFieldEntity.java b/src/main/java/net/gepafin/tendermanagement/entities/CriteriaFormFieldEntity.java index 1177bbd2..8a65ee5e 100644 --- a/src/main/java/net/gepafin/tendermanagement/entities/CriteriaFormFieldEntity.java +++ b/src/main/java/net/gepafin/tendermanagement/entities/CriteriaFormFieldEntity.java @@ -4,10 +4,12 @@ import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.Table; import lombok.Data; +import org.hibernate.annotations.Where; @Entity @Table(name = "criteria_form_field") @Data +@Where(clause = "is_deleted = false") public class CriteriaFormFieldEntity extends BaseEntity { private Long callId; @@ -17,8 +19,5 @@ public class CriteriaFormFieldEntity extends BaseEntity { private String formFieldId; private Long evaluationCriteriaId; - - @Column(name ="IS_DELETED", nullable = false) - private Boolean isDeleted = false; } diff --git a/src/main/java/net/gepafin/tendermanagement/entities/DocumentCategoryEntity.java b/src/main/java/net/gepafin/tendermanagement/entities/DocumentCategoryEntity.java index 25a25d6d..3307e2d0 100644 --- a/src/main/java/net/gepafin/tendermanagement/entities/DocumentCategoryEntity.java +++ b/src/main/java/net/gepafin/tendermanagement/entities/DocumentCategoryEntity.java @@ -4,10 +4,12 @@ import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.Table; import lombok.Data; +import org.hibernate.annotations.Where; @Entity @Table(name = "document_category") @Data +@Where(clause = "is_deleted = false") public class DocumentCategoryEntity extends BaseEntity { @Column(name = "CATEGORY_NAME") @@ -16,7 +18,4 @@ public class DocumentCategoryEntity extends BaseEntity { @Column(name = "DESCRIPTION") private String description; - @Column(name ="IS_DELETED") - private Boolean isDeleted = false; - } diff --git a/src/main/java/net/gepafin/tendermanagement/entities/DocumentEntity.java b/src/main/java/net/gepafin/tendermanagement/entities/DocumentEntity.java index 08bfd623..8c658411 100644 --- a/src/main/java/net/gepafin/tendermanagement/entities/DocumentEntity.java +++ b/src/main/java/net/gepafin/tendermanagement/entities/DocumentEntity.java @@ -6,10 +6,12 @@ import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; import lombok.Data; +import org.hibernate.annotations.Where; @Entity @Table(name = "DOCUMENT") @Data +@Where(clause = "is_deleted = false") public class DocumentEntity extends BaseEntity{ @Column(name = "FILE_NAME", length = 255) private String fileName; @@ -26,9 +28,6 @@ public class DocumentEntity extends BaseEntity{ @Column(name="SOURCE_ID") private Long sourceId; - @Column(name ="IS_DELETED", nullable = false) - private Boolean isDeleted = false; - @Column(name="DOCUMENT_ATTACHMENT_ID") private String documentAttachmentId; diff --git a/src/main/java/net/gepafin/tendermanagement/entities/EmailLogEntity.java b/src/main/java/net/gepafin/tendermanagement/entities/EmailLogEntity.java index 03ad4c2b..53c3f66c 100644 --- a/src/main/java/net/gepafin/tendermanagement/entities/EmailLogEntity.java +++ b/src/main/java/net/gepafin/tendermanagement/entities/EmailLogEntity.java @@ -1,12 +1,14 @@ package net.gepafin.tendermanagement.entities; import jakarta.persistence.*; import lombok.Data; +import org.hibernate.annotations.Where; import java.time.LocalDateTime; @Entity @Data @Table(name = "email_log") +@Where(clause = "is_deleted = false") public class EmailLogEntity extends BaseEntity{ @Column(name = "email_type", nullable = false, length = 255) @@ -53,8 +55,5 @@ public class EmailLogEntity extends BaseEntity{ @Column(name = "call_id") private Long callId; - - @Column(name = "is_deleted") - private Boolean isDeleted; } diff --git a/src/main/java/net/gepafin/tendermanagement/entities/EvaluationCriteriaEntity.java b/src/main/java/net/gepafin/tendermanagement/entities/EvaluationCriteriaEntity.java index 39b3eb05..6c035b38 100644 --- a/src/main/java/net/gepafin/tendermanagement/entities/EvaluationCriteriaEntity.java +++ b/src/main/java/net/gepafin/tendermanagement/entities/EvaluationCriteriaEntity.java @@ -5,11 +5,12 @@ import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; import lombok.Data; - +import org.hibernate.annotations.Where; @Entity @Table(name = "EVALUATION_CRITERIA") @Data +@Where(clause = "is_deleted = false") public class EvaluationCriteriaEntity extends BaseEntity { @ManyToOne @@ -22,8 +23,5 @@ public class EvaluationCriteriaEntity extends BaseEntity { @Column(name = "SCORE", nullable = false) private Long score; - - @Column(name ="IS_DELETED", nullable = false) - private Boolean isDeleted = false; } diff --git a/src/main/java/net/gepafin/tendermanagement/entities/EvaluationFormEntity.java b/src/main/java/net/gepafin/tendermanagement/entities/EvaluationFormEntity.java index 9d3bd3fa..e839814a 100644 --- a/src/main/java/net/gepafin/tendermanagement/entities/EvaluationFormEntity.java +++ b/src/main/java/net/gepafin/tendermanagement/entities/EvaluationFormEntity.java @@ -5,12 +5,15 @@ import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; +import org.hibernate.annotations.Where; + @Entity @Table(name = "EVALUATION_FORM") @Data @NoArgsConstructor @AllArgsConstructor @Builder +@Where(clause = "is_deleted = false") public class EvaluationFormEntity extends BaseEntity{ @@ -24,6 +27,4 @@ public class EvaluationFormEntity extends BaseEntity{ @Column(name = "CONTENT") private String content; - @Column(name="IS_DELETED") - private Boolean isDeleted; } diff --git a/src/main/java/net/gepafin/tendermanagement/entities/ExpirationConfigEntity.java b/src/main/java/net/gepafin/tendermanagement/entities/ExpirationConfigEntity.java index 83159e3a..0f8336db 100644 --- a/src/main/java/net/gepafin/tendermanagement/entities/ExpirationConfigEntity.java +++ b/src/main/java/net/gepafin/tendermanagement/entities/ExpirationConfigEntity.java @@ -4,10 +4,12 @@ import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.Table; import lombok.Data; +import org.hibernate.annotations.Where; @Entity @Table(name = "expiration_config") @Data +@Where(clause = "is_deleted = false") public class ExpirationConfigEntity extends BaseEntity { @Column(name="INTERVAL_DAYS") @@ -15,7 +17,4 @@ public class ExpirationConfigEntity extends BaseEntity { @Column(name="TYPE") private String type; - - @Column(name="IS_DELETED") - private Boolean isDeleted; } diff --git a/src/main/java/net/gepafin/tendermanagement/entities/FaqEntity.java b/src/main/java/net/gepafin/tendermanagement/entities/FaqEntity.java index 46ebff24..5df715c9 100644 --- a/src/main/java/net/gepafin/tendermanagement/entities/FaqEntity.java +++ b/src/main/java/net/gepafin/tendermanagement/entities/FaqEntity.java @@ -9,11 +9,12 @@ import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; import lombok.Data; - +import org.hibernate.annotations.Where; @Entity @Table(name = "FAQ") @Data +@Where(clause = "is_deleted = false") public class FaqEntity extends BaseEntity { @ManyToOne @@ -38,10 +39,7 @@ public class FaqEntity extends BaseEntity { @Column(name = "RESPONSE_DATE") private LocalDateTime responseDate; - - @Column(name ="IS_DELETED", nullable = false) - private Boolean isDeleted = false; - + @Column(name ="COMPANY_ID") private Long companyId; diff --git a/src/main/java/net/gepafin/tendermanagement/entities/FlowDataEntity.java b/src/main/java/net/gepafin/tendermanagement/entities/FlowDataEntity.java index cdffe410..a8c99131 100644 --- a/src/main/java/net/gepafin/tendermanagement/entities/FlowDataEntity.java +++ b/src/main/java/net/gepafin/tendermanagement/entities/FlowDataEntity.java @@ -1,6 +1,7 @@ package net.gepafin.tendermanagement.entities; import jakarta.persistence.*; import lombok.*; +import org.hibernate.annotations.Where; import java.time.LocalDateTime; @@ -10,6 +11,7 @@ import java.time.LocalDateTime; @NoArgsConstructor @AllArgsConstructor @Builder +@Where(clause = "is_deleted = false") public class FlowDataEntity extends BaseEntity{ @Column(name = "FORM_ID") @@ -23,4 +25,5 @@ public class FlowDataEntity extends BaseEntity{ @Column(name="CALL_ID") private Long callId; + } diff --git a/src/main/java/net/gepafin/tendermanagement/entities/FlowEdgesEntity.java b/src/main/java/net/gepafin/tendermanagement/entities/FlowEdgesEntity.java index 119af700..4bcab38d 100644 --- a/src/main/java/net/gepafin/tendermanagement/entities/FlowEdgesEntity.java +++ b/src/main/java/net/gepafin/tendermanagement/entities/FlowEdgesEntity.java @@ -2,6 +2,7 @@ package net.gepafin.tendermanagement.entities; import jakarta.persistence.*; import lombok.*; +import org.hibernate.annotations.Where; import java.time.LocalDateTime; @@ -11,6 +12,7 @@ import java.time.LocalDateTime; @NoArgsConstructor @AllArgsConstructor @Builder +@Where(clause = "is_deleted = false") public class FlowEdgesEntity extends BaseEntity { @Column(name = "SOURCE_ID") diff --git a/src/main/java/net/gepafin/tendermanagement/entities/FormEntity.java b/src/main/java/net/gepafin/tendermanagement/entities/FormEntity.java index 588619a5..5861b7a8 100644 --- a/src/main/java/net/gepafin/tendermanagement/entities/FormEntity.java +++ b/src/main/java/net/gepafin/tendermanagement/entities/FormEntity.java @@ -5,6 +5,7 @@ import lombok.AllArgsConstructor; import lombok.Data; import lombok.Builder; import lombok.NoArgsConstructor; +import org.hibernate.annotations.Where; @Entity @Table(name = "FORM") @@ -12,6 +13,7 @@ import lombok.NoArgsConstructor; @NoArgsConstructor @AllArgsConstructor @Builder +@Where(clause = "is_deleted = false") public class FormEntity extends BaseEntity{ @Column(name = "LABEL", length = 255) diff --git a/src/main/java/net/gepafin/tendermanagement/entities/FormFieldEntity.java b/src/main/java/net/gepafin/tendermanagement/entities/FormFieldEntity.java index b75b0c57..66e1485d 100644 --- a/src/main/java/net/gepafin/tendermanagement/entities/FormFieldEntity.java +++ b/src/main/java/net/gepafin/tendermanagement/entities/FormFieldEntity.java @@ -5,6 +5,7 @@ import lombok.AllArgsConstructor; import lombok.Data; import lombok.Builder; import lombok.NoArgsConstructor; +import org.hibernate.annotations.Where; @Entity @Table(name = "FORM_FIELD") @@ -12,6 +13,7 @@ import lombok.NoArgsConstructor; @NoArgsConstructor @AllArgsConstructor @Builder +@Where(clause = "is_deleted = false") public class FormFieldEntity extends BaseEntity{ @Column(name = "NAME", length = 255) @@ -31,4 +33,5 @@ public class FormFieldEntity extends BaseEntity{ @Column(name = "SORT_ORDER") private Integer sortOrder; + } diff --git a/src/main/java/net/gepafin/tendermanagement/entities/FormTemplateEntity.java b/src/main/java/net/gepafin/tendermanagement/entities/FormTemplateEntity.java index 897a9dfd..93f20e18 100644 --- a/src/main/java/net/gepafin/tendermanagement/entities/FormTemplateEntity.java +++ b/src/main/java/net/gepafin/tendermanagement/entities/FormTemplateEntity.java @@ -5,6 +5,7 @@ import lombok.AllArgsConstructor; import lombok.Data; import lombok.Builder; import lombok.NoArgsConstructor; +import org.hibernate.annotations.Where; @Entity @Table(name = "FORM_TEMPLATE") @@ -12,6 +13,7 @@ import lombok.NoArgsConstructor; @NoArgsConstructor @AllArgsConstructor @Builder +@Where(clause = "is_deleted = false") public class FormTemplateEntity extends BaseEntity{ @Column(name = "LABEL", length = 255) diff --git a/src/main/java/net/gepafin/tendermanagement/entities/GlobalConfigEntity.java b/src/main/java/net/gepafin/tendermanagement/entities/GlobalConfigEntity.java index 3f55abdf..21201ba5 100644 --- a/src/main/java/net/gepafin/tendermanagement/entities/GlobalConfigEntity.java +++ b/src/main/java/net/gepafin/tendermanagement/entities/GlobalConfigEntity.java @@ -15,7 +15,4 @@ public class GlobalConfigEntity extends BaseEntity{ @Column(name = "TYPE") private String type; - - @Column(name = "IS_DELETED") - private Boolean isDeleted = false; } diff --git a/src/main/java/net/gepafin/tendermanagement/entities/HubEntity.java b/src/main/java/net/gepafin/tendermanagement/entities/HubEntity.java index 603b42b4..f59e3d3c 100644 --- a/src/main/java/net/gepafin/tendermanagement/entities/HubEntity.java +++ b/src/main/java/net/gepafin/tendermanagement/entities/HubEntity.java @@ -4,12 +4,13 @@ import jakarta.persistence.*; import jakarta.validation.constraints.Size; import lombok.Getter; import lombok.Setter; - +import org.hibernate.annotations.Where; @Entity @Table(name="hub") @Setter @Getter +@Where(clause = "is_deleted = false") public class HubEntity extends BaseEntity{ @Column(name = "COMPANY_NAME") diff --git a/src/main/java/net/gepafin/tendermanagement/entities/HubUserEntity.java b/src/main/java/net/gepafin/tendermanagement/entities/HubUserEntity.java index 1bec68ce..ebed1915 100644 --- a/src/main/java/net/gepafin/tendermanagement/entities/HubUserEntity.java +++ b/src/main/java/net/gepafin/tendermanagement/entities/HubUserEntity.java @@ -3,11 +3,13 @@ package net.gepafin.tendermanagement.entities; import jakarta.persistence.*; import lombok.Getter; import lombok.Setter; +import org.hibernate.annotations.Where; @Entity @Table(name = "hub_user") @Getter @Setter +@Where(clause = "is_deleted = false") public class HubUserEntity extends BaseEntity{ @ManyToOne diff --git a/src/main/java/net/gepafin/tendermanagement/entities/LoginAttemptEntity.java b/src/main/java/net/gepafin/tendermanagement/entities/LoginAttemptEntity.java index bc3e445e..6ad5a652 100644 --- a/src/main/java/net/gepafin/tendermanagement/entities/LoginAttemptEntity.java +++ b/src/main/java/net/gepafin/tendermanagement/entities/LoginAttemptEntity.java @@ -2,12 +2,14 @@ package net.gepafin.tendermanagement.entities; import jakarta.persistence.*; import lombok.Data; +import org.hibernate.annotations.Where; import java.time.LocalDateTime; @Entity @Table(name = "LOGIN_ATTEMPT") @Data +@Where(clause = "is_deleted = false") public class LoginAttemptEntity extends BaseEntity{ @Column(name = "USERNAME") diff --git a/src/main/java/net/gepafin/tendermanagement/entities/LookUpDataEntity.java b/src/main/java/net/gepafin/tendermanagement/entities/LookUpDataEntity.java index 1afe87aa..21475d82 100644 --- a/src/main/java/net/gepafin/tendermanagement/entities/LookUpDataEntity.java +++ b/src/main/java/net/gepafin/tendermanagement/entities/LookUpDataEntity.java @@ -3,10 +3,12 @@ package net.gepafin.tendermanagement.entities; import com.fasterxml.jackson.annotation.JsonValue; import jakarta.persistence.*; import lombok.Data; +import org.hibernate.annotations.Where; @Entity @Table(name = "LOOKUP_DATA") @Data +@Where(clause = "is_deleted = false") public class LookUpDataEntity extends BaseEntity{ @Column(name = "TITLE", columnDefinition = "TEXT", nullable = true) diff --git a/src/main/java/net/gepafin/tendermanagement/entities/NotificationEntity.java b/src/main/java/net/gepafin/tendermanagement/entities/NotificationEntity.java index 718a7367..9896140b 100644 --- a/src/main/java/net/gepafin/tendermanagement/entities/NotificationEntity.java +++ b/src/main/java/net/gepafin/tendermanagement/entities/NotificationEntity.java @@ -6,10 +6,12 @@ import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; import lombok.Data; +import org.hibernate.annotations.Where; @Entity @Table(name = "NOTIFICATION") @Data +@Where(clause = "is_deleted = false") public class NotificationEntity extends BaseEntity { @Column(name = "USER_ID") @@ -24,9 +26,6 @@ public class NotificationEntity extends BaseEntity { @Column(name = "STATUS") private String status; - @Column(name = "IS_DELETED") - private Boolean isDeleted; - @Column(name = "NOTIFICATION_TYPE") private String notificationType; diff --git a/src/main/java/net/gepafin/tendermanagement/entities/NotificationTypeEntity.java b/src/main/java/net/gepafin/tendermanagement/entities/NotificationTypeEntity.java index 40d3f220..8a56b9ab 100644 --- a/src/main/java/net/gepafin/tendermanagement/entities/NotificationTypeEntity.java +++ b/src/main/java/net/gepafin/tendermanagement/entities/NotificationTypeEntity.java @@ -4,10 +4,12 @@ import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.Table; import lombok.Data; +import org.hibernate.annotations.Where; @Entity @Data @Table(name = "NOTIFICATION_TYPE") +@Where(clause = "is_deleted = false") public class NotificationTypeEntity extends BaseEntity { @Column(name = "NOTIFICATION_NAME") @@ -18,7 +20,4 @@ public class NotificationTypeEntity extends BaseEntity { @Column(name = "TITLE") private String title; - - @Column(name="IS_DELETED") - private Boolean isDeleted; } diff --git a/src/main/java/net/gepafin/tendermanagement/entities/ProtocolEntity.java b/src/main/java/net/gepafin/tendermanagement/entities/ProtocolEntity.java index 69303b41..a6ecbfe3 100644 --- a/src/main/java/net/gepafin/tendermanagement/entities/ProtocolEntity.java +++ b/src/main/java/net/gepafin/tendermanagement/entities/ProtocolEntity.java @@ -3,12 +3,14 @@ package net.gepafin.tendermanagement.entities; import jakarta.persistence.*; import lombok.Data; import net.gepafin.tendermanagement.config.LocalTimeAttributeConverter; +import org.hibernate.annotations.Where; import java.time.LocalTime; @Entity @Table(name = "PROTOCOL") @Data +@Where(clause = "is_deleted = false") public class ProtocolEntity extends BaseEntity { @Column(name = "PROTOCOL_NUMBER", nullable = false) diff --git a/src/main/java/net/gepafin/tendermanagement/entities/RegionEntity.java b/src/main/java/net/gepafin/tendermanagement/entities/RegionEntity.java index 386b9787..d18020bd 100644 --- a/src/main/java/net/gepafin/tendermanagement/entities/RegionEntity.java +++ b/src/main/java/net/gepafin/tendermanagement/entities/RegionEntity.java @@ -6,6 +6,7 @@ import jakarta.persistence.Table; import lombok.Getter; import lombok.Setter; +import org.hibernate.annotations.Where; import java.math.BigDecimal; @@ -13,6 +14,7 @@ import java.math.BigDecimal; @Table(name = "REGION") @Getter @Setter +@Where(clause = "is_deleted = false") public class RegionEntity extends BaseEntity { @Column(name = "REGION_NAME", length = 255, nullable = true) diff --git a/src/main/java/net/gepafin/tendermanagement/entities/RoleActionContextEntity.java b/src/main/java/net/gepafin/tendermanagement/entities/RoleActionContextEntity.java index a3ff676f..88408904 100644 --- a/src/main/java/net/gepafin/tendermanagement/entities/RoleActionContextEntity.java +++ b/src/main/java/net/gepafin/tendermanagement/entities/RoleActionContextEntity.java @@ -4,10 +4,12 @@ import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.Table; import lombok.Data; +import org.hibernate.annotations.Where; @Entity @Data @Table(name ="role_action_context") +@Where(clause = "is_deleted = false") public class RoleActionContextEntity extends BaseEntity { @Column(name = "action_context") @@ -16,9 +18,6 @@ public class RoleActionContextEntity extends BaseEntity { @Column(name = "role_id") private Long roleId; - @Column(name="is_deleted") - private Boolean isDeleted; - @Column(name = "is_viewed") private Boolean isViewed; diff --git a/src/main/java/net/gepafin/tendermanagement/entities/RoleEntity.java b/src/main/java/net/gepafin/tendermanagement/entities/RoleEntity.java index c94b36b9..55091136 100644 --- a/src/main/java/net/gepafin/tendermanagement/entities/RoleEntity.java +++ b/src/main/java/net/gepafin/tendermanagement/entities/RoleEntity.java @@ -4,11 +4,13 @@ import jakarta.persistence.*; import lombok.Getter; import lombok.Setter; +import org.hibernate.annotations.Where; @Entity @Table(name = "ROLE") @Getter @Setter +@Where(clause = "is_deleted = false") public class RoleEntity extends BaseEntity { @Column(name = "ROLE_NAME", length = 255, nullable = true) diff --git a/src/main/java/net/gepafin/tendermanagement/entities/S3ConfigEntity.java b/src/main/java/net/gepafin/tendermanagement/entities/S3ConfigEntity.java index 66502bbc..b058aa70 100644 --- a/src/main/java/net/gepafin/tendermanagement/entities/S3ConfigEntity.java +++ b/src/main/java/net/gepafin/tendermanagement/entities/S3ConfigEntity.java @@ -4,10 +4,12 @@ import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.Table; import lombok.Data; +import org.hibernate.annotations.Where; @Entity @Table(name = "s3_path_configuration") @Data +@Where(clause = "is_deleted = false") public class S3ConfigEntity extends BaseEntity { @Column(name = "TYPE") @@ -21,5 +23,6 @@ public class S3ConfigEntity extends BaseEntity { @Column(name = "PARENT_FOLDER") private String parentFolder; + } diff --git a/src/main/java/net/gepafin/tendermanagement/entities/SamlResponseEntity.java b/src/main/java/net/gepafin/tendermanagement/entities/SamlResponseEntity.java index 05535e97..a3dcd2c8 100644 --- a/src/main/java/net/gepafin/tendermanagement/entities/SamlResponseEntity.java +++ b/src/main/java/net/gepafin/tendermanagement/entities/SamlResponseEntity.java @@ -4,10 +4,12 @@ import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.Table; import lombok.Data; +import org.hibernate.annotations.Where; @Entity @Table(name = "SAML_RESPONSE") @Data +@Where(clause = "is_deleted = false") public class SamlResponseEntity extends BaseEntity{ @Column(name = "AUTHENTICATION_OBJECT") @@ -30,6 +32,5 @@ public class SamlResponseEntity extends BaseEntity{ @Column(name = "TOKEN") private String token; - } diff --git a/src/main/java/net/gepafin/tendermanagement/entities/SystemEmailTemplatesEntity.java b/src/main/java/net/gepafin/tendermanagement/entities/SystemEmailTemplatesEntity.java index b2c3cfb7..c35838ab 100644 --- a/src/main/java/net/gepafin/tendermanagement/entities/SystemEmailTemplatesEntity.java +++ b/src/main/java/net/gepafin/tendermanagement/entities/SystemEmailTemplatesEntity.java @@ -8,10 +8,12 @@ import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; import lombok.Data; +import org.hibernate.annotations.Where; @Entity @Table(name = "system_email_template") @Data +@Where(clause = "is_deleted = false") public class SystemEmailTemplatesEntity extends BaseEntity { @@ -32,9 +34,6 @@ public class SystemEmailTemplatesEntity extends BaseEntity { @Column(name = "SYSTEM") private Boolean system; - - @Column(name ="IS_DELETED", nullable = false) - private Boolean isDeleted = false; @Column(name = "email_scenario") private String emailScenario; diff --git a/src/main/java/net/gepafin/tendermanagement/entities/UserActionEntity.java b/src/main/java/net/gepafin/tendermanagement/entities/UserActionEntity.java index 6f85f6ef..f15e173c 100644 --- a/src/main/java/net/gepafin/tendermanagement/entities/UserActionEntity.java +++ b/src/main/java/net/gepafin/tendermanagement/entities/UserActionEntity.java @@ -4,10 +4,12 @@ import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.Table; import lombok.Data; +import org.hibernate.annotations.Where; @Data @Entity @Table(name = "user_action") +@Where(clause = "is_deleted = false") public class UserActionEntity extends BaseEntity { @Column(name = "USER_ID") @@ -40,7 +42,4 @@ public class UserActionEntity extends BaseEntity { @Column(name = "RESPONSE") private String response; - @Column(name = "IS_DELETED", nullable = false) - private Boolean isDeleted = false; - } \ No newline at end of file diff --git a/src/main/java/net/gepafin/tendermanagement/entities/UserCompanyDelegationEntity.java b/src/main/java/net/gepafin/tendermanagement/entities/UserCompanyDelegationEntity.java index 9def5dc3..8659d16a 100644 --- a/src/main/java/net/gepafin/tendermanagement/entities/UserCompanyDelegationEntity.java +++ b/src/main/java/net/gepafin/tendermanagement/entities/UserCompanyDelegationEntity.java @@ -2,10 +2,12 @@ package net.gepafin.tendermanagement.entities; import jakarta.persistence.*; import lombok.Data; +import org.hibernate.annotations.Where; @Data @Entity @Table(name = "user_company_delegation") +@Where(clause = "is_deleted = false") public class UserCompanyDelegationEntity extends BaseEntity{ @Column(name="USER_ID") diff --git a/src/main/java/net/gepafin/tendermanagement/entities/UserEntity.java b/src/main/java/net/gepafin/tendermanagement/entities/UserEntity.java index 606f52d9..51efe5a9 100644 --- a/src/main/java/net/gepafin/tendermanagement/entities/UserEntity.java +++ b/src/main/java/net/gepafin/tendermanagement/entities/UserEntity.java @@ -7,6 +7,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.Getter; import lombok.Setter; +import org.hibernate.annotations.Where; import java.time.LocalDateTime; @@ -14,6 +15,7 @@ import java.time.LocalDateTime; @Table(name = "GEPAFIN_USER") @Getter @Setter +@Where(clause = "is_deleted = false") public class UserEntity extends BaseEntity { @Column(name = "PASSWORD", columnDefinition = "TEXT",nullable = true) diff --git a/src/main/java/net/gepafin/tendermanagement/entities/UserWithCompanyEntity.java b/src/main/java/net/gepafin/tendermanagement/entities/UserWithCompanyEntity.java index 65afbe76..9c067b32 100644 --- a/src/main/java/net/gepafin/tendermanagement/entities/UserWithCompanyEntity.java +++ b/src/main/java/net/gepafin/tendermanagement/entities/UserWithCompanyEntity.java @@ -4,10 +4,12 @@ import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.Table; import lombok.Data; +import org.hibernate.annotations.Where; @Entity @Table(name = "USER_WITH_COMPANY") @Data +@Where(clause = "is_deleted = false") public class UserWithCompanyEntity extends BaseEntity{ @Column(name = "USER_ID") @@ -34,8 +36,5 @@ public class UserWithCompanyEntity extends BaseEntity{ @Column(name = "EMAIL") private String email; - - @Column(name = "IS_DELETED") - private Boolean isDeleted = false; - + } diff --git a/src/main/java/net/gepafin/tendermanagement/entities/VersionHistoryEntity.java b/src/main/java/net/gepafin/tendermanagement/entities/VersionHistoryEntity.java index 2445fb44..cca7dfc2 100644 --- a/src/main/java/net/gepafin/tendermanagement/entities/VersionHistoryEntity.java +++ b/src/main/java/net/gepafin/tendermanagement/entities/VersionHistoryEntity.java @@ -4,10 +4,12 @@ import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.Table; import lombok.Data; +import org.hibernate.annotations.Where; @Data @Entity @Table(name = "version_history") +@Where(clause = "is_deleted = false") public class VersionHistoryEntity extends BaseEntity { @Column(name = "OLD_DATA", columnDefinition = "LONGTEXT") @@ -30,7 +32,4 @@ public class VersionHistoryEntity extends BaseEntity { @Column(name = "USER_ID") private Long userId; - - @Column(name = "IS_DELETED", nullable = false) - private Boolean isDeleted = false; } 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 f4e71555..eb0713f9 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 @@ -2722,4 +2722,139 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 368e115ea2ca5c816a356502b856f7572dc0ad42 Mon Sep 17 00:00:00 2001 From: rajesh Date: Wed, 4 Jun 2025 13:03:04 +0530 Subject: [PATCH 07/35] Removed = sign from csv data --- .../java/net/gepafin/tendermanagement/dao/ApplicationDao.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java index c45171e8..f5045d74 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java @@ -1972,7 +1972,7 @@ public class ApplicationDao { } // Wrap it in ="..." to make Excel treat it as a literal string - return "=\"" + stringValue.replace("\"", "\"\"") + "\""; + return stringValue; } catch (Exception e) { return ""; From 578fd8e6ee2f1a5f2f6429d1023b618b0f783f65 Mon Sep 17 00:00:00 2001 From: rajesh Date: Wed, 4 Jun 2025 13:03:04 +0530 Subject: [PATCH 08/35] Removed = sign from csv data --- .../java/net/gepafin/tendermanagement/dao/ApplicationDao.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java index b0f09777..108b49da 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java @@ -1956,7 +1956,7 @@ public class ApplicationDao { } // Wrap it in ="..." to make Excel treat it as a literal string - return "=\"" + stringValue.replace("\"", "\"\"") + "\""; + return stringValue; } catch (Exception e) { return ""; From 4482fb003f139985fa0af1ee533ceff5b73b651c Mon Sep 17 00:00:00 2001 From: rajesh Date: Thu, 5 Jun 2025 15:20:03 +0530 Subject: [PATCH 09/35] Fixed Ndg visura api call. --- .../tendermanagement/dao/AppointmentDao.java | 58 +++++++++++++------ 1 file changed, 39 insertions(+), 19 deletions(-) diff --git a/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java b/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java index f82d9906..b8507b3a 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java @@ -486,7 +486,7 @@ public class AppointmentDao { // Try retrieving NDG by VAT number AppointmentLoginResponse ndgResponse = retrieveNdgByVatNumber(company.getVatNumber(), authorizationToken, hub, application); if (isNdgValid(ndgResponse.getNdg())) { - saveNdgAndIdVisura(application, company, ndgResponse.getNdg(), null); + saveNdgAndIdVisura(application, company, ndgResponse.getNdg()); log.info("NDG successfully generated for applicationId: {}", applicationId); } else { // If NDG isn't immediately available, start polling @@ -511,33 +511,54 @@ public class AppointmentDao { } try { - // Fetch Visura list and attempt to parse NDG - String visuraListJson = getVisuraList(application.getIdVisura(), authorizationToken, application, hub); - log.debug("Parsing NDG from visura list response | ApplicationId: {}", application.getId()); - String ndg = parseNdgFromVisuraListResponse(visuraListJson); - if (isNdgValid(ndg)) { - // CompanyEntity oldCompanyData = Utils.getClonedEntityForData(company); - // ApplicationEntity oldApplicationData = Utils.getClonedEntityForData(application); - - log.info("Valid NDG retrieved: {} | ApplicationId: {}", ndg, application.getId()); - company.setNdg(ndg); - application.setNdg(ndg); + // Fetch Ndg via creating visura + AppointmentLoginResponse visuraResponse = createVisura(company, authorizationToken, hub); + if (isNdgValid(visuraResponse.getNdg())) { + log.info("Valid NDG retrieved from create visura api response: {} | ApplicationId: {}", visuraResponse.getNdg(), application.getId()); + company.setNdg(visuraResponse.getNdg()); + application.setNdg(visuraResponse.getNdg()); application.setNdgStatus(GepafinConstant.NDG_GENERATED); application.setStatus(ApplicationStatusTypeEnum.NDG.getValue()); + application.setIdVisura(visuraResponse.getIdVisura()); applicationRepository.save(application); companyRepository.save(company); - ApplicationEvaluationEntity applicationEvaluationEntity = applicationEvaluationService.validateApplicationEvaluation(application.getApplicationEvaluationId()); -// Map placeHolders = notificationDao.sendNotificationToBeneficiary(application, NotificationTypeEnum.NDG_GENERATION); - + ApplicationEvaluationEntity applicationEvaluationEntity = applicationEvaluationService.validateApplicationEvaluation( + application.getApplicationEvaluationId()); Map placeHolders = new HashMap<>(); placeHolders.put("{{call_name}}", application.getCall().getName()); placeHolders.put("{{protocol_number}}", String.valueOf(application.getProtocol().getProtocolNumber())); notificationDao.sendNotificationToInstructor(placeHolders, applicationEvaluationEntity, NotificationTypeEnum.NDG_GENERATION); notificationDao.sendNotificationToSuperUser(application, placeHolders, NotificationTypeEnum.NDG_GENERATION); - log.info("NDG saved successfully for applicationId: {}", application.getId()); + log.info("Got NDG and saved successfully for applicationId: {}", application.getId()); break; - } + } else { + // Fetch Visura list and attempt to parse NDG + String visuraListJson = getVisuraList(visuraResponse.getIdVisura(), authorizationToken, application, hub); + log.debug("Parsing NDG from visura list response | ApplicationId: {}", application.getId()); + String ndg = parseNdgFromVisuraListResponse(visuraListJson); + if (isNdgValid(ndg)) { + // CompanyEntity oldCompanyData = Utils.getClonedEntityForData(company); + // ApplicationEntity oldApplicationData = Utils.getClonedEntityForData(application); + log.info("Valid NDG retrieved: {} | ApplicationId: {}", ndg, application.getId()); + company.setNdg(ndg); + application.setNdg(ndg); + application.setNdgStatus(GepafinConstant.NDG_GENERATED); + application.setStatus(ApplicationStatusTypeEnum.NDG.getValue()); + application.setIdVisura(visuraResponse.getIdVisura()); + applicationRepository.save(application); + companyRepository.save(company); + ApplicationEvaluationEntity applicationEvaluationEntity = applicationEvaluationService.validateApplicationEvaluation( + application.getApplicationEvaluationId()); + Map placeHolders = new HashMap<>(); + placeHolders.put("{{call_name}}", application.getCall().getName()); + placeHolders.put("{{protocol_number}}", String.valueOf(application.getProtocol().getProtocolNumber())); + notificationDao.sendNotificationToInstructor(placeHolders, applicationEvaluationEntity, NotificationTypeEnum.NDG_GENERATION); + notificationDao.sendNotificationToSuperUser(application, placeHolders, NotificationTypeEnum.NDG_GENERATION); + log.info("NDG saved successfully for applicationId: {}", application.getId()); + break; + } + } // Check if polling has timed out if (System.currentTimeMillis() - startTime > TimeUnit.HOURS.toMillis(2)) { log.warn("NDG polling timed out for applicationId: {}", application.getId()); @@ -571,10 +592,9 @@ public class AppointmentDao { return ndg != null && !ndg.isEmpty(); } - private void saveNdgAndIdVisura(ApplicationEntity application, CompanyEntity company, String ndg, String idVisura) { + private void saveNdgAndIdVisura(ApplicationEntity application, CompanyEntity company, String ndg) { application.setNdg(ndg); - application.setIdVisura(idVisura); application.setNdgStatus(GepafinConstant.NDG_GENERATED); application.setStatus(ApplicationStatusTypeEnum.NDG.getValue()); company.setNdg(ndg); From cdc98d2918d2a26626a0a77be72664a96f10462a Mon Sep 17 00:00:00 2001 From: rajesh Date: Thu, 5 Jun 2025 15:20:03 +0530 Subject: [PATCH 10/35] Fixed Ndg visura api call. --- .../tendermanagement/dao/AppointmentDao.java | 58 +++++++++++++------ 1 file changed, 39 insertions(+), 19 deletions(-) diff --git a/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java b/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java index 6b57af23..969a5430 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java @@ -488,7 +488,7 @@ public class AppointmentDao { // Try retrieving NDG by VAT number AppointmentLoginResponse ndgResponse = retrieveNdgByVatNumber(company.getVatNumber(), authorizationToken, hub, application); if (isNdgValid(ndgResponse.getNdg())) { - saveNdgAndIdVisura(application, company, ndgResponse.getNdg(), null); + saveNdgAndIdVisura(application, company, ndgResponse.getNdg()); log.info("NDG successfully generated for applicationId: {}", applicationId); } else { // If NDG isn't immediately available, start polling @@ -513,33 +513,54 @@ public class AppointmentDao { } try { - // Fetch Visura list and attempt to parse NDG - String visuraListJson = getVisuraList(application.getIdVisura(), authorizationToken, application, hub); - log.debug("Parsing NDG from visura list response | ApplicationId: {}", application.getId()); - String ndg = parseNdgFromVisuraListResponse(visuraListJson); - if (isNdgValid(ndg)) { - // CompanyEntity oldCompanyData = Utils.getClonedEntityForData(company); - // ApplicationEntity oldApplicationData = Utils.getClonedEntityForData(application); - - log.info("Valid NDG retrieved: {} | ApplicationId: {}", ndg, application.getId()); - company.setNdg(ndg); - application.setNdg(ndg); + // Fetch Ndg via creating visura + AppointmentLoginResponse visuraResponse = createVisura(company, authorizationToken, hub); + if (isNdgValid(visuraResponse.getNdg())) { + log.info("Valid NDG retrieved from create visura api response: {} | ApplicationId: {}", visuraResponse.getNdg(), application.getId()); + company.setNdg(visuraResponse.getNdg()); + application.setNdg(visuraResponse.getNdg()); application.setNdgStatus(GepafinConstant.NDG_GENERATED); application.setStatus(ApplicationStatusTypeEnum.NDG.getValue()); + application.setIdVisura(visuraResponse.getIdVisura()); applicationRepository.save(application); companyRepository.save(company); - ApplicationEvaluationEntity applicationEvaluationEntity = applicationEvaluationService.validateApplicationEvaluation(application.getApplicationEvaluationId()); -// Map placeHolders = notificationDao.sendNotificationToBeneficiary(application, NotificationTypeEnum.NDG_GENERATION); - + ApplicationEvaluationEntity applicationEvaluationEntity = applicationEvaluationService.validateApplicationEvaluation( + application.getApplicationEvaluationId()); Map placeHolders = new HashMap<>(); placeHolders.put("{{call_name}}", application.getCall().getName()); placeHolders.put("{{protocol_number}}", String.valueOf(application.getProtocol().getProtocolNumber())); notificationDao.sendNotificationToInstructor(placeHolders, applicationEvaluationEntity, NotificationTypeEnum.NDG_GENERATION); notificationDao.sendNotificationToSuperUser(application, placeHolders, NotificationTypeEnum.NDG_GENERATION); - log.info("NDG saved successfully for applicationId: {}", application.getId()); + log.info("Got NDG and saved successfully for applicationId: {}", application.getId()); break; - } + } else { + // Fetch Visura list and attempt to parse NDG + String visuraListJson = getVisuraList(visuraResponse.getIdVisura(), authorizationToken, application, hub); + log.debug("Parsing NDG from visura list response | ApplicationId: {}", application.getId()); + String ndg = parseNdgFromVisuraListResponse(visuraListJson); + if (isNdgValid(ndg)) { + // CompanyEntity oldCompanyData = Utils.getClonedEntityForData(company); + // ApplicationEntity oldApplicationData = Utils.getClonedEntityForData(application); + log.info("Valid NDG retrieved: {} | ApplicationId: {}", ndg, application.getId()); + company.setNdg(ndg); + application.setNdg(ndg); + application.setNdgStatus(GepafinConstant.NDG_GENERATED); + application.setStatus(ApplicationStatusTypeEnum.NDG.getValue()); + application.setIdVisura(visuraResponse.getIdVisura()); + applicationRepository.save(application); + companyRepository.save(company); + ApplicationEvaluationEntity applicationEvaluationEntity = applicationEvaluationService.validateApplicationEvaluation( + application.getApplicationEvaluationId()); + Map placeHolders = new HashMap<>(); + placeHolders.put("{{call_name}}", application.getCall().getName()); + placeHolders.put("{{protocol_number}}", String.valueOf(application.getProtocol().getProtocolNumber())); + notificationDao.sendNotificationToInstructor(placeHolders, applicationEvaluationEntity, NotificationTypeEnum.NDG_GENERATION); + notificationDao.sendNotificationToSuperUser(application, placeHolders, NotificationTypeEnum.NDG_GENERATION); + log.info("NDG saved successfully for applicationId: {}", application.getId()); + break; + } + } // Check if polling has timed out if (System.currentTimeMillis() - startTime > TimeUnit.HOURS.toMillis(2)) { log.warn("NDG polling timed out for applicationId: {}", application.getId()); @@ -573,10 +594,9 @@ public class AppointmentDao { return ndg != null && !ndg.isEmpty(); } - private void saveNdgAndIdVisura(ApplicationEntity application, CompanyEntity company, String ndg, String idVisura) { + private void saveNdgAndIdVisura(ApplicationEntity application, CompanyEntity company, String ndg) { application.setNdg(ndg); - application.setIdVisura(idVisura); application.setNdgStatus(GepafinConstant.NDG_GENERATED); application.setStatus(ApplicationStatusTypeEnum.NDG.getValue()); company.setNdg(ndg); From 7b0e9b57b2c7cc5f2da1d82f4a79c8bf29a9defa Mon Sep 17 00:00:00 2001 From: rajesh Date: Thu, 5 Jun 2025 16:33:54 +0530 Subject: [PATCH 11/35] Updated code for visura response and added logs for better tracking. --- .../tendermanagement/dao/AppointmentDao.java | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java b/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java index b8507b3a..1589a045 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java @@ -770,19 +770,28 @@ public class AppointmentDao { public AppointmentLoginResponse parseVisuraResponse(String jsonResponse) { try { + // Log full raw JSON for debug purposes + log.info("Raw Visura JSON Response: {}", jsonResponse); ObjectMapper objectMapper = new ObjectMapper(); JsonNode rootNode = objectMapper.readTree(jsonResponse); JsonNode dataNode = rootNode.get(GepafinConstant.DATA_STRING); - if (dataNode != null) { + if (dataNode != null && dataNode.isObject()) { AppointmentLoginResponse response = new AppointmentLoginResponse(); - response.setIdVisura(normalizeNullValue(dataNode.get(GepafinConstant.ID_VISURA_STRING).asText())); - response.setNdg(normalizeNullValue(dataNode.get(GepafinConstant.NDG_STRING).asText())); + JsonNode idVisuraNode = dataNode.get(GepafinConstant.ID_VISURA_STRING); + JsonNode ndgNode = dataNode.get(GepafinConstant.NDG_STRING); + if (idVisuraNode == null || ndgNode == null) { + log.error("Missing expected fields in 'data' node. JSON: {}", dataNode); + } + response.setIdVisura(normalizeNullValue(idVisuraNode != null ? idVisuraNode.asText() : null)); + response.setNdg(normalizeNullValue(ndgNode != null ? ndgNode.asText() : null)); return response; } else { - throw new RuntimeException("Invalid JSON structure: Missing 'data' node."); + System.err.println("Invalid JSON: 'data' node is missing or not an object."); + throw new RuntimeException("Invalid JSON structure: Missing or malformed 'data' node."); } } catch (Exception e) { + System.err.println("Exception while parsing Visura response: " + e.getMessage()); throw new RuntimeException("Failed to parse response: " + e.getMessage(), e); } } From f380aee9199b04f9d58be116f2eb1869f23ffe39 Mon Sep 17 00:00:00 2001 From: rajesh Date: Thu, 5 Jun 2025 16:33:54 +0530 Subject: [PATCH 12/35] Updated code for visura response and added logs for better tracking. --- .../tendermanagement/dao/AppointmentDao.java | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java b/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java index 969a5430..5048e1df 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java @@ -772,19 +772,28 @@ public class AppointmentDao { public AppointmentLoginResponse parseVisuraResponse(String jsonResponse) { try { + // Log full raw JSON for debug purposes + log.info("Raw Visura JSON Response: {}", jsonResponse); ObjectMapper objectMapper = new ObjectMapper(); JsonNode rootNode = objectMapper.readTree(jsonResponse); JsonNode dataNode = rootNode.get(GepafinConstant.DATA_STRING); - if (dataNode != null) { + if (dataNode != null && dataNode.isObject()) { AppointmentLoginResponse response = new AppointmentLoginResponse(); - response.setIdVisura(normalizeNullValue(dataNode.get(GepafinConstant.ID_VISURA_STRING).asText())); - response.setNdg(normalizeNullValue(dataNode.get(GepafinConstant.NDG_STRING).asText())); + JsonNode idVisuraNode = dataNode.get(GepafinConstant.ID_VISURA_STRING); + JsonNode ndgNode = dataNode.get(GepafinConstant.NDG_STRING); + if (idVisuraNode == null || ndgNode == null) { + log.error("Missing expected fields in 'data' node. JSON: {}", dataNode); + } + response.setIdVisura(normalizeNullValue(idVisuraNode != null ? idVisuraNode.asText() : null)); + response.setNdg(normalizeNullValue(ndgNode != null ? ndgNode.asText() : null)); return response; } else { - throw new RuntimeException("Invalid JSON structure: Missing 'data' node."); + System.err.println("Invalid JSON: 'data' node is missing or not an object."); + throw new RuntimeException("Invalid JSON structure: Missing or malformed 'data' node."); } } catch (Exception e) { + System.err.println("Exception while parsing Visura response: " + e.getMessage()); throw new RuntimeException("Failed to parse response: " + e.getMessage(), e); } } From 3b015252e22b6274c6055fa3e1cc40fa7cab65ad Mon Sep 17 00:00:00 2001 From: rajesh Date: Thu, 5 Jun 2025 19:40:31 +0530 Subject: [PATCH 13/35] Done ticket GEPAFINBE-228 --- .../constants/GepafinConstant.java | 2 +- .../tendermanagement/dao/AppointmentDao.java | 277 +++++++++++------- .../enums/HttpMethodEnum.java | 18 ++ .../gepafin/tendermanagement/util/Utils.java | 11 + 4 files changed, 203 insertions(+), 105 deletions(-) create mode 100644 src/main/java/net/gepafin/tendermanagement/enums/HttpMethodEnum.java diff --git a/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java b/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java index 57139e52..90988320 100644 --- a/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java +++ b/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java @@ -571,7 +571,7 @@ public class GepafinConstant { public static final String PROTOCOL_EXTERNAL_NUMBER="NUM_PG"; public static final String PROTOCOL_EXTERNAL_DATE="DATA_PG_DT"; public static final String PROTOCOL_DOC_SUFFIX="#noauth"; - + public static final String CREATE_NDG="CHECK_OR_CREATE_NDG_CODE"; } diff --git a/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java b/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java index b8507b3a..f9a1532b 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java @@ -18,10 +18,7 @@ import net.gepafin.tendermanagement.entities.ApplicationEvaluationEntity; import net.gepafin.tendermanagement.entities.CompanyEntity; import net.gepafin.tendermanagement.entities.DocumentEntity; import net.gepafin.tendermanagement.entities.HubEntity; -import net.gepafin.tendermanagement.enums.ApplicationStatusTypeEnum; -import net.gepafin.tendermanagement.enums.DocumentSourceTypeEnum; -import net.gepafin.tendermanagement.enums.NotificationTypeEnum; -import net.gepafin.tendermanagement.enums.VersionActionTypeEnum; +import net.gepafin.tendermanagement.enums.*; import net.gepafin.tendermanagement.model.request.AppointmentCreationRequest; import net.gepafin.tendermanagement.model.request.AppointmentNdgRequest; import net.gepafin.tendermanagement.model.request.AppointmentVisuraListRequest; @@ -66,7 +63,10 @@ import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.ScheduledExecutorService; @Slf4j @Component @@ -150,8 +150,9 @@ public class AppointmentDao { @Autowired private ApplicationEvaluationDao applicationEvaluationDao; - private final Map executorMap = new ConcurrentHashMap<>(); + private final Map executorMap = new ConcurrentHashMap<>(); + private final ConcurrentHashMap threadForDocumentMap = new ConcurrentHashMap<>(); private static final ThreadLocal threadLocalHubId = new ThreadLocal<>(); @@ -432,35 +433,89 @@ public class AppointmentDao { } private void startAsyncNdgProcessing(Long applicationId) { - // Check if a thread is already running for this application + // If already polling for this applicationId, do nothing: if (executorMap.containsKey(applicationId)) { log.warn("Async processing already running for applicationId: {}", applicationId); return; } - // Create a dedicated thread for asynchronous processing - ExecutorService executor = Executors.newSingleThreadExecutor(runnable -> { - Thread thread = new Thread(runnable); - thread.setName("AsyncNdgProcessing-" + applicationId); - return thread; + ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(runnable -> { + Thread t = new Thread(runnable); + t.setName("AsyncNdgProcessing-" + applicationId); + return t; }); - executorMap.put(applicationId, executor); + executorMap.put(applicationId, scheduler); - executor.submit(() -> { + // Record the start time so we can stop after 2 hours: + long startTime = System.currentTimeMillis(); + long twoHoursMs = TimeUnit.HOURS.toMillis(2); + long fifteenMin = 15; // in MINUTES + + // We need a reference to cancel the scheduled task from inside itself when we're done: + AtomicReference> futureRef = new AtomicReference<>(); + + Runnable pollingTask = () -> { try { - log.info("Starting async processing for applicationId: {}", applicationId); - processNdgGeneration(applicationId); - } catch (Exception e) { - log.error("Error in async NDG processing for applicationId: {}", applicationId, e); - } finally { - // Cleanup resources - ExecutorService executorToShutdown = executorMap.remove(applicationId); - if (executorToShutdown != null) { - executorToShutdown.shutdown(); + // 1) If 2 hours have already passed, mark as FAILED and shut down: + if (System.currentTimeMillis() - startTime > twoHoursMs) { + ApplicationEntity app = applicationService.validateApplication(applicationId); + log.warn("2-hour timeout reached for applicationId {}. Marking NDG_FAILED.", applicationId); + app.setNdgStatus(GepafinConstant.NDG_FAILED); + applicationRepository.save(app); + + futureRef.get().cancel(false); + shutdownScheduler(applicationId); + return; + } + + // 2) Otherwise, call processNdgGeneration once: + processNdgGeneration(applicationId); + + // 3) After return, check if NDG is now set or timed out. If so, cancel & shut down: + ApplicationEntity updated = applicationService.validateApplication(applicationId); + if (isNdgValid(updated.getNdg())) { + log.info("NDG found for applicationId {}. Shutting down scheduler.", applicationId); + futureRef.get().cancel(false); + shutdownScheduler(applicationId); + } else if (updated.getNdgStatus() != null && updated.getNdgStatus().equals(GepafinConstant.NDG_FAILED)) { + log.info("NDG status is NDG_FAILED for applicationId {}. Shutting down scheduler.", applicationId); + futureRef.get().cancel(false); + shutdownScheduler(applicationId); + } + // Otherwise: no NDG yet, not timed out → next run happens in 15 minutes automatically. + } catch (Exception ex) { + log.error("Unexpected error during scheduled polling for applicationId {}: {}", applicationId, ex.getMessage(), ex); + try { + ApplicationEntity checkApp = applicationService.validateApplication(applicationId); + if (System.currentTimeMillis() - startTime > twoHoursMs) { + log.warn("After exception, 2-hour window passed for applicationId {}. Marking NDG_FAILED.", applicationId); + checkApp.setNdgStatus(GepafinConstant.NDG_FAILED); + applicationRepository.save(checkApp); + + futureRef.get().cancel(false); + shutdownScheduler(applicationId); + } + } catch (Exception ignore) { + futureRef.get().cancel(false); + shutdownScheduler(applicationId); } - log.info("Async processing completed for applicationId: {}", applicationId); } - }); + }; + + // Schedule pollingTask: run now (delay=0), then every fifteen minutes: + ScheduledFuture future = scheduler.scheduleWithFixedDelay(pollingTask, 0, // initial delay = 0 min → run immediately + fifteenMin, // subsequent runs every 15 minutes + TimeUnit.MINUTES); + futureRef.set(future); + } + + private void shutdownScheduler(Long applicationId) { + + ScheduledExecutorService shed = executorMap.remove(applicationId); + if (shed != null) { + shed.shutdownNow(); + } + log.info("Scheduler shut down for applicationId: {}", applicationId); } private void processNdgGeneration(Long applicationId) { @@ -489,97 +544,108 @@ public class AppointmentDao { saveNdgAndIdVisura(application, company, ndgResponse.getNdg()); log.info("NDG successfully generated for applicationId: {}", applicationId); } else { - // If NDG isn't immediately available, start polling + log.info("Polling for NDG for applicationId: {}", applicationId); handleNdgPolling(application, company, hub, authorizationToken); } } catch (Exception e) { - log.error("Exception occurred during NDG generation. ApplicationId: {}, CompanyId: {}, HubId: {}, Error: {}", - applicationId, company.getId(), hub.getId(), e.getMessage(), e); + log.error("Exception occurred during NDG generation. ApplicationId: {}, CompanyId: {}, HubId: {}, Error: {}", applicationId, company.getId(), hub.getId(), + e.getMessage(), e); } } private void handleNdgPolling(ApplicationEntity application, CompanyEntity company, HubEntity hub, String authorizationToken) { + log.info("Starting singleâ€shot NDG polling attempt for applicationId: {}, CompanyId: {}, HubId: {}", application.getId(), company.getId(), hub.getId()); + + long startTime = System.currentTimeMillis(); + long twoHoursMs = TimeUnit.HOURS.toMillis(2); + try { - log.info("Starting NDG polling for applicationId: {}, CompanyId: {}, HubId: {}", application.getId(),company.getId(), hub.getId()); - long startTime = System.currentTimeMillis(); - - while (true) { - if (application.getNdg() != null) { - log.info("NDG retrieved for applicationId: {}", application.getId()); - break; - } - - try { - // Fetch Ndg via creating visura - AppointmentLoginResponse visuraResponse = createVisura(company, authorizationToken, hub); - if (isNdgValid(visuraResponse.getNdg())) { - log.info("Valid NDG retrieved from create visura api response: {} | ApplicationId: {}", visuraResponse.getNdg(), application.getId()); - company.setNdg(visuraResponse.getNdg()); - application.setNdg(visuraResponse.getNdg()); - application.setNdgStatus(GepafinConstant.NDG_GENERATED); - application.setStatus(ApplicationStatusTypeEnum.NDG.getValue()); - application.setIdVisura(visuraResponse.getIdVisura()); - applicationRepository.save(application); - companyRepository.save(company); - ApplicationEvaluationEntity applicationEvaluationEntity = applicationEvaluationService.validateApplicationEvaluation( - application.getApplicationEvaluationId()); - Map placeHolders = new HashMap<>(); - placeHolders.put("{{call_name}}", application.getCall().getName()); - placeHolders.put("{{protocol_number}}", String.valueOf(application.getProtocol().getProtocolNumber())); - notificationDao.sendNotificationToInstructor(placeHolders, applicationEvaluationEntity, NotificationTypeEnum.NDG_GENERATION); - notificationDao.sendNotificationToSuperUser(application, placeHolders, NotificationTypeEnum.NDG_GENERATION); - log.info("Got NDG and saved successfully for applicationId: {}", application.getId()); - break; - } else { - // Fetch Visura list and attempt to parse NDG - String visuraListJson = getVisuraList(visuraResponse.getIdVisura(), authorizationToken, application, hub); - log.debug("Parsing NDG from visura list response | ApplicationId: {}", application.getId()); - String ndg = parseNdgFromVisuraListResponse(visuraListJson); - if (isNdgValid(ndg)) { - // CompanyEntity oldCompanyData = Utils.getClonedEntityForData(company); - // ApplicationEntity oldApplicationData = Utils.getClonedEntityForData(application); - - log.info("Valid NDG retrieved: {} | ApplicationId: {}", ndg, application.getId()); - company.setNdg(ndg); - application.setNdg(ndg); - application.setNdgStatus(GepafinConstant.NDG_GENERATED); - application.setStatus(ApplicationStatusTypeEnum.NDG.getValue()); - application.setIdVisura(visuraResponse.getIdVisura()); - applicationRepository.save(application); - companyRepository.save(company); - ApplicationEvaluationEntity applicationEvaluationEntity = applicationEvaluationService.validateApplicationEvaluation( - application.getApplicationEvaluationId()); - Map placeHolders = new HashMap<>(); - placeHolders.put("{{call_name}}", application.getCall().getName()); - placeHolders.put("{{protocol_number}}", String.valueOf(application.getProtocol().getProtocolNumber())); - notificationDao.sendNotificationToInstructor(placeHolders, applicationEvaluationEntity, NotificationTypeEnum.NDG_GENERATION); - notificationDao.sendNotificationToSuperUser(application, placeHolders, NotificationTypeEnum.NDG_GENERATION); - log.info("NDG saved successfully for applicationId: {}", application.getId()); - break; - } - } - // Check if polling has timed out - if (System.currentTimeMillis() - startTime > TimeUnit.HOURS.toMillis(2)) { - log.warn("NDG polling timed out for applicationId: {}", application.getId()); - application.setNdgStatus(GepafinConstant.NDG_FAILED); - applicationRepository.save(application); - break; - } - - // Wait before the next polling attempt - Thread.sleep(TimeUnit.MINUTES.toMillis(15)); - } catch (InterruptedException e) { - log.warn("NDG polling interrupted for applicationId: {}", application.getId()); - Thread.currentThread().interrupt(); - break; - } catch (Exception e) { - log.error("Error during NDG polling for applicationId: {}", application.getId(), e); - } + // 1) If NDG was already populated (perhaps by another thread), skip polling. + if (application.getNdg() != null) { + log.info("NDG already present for applicationId {}. Exiting singleâ€shot polling.", application.getId()); + return; + } + + // 2) Attempt to create Visura (this may immediately return a valid NDG) + AppointmentLoginResponse visuraResponse = createVisura(company, authorizationToken, hub); + + // 2a) If createVisura gave us a valid NDG, persist & exit + String fetchedNdg = visuraResponse.getNdg(); + if (isNdgValid(fetchedNdg)) { + log.info("Valid NDG retrieved from createVisura(): {} | applicationId: {}", fetchedNdg, application.getId()); + + company.setNdg(fetchedNdg); + application.setNdg(fetchedNdg); + application.setNdgStatus(GepafinConstant.NDG_GENERATED); + application.setStatus(ApplicationStatusTypeEnum.NDG.getValue()); + application.setIdVisura(visuraResponse.getIdVisura()); + + companyRepository.save(company); + applicationRepository.save(application); + + ApplicationEvaluationEntity eval = applicationEvaluationService.validateApplicationEvaluation(application.getApplicationEvaluationId()); + Map placeholders = new HashMap<>(); + placeholders.put("{{call_name}}", application.getCall().getName()); + placeholders.put("{{protocol_number}}", String.valueOf(application.getProtocol().getProtocolNumber())); + notificationDao.sendNotificationToInstructor(placeholders, eval, NotificationTypeEnum.NDG_GENERATION); + notificationDao.sendNotificationToSuperUser(application, placeholders, NotificationTypeEnum.NDG_GENERATION); + + log.info("NDG saved successfully for applicationId: {}", application.getId()); + return; + } + + // 2b) If no immediate NDG, fetch the Visura list JSON and parse out NDG + String visuraListJson = getVisuraList(visuraResponse.getIdVisura(), authorizationToken, application, hub); + log.debug("Parsing NDG from VisuraList JSON for applicationId: {}", application.getId()); + String parsedNdg = parseNdgFromVisuraListResponse(visuraListJson); + + if (isNdgValid(parsedNdg)) { + log.info("Valid NDG parsed from VisuraList: {} | applicationId: {}", parsedNdg, application.getId()); + + company.setNdg(parsedNdg); + application.setNdg(parsedNdg); + application.setNdgStatus(GepafinConstant.NDG_GENERATED); + application.setStatus(ApplicationStatusTypeEnum.NDG.getValue()); + application.setIdVisura(visuraResponse.getIdVisura()); + + companyRepository.save(company); + applicationRepository.save(application); + + ApplicationEvaluationEntity eval = applicationEvaluationService.validateApplicationEvaluation(application.getApplicationEvaluationId()); + Map placeholders = new HashMap<>(); + placeholders.put("{{call_name}}", application.getCall().getName()); + placeholders.put("{{protocol_number}}", String.valueOf(application.getProtocol().getProtocolNumber())); + notificationDao.sendNotificationToInstructor(placeholders, eval, NotificationTypeEnum.NDG_GENERATION); + notificationDao.sendNotificationToSuperUser(application, placeholders, NotificationTypeEnum.NDG_GENERATION); + + log.info("NDG saved successfully for applicationId: {}", application.getId()); + return; + } + + // 3) Neither direct API nor parsing yielded a valid NDG. Check timeout. + if (System.currentTimeMillis() - startTime > twoHoursMs) { + log.warn("NDG polling timed out after 2 hours for applicationId: {}", application.getId()); + application.setNdgStatus(GepafinConstant.NDG_FAILED); + applicationRepository.save(application); + return; + } + + // 4) No NDG yet—just return. The scheduler will retry in 15 minutes. + log.info("No valid NDG yet for applicationId {}. Returning to scheduler—next attempt in 15 minutes.", application.getId()); + } catch (Exception e) { + log.error("Exception during NDG polling for applicationId: {}", application.getId(), e); + // If timeout after exception, mark NDG_FAILED + if (System.currentTimeMillis() - startTime > twoHoursMs) { + log.warn("Example: exiting singleâ€shot polling due to timeout after exception for applicationId: {}", application.getId()); + application.setNdgStatus(GepafinConstant.NDG_FAILED); + applicationRepository.save(application); + } else { + log.info("Exception occurred but not timed out for applicationId {}. Returning to scheduler for next attempt in 15 minutes.", application.getId()); } - } finally { - log.info("NDG polling completed for applicationId: {}", application.getId()); } + + log.info("NDG polling completed for applicationId: {}", application.getId()); } private static String getBearerToken(HubEntity hub) { @@ -594,12 +660,15 @@ public class AppointmentDao { private void saveNdgAndIdVisura(ApplicationEntity application, CompanyEntity company, String ndg) { + ApplicationEntity oldApplication = Utils.getClonedEntityForData(application); application.setNdg(ndg); application.setNdgStatus(GepafinConstant.NDG_GENERATED); application.setStatus(ApplicationStatusTypeEnum.NDG.getValue()); company.setNdg(ndg); companyRepository.save(company); applicationRepository.save(application); + loggingUtil.addVersionHistory( + VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldApplication).newData(application).build()); ApplicationEvaluationEntity applicationEvaluationEntity = applicationEvaluationService.validateApplicationEvaluation(application.getApplicationEvaluationId()); // Map placeHolders = notificationDao.sendNotificationToBeneficiary(application, NotificationTypeEnum.NDG_GENERATION); Map placeHolders = new HashMap<>(); diff --git a/src/main/java/net/gepafin/tendermanagement/enums/HttpMethodEnum.java b/src/main/java/net/gepafin/tendermanagement/enums/HttpMethodEnum.java new file mode 100644 index 00000000..f71842fb --- /dev/null +++ b/src/main/java/net/gepafin/tendermanagement/enums/HttpMethodEnum.java @@ -0,0 +1,18 @@ +package net.gepafin.tendermanagement.enums; + +import com.fasterxml.jackson.annotation.JsonValue; + +public enum HttpMethodEnum { + POST("POST"), PUT("PUT"), GET("GET"), DELETE("DELETE"); + + private String value; + + HttpMethodEnum(String value) { + this.value = value; + } + + @JsonValue + public String getValue() { + return value; + } +} \ No newline at end of file diff --git a/src/main/java/net/gepafin/tendermanagement/util/Utils.java b/src/main/java/net/gepafin/tendermanagement/util/Utils.java index 469d93ba..13cf488e 100644 --- a/src/main/java/net/gepafin/tendermanagement/util/Utils.java +++ b/src/main/java/net/gepafin/tendermanagement/util/Utils.java @@ -1052,5 +1052,16 @@ public class Utils { headers.add(org.apache.http.HttpHeaders.USER_AGENT, "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0"); return headers; } + + public static void setHttpServletRequestForThread(String redirectURI, String methodType,HttpServletRequest request,String remoteUser) { + MockHttpServletRequest mockRequest = new MockHttpServletRequest(); + mockRequest.setRequestURI(redirectURI); + mockRequest.setMethod(methodType); + mockRequest.setRemoteUser(remoteUser); + Long userActionId = (Long) request.getAttribute(GepafinConstant.USER_ACTION_ID); + mockRequest.setAttribute(GepafinConstant.USER_ACTION_ID,userActionId ); + ServletRequestAttributes attributes = new ServletRequestAttributes(mockRequest); + RequestContextHolder.setRequestAttributes(attributes, true); + } } From 43e8df6cca87f27913229ac0f6752ed1ec7e7d7a Mon Sep 17 00:00:00 2001 From: rajesh Date: Thu, 5 Jun 2025 19:40:31 +0530 Subject: [PATCH 14/35] Resolved conflicts --- .../constants/GepafinConstant.java | 2 +- .../tendermanagement/dao/AppointmentDao.java | 277 +++++++++++------- .../enums/HttpMethodEnum.java | 18 ++ .../gepafin/tendermanagement/util/Utils.java | 10 + 4 files changed, 202 insertions(+), 105 deletions(-) create mode 100644 src/main/java/net/gepafin/tendermanagement/enums/HttpMethodEnum.java diff --git a/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java b/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java index f9fbe5d6..577d5dd4 100644 --- a/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java +++ b/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java @@ -549,7 +549,7 @@ public class GepafinConstant { public static final String CAUSE_STRING = "cause" ; public static final String ERROR_DESCRIPTION_STRING = "errorDescription" ; public static final String ERROR_STRING = "errors"; - + public static final String CREATE_NDG="CHECK_OR_CREATE_NDG_CODE"; } diff --git a/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java b/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java index 5048e1df..ddbbffbe 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java @@ -18,10 +18,7 @@ import net.gepafin.tendermanagement.entities.ApplicationEvaluationEntity; import net.gepafin.tendermanagement.entities.CompanyEntity; import net.gepafin.tendermanagement.entities.DocumentEntity; import net.gepafin.tendermanagement.entities.HubEntity; -import net.gepafin.tendermanagement.enums.ApplicationStatusTypeEnum; -import net.gepafin.tendermanagement.enums.DocumentSourceTypeEnum; -import net.gepafin.tendermanagement.enums.NotificationTypeEnum; -import net.gepafin.tendermanagement.enums.VersionActionTypeEnum; +import net.gepafin.tendermanagement.enums.*; import net.gepafin.tendermanagement.model.request.AppointmentCreationRequest; import net.gepafin.tendermanagement.model.request.AppointmentNdgRequest; import net.gepafin.tendermanagement.model.request.AppointmentVisuraListRequest; @@ -66,7 +63,10 @@ import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.ScheduledExecutorService; @Slf4j @Component @@ -150,8 +150,9 @@ public class AppointmentDao { @Autowired private ApplicationEvaluationDao applicationEvaluationDao; - private final Map executorMap = new ConcurrentHashMap<>(); + private final Map executorMap = new ConcurrentHashMap<>(); + private final ConcurrentHashMap threadForDocumentMap = new ConcurrentHashMap<>(); private static final ThreadLocal threadLocalHubId = new ThreadLocal<>(); @@ -434,35 +435,89 @@ public class AppointmentDao { } private void startAsyncNdgProcessing(Long applicationId) { - // Check if a thread is already running for this application + // If already polling for this applicationId, do nothing: if (executorMap.containsKey(applicationId)) { log.warn("Async processing already running for applicationId: {}", applicationId); return; } - // Create a dedicated thread for asynchronous processing - ExecutorService executor = Executors.newSingleThreadExecutor(runnable -> { - Thread thread = new Thread(runnable); - thread.setName("AsyncNdgProcessing-" + applicationId); - return thread; + ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(runnable -> { + Thread t = new Thread(runnable); + t.setName("AsyncNdgProcessing-" + applicationId); + return t; }); - executorMap.put(applicationId, executor); + executorMap.put(applicationId, scheduler); - executor.submit(() -> { + // Record the start time so we can stop after 2 hours: + long startTime = System.currentTimeMillis(); + long twoHoursMs = TimeUnit.HOURS.toMillis(2); + long fifteenMin = 15; // in MINUTES + + // We need a reference to cancel the scheduled task from inside itself when we're done: + AtomicReference> futureRef = new AtomicReference<>(); + + Runnable pollingTask = () -> { try { - log.info("Starting async processing for applicationId: {}", applicationId); - processNdgGeneration(applicationId); - } catch (Exception e) { - log.error("Error in async NDG processing for applicationId: {}", applicationId, e); - } finally { - // Cleanup resources - ExecutorService executorToShutdown = executorMap.remove(applicationId); - if (executorToShutdown != null) { - executorToShutdown.shutdown(); + // 1) If 2 hours have already passed, mark as FAILED and shut down: + if (System.currentTimeMillis() - startTime > twoHoursMs) { + ApplicationEntity app = applicationService.validateApplication(applicationId); + log.warn("2-hour timeout reached for applicationId {}. Marking NDG_FAILED.", applicationId); + app.setNdgStatus(GepafinConstant.NDG_FAILED); + applicationRepository.save(app); + + futureRef.get().cancel(false); + shutdownScheduler(applicationId); + return; + } + + // 2) Otherwise, call processNdgGeneration once: + processNdgGeneration(applicationId); + + // 3) After return, check if NDG is now set or timed out. If so, cancel & shut down: + ApplicationEntity updated = applicationService.validateApplication(applicationId); + if (isNdgValid(updated.getNdg())) { + log.info("NDG found for applicationId {}. Shutting down scheduler.", applicationId); + futureRef.get().cancel(false); + shutdownScheduler(applicationId); + } else if (updated.getNdgStatus() != null && updated.getNdgStatus().equals(GepafinConstant.NDG_FAILED)) { + log.info("NDG status is NDG_FAILED for applicationId {}. Shutting down scheduler.", applicationId); + futureRef.get().cancel(false); + shutdownScheduler(applicationId); + } + // Otherwise: no NDG yet, not timed out → next run happens in 15 minutes automatically. + } catch (Exception ex) { + log.error("Unexpected error during scheduled polling for applicationId {}: {}", applicationId, ex.getMessage(), ex); + try { + ApplicationEntity checkApp = applicationService.validateApplication(applicationId); + if (System.currentTimeMillis() - startTime > twoHoursMs) { + log.warn("After exception, 2-hour window passed for applicationId {}. Marking NDG_FAILED.", applicationId); + checkApp.setNdgStatus(GepafinConstant.NDG_FAILED); + applicationRepository.save(checkApp); + + futureRef.get().cancel(false); + shutdownScheduler(applicationId); + } + } catch (Exception ignore) { + futureRef.get().cancel(false); + shutdownScheduler(applicationId); } - log.info("Async processing completed for applicationId: {}", applicationId); } - }); + }; + + // Schedule pollingTask: run now (delay=0), then every fifteen minutes: + ScheduledFuture future = scheduler.scheduleWithFixedDelay(pollingTask, 0, // initial delay = 0 min → run immediately + fifteenMin, // subsequent runs every 15 minutes + TimeUnit.MINUTES); + futureRef.set(future); + } + + private void shutdownScheduler(Long applicationId) { + + ScheduledExecutorService shed = executorMap.remove(applicationId); + if (shed != null) { + shed.shutdownNow(); + } + log.info("Scheduler shut down for applicationId: {}", applicationId); } private void processNdgGeneration(Long applicationId) { @@ -491,97 +546,108 @@ public class AppointmentDao { saveNdgAndIdVisura(application, company, ndgResponse.getNdg()); log.info("NDG successfully generated for applicationId: {}", applicationId); } else { - // If NDG isn't immediately available, start polling + log.info("Polling for NDG for applicationId: {}", applicationId); handleNdgPolling(application, company, hub, authorizationToken); } } catch (Exception e) { - log.error("Exception occurred during NDG generation. ApplicationId: {}, CompanyId: {}, HubId: {}, Error: {}", - applicationId, company.getId(), hub.getId(), e.getMessage(), e); + log.error("Exception occurred during NDG generation. ApplicationId: {}, CompanyId: {}, HubId: {}, Error: {}", applicationId, company.getId(), hub.getId(), + e.getMessage(), e); } } private void handleNdgPolling(ApplicationEntity application, CompanyEntity company, HubEntity hub, String authorizationToken) { + log.info("Starting singleâ€shot NDG polling attempt for applicationId: {}, CompanyId: {}, HubId: {}", application.getId(), company.getId(), hub.getId()); + + long startTime = System.currentTimeMillis(); + long twoHoursMs = TimeUnit.HOURS.toMillis(2); + try { - log.info("Starting NDG polling for applicationId: {}, CompanyId: {}, HubId: {}", application.getId(),company.getId(), hub.getId()); - long startTime = System.currentTimeMillis(); - - while (true) { - if (application.getNdg() != null) { - log.info("NDG retrieved for applicationId: {}", application.getId()); - break; - } - - try { - // Fetch Ndg via creating visura - AppointmentLoginResponse visuraResponse = createVisura(company, authorizationToken, hub); - if (isNdgValid(visuraResponse.getNdg())) { - log.info("Valid NDG retrieved from create visura api response: {} | ApplicationId: {}", visuraResponse.getNdg(), application.getId()); - company.setNdg(visuraResponse.getNdg()); - application.setNdg(visuraResponse.getNdg()); - application.setNdgStatus(GepafinConstant.NDG_GENERATED); - application.setStatus(ApplicationStatusTypeEnum.NDG.getValue()); - application.setIdVisura(visuraResponse.getIdVisura()); - applicationRepository.save(application); - companyRepository.save(company); - ApplicationEvaluationEntity applicationEvaluationEntity = applicationEvaluationService.validateApplicationEvaluation( - application.getApplicationEvaluationId()); - Map placeHolders = new HashMap<>(); - placeHolders.put("{{call_name}}", application.getCall().getName()); - placeHolders.put("{{protocol_number}}", String.valueOf(application.getProtocol().getProtocolNumber())); - notificationDao.sendNotificationToInstructor(placeHolders, applicationEvaluationEntity, NotificationTypeEnum.NDG_GENERATION); - notificationDao.sendNotificationToSuperUser(application, placeHolders, NotificationTypeEnum.NDG_GENERATION); - log.info("Got NDG and saved successfully for applicationId: {}", application.getId()); - break; - } else { - // Fetch Visura list and attempt to parse NDG - String visuraListJson = getVisuraList(visuraResponse.getIdVisura(), authorizationToken, application, hub); - log.debug("Parsing NDG from visura list response | ApplicationId: {}", application.getId()); - String ndg = parseNdgFromVisuraListResponse(visuraListJson); - if (isNdgValid(ndg)) { - // CompanyEntity oldCompanyData = Utils.getClonedEntityForData(company); - // ApplicationEntity oldApplicationData = Utils.getClonedEntityForData(application); - - log.info("Valid NDG retrieved: {} | ApplicationId: {}", ndg, application.getId()); - company.setNdg(ndg); - application.setNdg(ndg); - application.setNdgStatus(GepafinConstant.NDG_GENERATED); - application.setStatus(ApplicationStatusTypeEnum.NDG.getValue()); - application.setIdVisura(visuraResponse.getIdVisura()); - applicationRepository.save(application); - companyRepository.save(company); - ApplicationEvaluationEntity applicationEvaluationEntity = applicationEvaluationService.validateApplicationEvaluation( - application.getApplicationEvaluationId()); - Map placeHolders = new HashMap<>(); - placeHolders.put("{{call_name}}", application.getCall().getName()); - placeHolders.put("{{protocol_number}}", String.valueOf(application.getProtocol().getProtocolNumber())); - notificationDao.sendNotificationToInstructor(placeHolders, applicationEvaluationEntity, NotificationTypeEnum.NDG_GENERATION); - notificationDao.sendNotificationToSuperUser(application, placeHolders, NotificationTypeEnum.NDG_GENERATION); - log.info("NDG saved successfully for applicationId: {}", application.getId()); - break; - } - } - // Check if polling has timed out - if (System.currentTimeMillis() - startTime > TimeUnit.HOURS.toMillis(2)) { - log.warn("NDG polling timed out for applicationId: {}", application.getId()); - application.setNdgStatus(GepafinConstant.NDG_FAILED); - applicationRepository.save(application); - break; - } - - // Wait before the next polling attempt - Thread.sleep(TimeUnit.MINUTES.toMillis(15)); - } catch (InterruptedException e) { - log.warn("NDG polling interrupted for applicationId: {}", application.getId()); - Thread.currentThread().interrupt(); - break; - } catch (Exception e) { - log.error("Error during NDG polling for applicationId: {}", application.getId(), e); - } + // 1) If NDG was already populated (perhaps by another thread), skip polling. + if (application.getNdg() != null) { + log.info("NDG already present for applicationId {}. Exiting singleâ€shot polling.", application.getId()); + return; + } + + // 2) Attempt to create Visura (this may immediately return a valid NDG) + AppointmentLoginResponse visuraResponse = createVisura(company, authorizationToken, hub); + + // 2a) If createVisura gave us a valid NDG, persist & exit + String fetchedNdg = visuraResponse.getNdg(); + if (isNdgValid(fetchedNdg)) { + log.info("Valid NDG retrieved from createVisura(): {} | applicationId: {}", fetchedNdg, application.getId()); + + company.setNdg(fetchedNdg); + application.setNdg(fetchedNdg); + application.setNdgStatus(GepafinConstant.NDG_GENERATED); + application.setStatus(ApplicationStatusTypeEnum.NDG.getValue()); + application.setIdVisura(visuraResponse.getIdVisura()); + + companyRepository.save(company); + applicationRepository.save(application); + + ApplicationEvaluationEntity eval = applicationEvaluationService.validateApplicationEvaluation(application.getApplicationEvaluationId()); + Map placeholders = new HashMap<>(); + placeholders.put("{{call_name}}", application.getCall().getName()); + placeholders.put("{{protocol_number}}", String.valueOf(application.getProtocol().getProtocolNumber())); + notificationDao.sendNotificationToInstructor(placeholders, eval, NotificationTypeEnum.NDG_GENERATION); + notificationDao.sendNotificationToSuperUser(application, placeholders, NotificationTypeEnum.NDG_GENERATION); + + log.info("NDG saved successfully for applicationId: {}", application.getId()); + return; + } + + // 2b) If no immediate NDG, fetch the Visura list JSON and parse out NDG + String visuraListJson = getVisuraList(visuraResponse.getIdVisura(), authorizationToken, application, hub); + log.debug("Parsing NDG from VisuraList JSON for applicationId: {}", application.getId()); + String parsedNdg = parseNdgFromVisuraListResponse(visuraListJson); + + if (isNdgValid(parsedNdg)) { + log.info("Valid NDG parsed from VisuraList: {} | applicationId: {}", parsedNdg, application.getId()); + + company.setNdg(parsedNdg); + application.setNdg(parsedNdg); + application.setNdgStatus(GepafinConstant.NDG_GENERATED); + application.setStatus(ApplicationStatusTypeEnum.NDG.getValue()); + application.setIdVisura(visuraResponse.getIdVisura()); + + companyRepository.save(company); + applicationRepository.save(application); + + ApplicationEvaluationEntity eval = applicationEvaluationService.validateApplicationEvaluation(application.getApplicationEvaluationId()); + Map placeholders = new HashMap<>(); + placeholders.put("{{call_name}}", application.getCall().getName()); + placeholders.put("{{protocol_number}}", String.valueOf(application.getProtocol().getProtocolNumber())); + notificationDao.sendNotificationToInstructor(placeholders, eval, NotificationTypeEnum.NDG_GENERATION); + notificationDao.sendNotificationToSuperUser(application, placeholders, NotificationTypeEnum.NDG_GENERATION); + + log.info("NDG saved successfully for applicationId: {}", application.getId()); + return; + } + + // 3) Neither direct API nor parsing yielded a valid NDG. Check timeout. + if (System.currentTimeMillis() - startTime > twoHoursMs) { + log.warn("NDG polling timed out after 2 hours for applicationId: {}", application.getId()); + application.setNdgStatus(GepafinConstant.NDG_FAILED); + applicationRepository.save(application); + return; + } + + // 4) No NDG yet—just return. The scheduler will retry in 15 minutes. + log.info("No valid NDG yet for applicationId {}. Returning to scheduler—next attempt in 15 minutes.", application.getId()); + } catch (Exception e) { + log.error("Exception during NDG polling for applicationId: {}", application.getId(), e); + // If timeout after exception, mark NDG_FAILED + if (System.currentTimeMillis() - startTime > twoHoursMs) { + log.warn("Example: exiting singleâ€shot polling due to timeout after exception for applicationId: {}", application.getId()); + application.setNdgStatus(GepafinConstant.NDG_FAILED); + applicationRepository.save(application); + } else { + log.info("Exception occurred but not timed out for applicationId {}. Returning to scheduler for next attempt in 15 minutes.", application.getId()); } - } finally { - log.info("NDG polling completed for applicationId: {}", application.getId()); } + + log.info("NDG polling completed for applicationId: {}", application.getId()); } private static String getBearerToken(HubEntity hub) { @@ -596,12 +662,15 @@ public class AppointmentDao { private void saveNdgAndIdVisura(ApplicationEntity application, CompanyEntity company, String ndg) { + ApplicationEntity oldApplication = Utils.getClonedEntityForData(application); application.setNdg(ndg); application.setNdgStatus(GepafinConstant.NDG_GENERATED); application.setStatus(ApplicationStatusTypeEnum.NDG.getValue()); company.setNdg(ndg); companyRepository.save(company); applicationRepository.save(application); + loggingUtil.addVersionHistory( + VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldApplication).newData(application).build()); ApplicationEvaluationEntity applicationEvaluationEntity = applicationEvaluationService.validateApplicationEvaluation(application.getApplicationEvaluationId()); // Map placeHolders = notificationDao.sendNotificationToBeneficiary(application, NotificationTypeEnum.NDG_GENERATION); Map placeHolders = new HashMap<>(); diff --git a/src/main/java/net/gepafin/tendermanagement/enums/HttpMethodEnum.java b/src/main/java/net/gepafin/tendermanagement/enums/HttpMethodEnum.java new file mode 100644 index 00000000..f71842fb --- /dev/null +++ b/src/main/java/net/gepafin/tendermanagement/enums/HttpMethodEnum.java @@ -0,0 +1,18 @@ +package net.gepafin.tendermanagement.enums; + +import com.fasterxml.jackson.annotation.JsonValue; + +public enum HttpMethodEnum { + POST("POST"), PUT("PUT"), GET("GET"), DELETE("DELETE"); + + private String value; + + HttpMethodEnum(String value) { + this.value = value; + } + + @JsonValue + public String getValue() { + return value; + } +} \ No newline at end of file diff --git a/src/main/java/net/gepafin/tendermanagement/util/Utils.java b/src/main/java/net/gepafin/tendermanagement/util/Utils.java index c14b25d9..d4bad6ed 100644 --- a/src/main/java/net/gepafin/tendermanagement/util/Utils.java +++ b/src/main/java/net/gepafin/tendermanagement/util/Utils.java @@ -1043,5 +1043,15 @@ public class Utils { return new ArrayList<>(responseMap.values()); } + public static void setHttpServletRequestForThread(String redirectURI, String methodType,HttpServletRequest request,String remoteUser) { + MockHttpServletRequest mockRequest = new MockHttpServletRequest(); + mockRequest.setRequestURI(redirectURI); + mockRequest.setMethod(methodType); + mockRequest.setRemoteUser(remoteUser); + Long userActionId = (Long) request.getAttribute(GepafinConstant.USER_ACTION_ID); + mockRequest.setAttribute(GepafinConstant.USER_ACTION_ID,userActionId ); + ServletRequestAttributes attributes = new ServletRequestAttributes(mockRequest); + RequestContextHolder.setRequestAttributes(attributes, true); + } } From bfc850b3b3375804efceab5683020afd2977ea75 Mon Sep 17 00:00:00 2001 From: rajesh Date: Fri, 6 Jun 2025 14:28:21 +0530 Subject: [PATCH 15/35] Logging for status EVALUATION to NDG --- .../tendermanagement/dao/AppointmentDao.java | 2636 +++++++++-------- .../gepafin/tendermanagement/util/Utils.java | 11 +- 2 files changed, 1336 insertions(+), 1311 deletions(-) diff --git a/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java b/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java index 15d9ef73..f0fd43cb 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java @@ -1,1307 +1,1331 @@ -package net.gepafin.tendermanagement.dao; - -import com.amazonaws.services.s3.AmazonS3Client; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import feign.FeignException; -import io.jsonwebtoken.Claims; -import jakarta.servlet.http.HttpServletRequest; -import lombok.extern.slf4j.Slf4j; -import net.gepafin.tendermanagement.config.Translator; -import net.gepafin.tendermanagement.config.jwt.TokenProvider; -import net.gepafin.tendermanagement.constants.AppointmentApiConstant; -import net.gepafin.tendermanagement.constants.GepafinConstant; -import net.gepafin.tendermanagement.entities.ApplicationAmendmentRequestEntity; -import net.gepafin.tendermanagement.entities.ApplicationEntity; -import net.gepafin.tendermanagement.entities.ApplicationEvaluationEntity; -import net.gepafin.tendermanagement.entities.CompanyEntity; -import net.gepafin.tendermanagement.entities.DocumentEntity; -import net.gepafin.tendermanagement.entities.HubEntity; -import net.gepafin.tendermanagement.enums.*; -import net.gepafin.tendermanagement.model.request.AppointmentCreationRequest; -import net.gepafin.tendermanagement.model.request.AppointmentNdgRequest; -import net.gepafin.tendermanagement.model.request.AppointmentVisuraListRequest; -import net.gepafin.tendermanagement.model.request.AppointmentVisuraRequest; -import net.gepafin.tendermanagement.model.request.CreateAppointmentRequest; -import net.gepafin.tendermanagement.model.request.UploadDocToExternalSystemRequest; -import net.gepafin.tendermanagement.model.request.VersionHistoryRequest; -import net.gepafin.tendermanagement.model.response.AppointmentCreationResponse; -import net.gepafin.tendermanagement.model.response.AppointmentLoginResponse; -import net.gepafin.tendermanagement.model.response.DocumentUploadResponse; -import net.gepafin.tendermanagement.model.response.NdgResponse; -import net.gepafin.tendermanagement.repositories.ApplicationRepository; -import net.gepafin.tendermanagement.repositories.CompanyRepository; -import net.gepafin.tendermanagement.repositories.DocumentRepository; -import net.gepafin.tendermanagement.repositories.HubRepository; -import net.gepafin.tendermanagement.repositories.UserRepository; -import net.gepafin.tendermanagement.service.AmazonS3Service; -import net.gepafin.tendermanagement.service.ApplicationService; -import net.gepafin.tendermanagement.service.CompanyService; -import net.gepafin.tendermanagement.service.feignClient.AppointmentApiService; -import net.gepafin.tendermanagement.service.impl.ApplicationEvaluationServiceImpl; -import net.gepafin.tendermanagement.util.LoggingUtil; -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.springframework.beans.BeanUtils; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; -import org.springframework.mock.web.MockMultipartFile; -import org.springframework.stereotype.Component; -import org.springframework.web.multipart.MultipartFile; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicReference; -import java.util.concurrent.ScheduledExecutorService; - -@Slf4j -@Component -public class AppointmentDao { - - @Value("${appointment.portal.user}") - private String user; - - @Value("${appointment.portal.password}") - private String password; - - @Value("${appointment.portal.source}") - private String source; - - @Value("${appointment.portal.context}") - private String context; - - @Value("${default.hub.uuid}") - private String defaultHubUuid; - - @Value("${aws.s3.url}") - private String s3Url; - - @Value("${aws.s3.bucket.name}") - private String OLD_BUCKET; - - @Autowired - private HubRepository hubRepository; - - @Autowired - private AppointmentApiService appointmentApiService; - - @Autowired - private ApplicationService applicationService; - - @Autowired - private CompanyService companyService; - - @Autowired - private ApplicationRepository applicationRepository; - - @Autowired - private CompanyRepository companyRepository; - - @Autowired - private DocumentDao documentDao; - - @Autowired - private AmazonS3Client s3Client; - - @Autowired - private DocumentRepository documentRepository; - - @Autowired - private HttpServletRequest request; - - @Autowired - private LoggingUtil loggingUtil; - - @Autowired - private TokenProvider tokenProvider; - - @Autowired - private NotificationDao notificationDao; - - @Autowired - private UserRepository userRepository; - - @Autowired - private ApplicationEvaluationServiceImpl applicationEvaluationService; - - @Autowired - private AmazonS3Service amazonS3Service; - - @Autowired - private ApplicationDao applicationDao; - - @Autowired - private ApplicationAmendmentRequestDao applicationAmendmentRequestDao; - - @Autowired - private ApplicationEvaluationDao applicationEvaluationDao; - - private final Map executorMap = new ConcurrentHashMap<>(); - - - private final ConcurrentHashMap threadForDocumentMap = new ConcurrentHashMap<>(); - - private static final ThreadLocal threadLocalHubId = new ThreadLocal<>(); - - public NdgResponse checkNdgForAppointment(Long applicationId) { - log.info("Starting NDG check for appointment. applicationId: {}", applicationId); - ApplicationEntity application = applicationService.validateApplication(applicationId); - NdgResponse ndgResponse = new NdgResponse(); - if (application.getNdgStatus() != null && application.getNdgStatus().equalsIgnoreCase(GepafinConstant.NDG_IN_PROGRESS)) { - log.warn("NDG generation already in progress. applicationId: {}", applicationId); - throw new CustomValidationException(Status.SUCCESS, Translator.toLocale(GepafinConstant.NDG_GENERATION_IS_IN_PROGRESS)); - } - - if (application.getNdgStatus() != null && application.getNdgStatus().equalsIgnoreCase(GepafinConstant.NDG_GENERATED) && application.getNdg() != null) { - ndgResponse.setNdg(application.getNdg()); - return ndgResponse; - } - - // Update application status - log.info("Updating NDG status to IN_PROGRESS. applicationId: {}", applicationId); - application.setNdgStatus(GepafinConstant.NDG_IN_PROGRESS); - applicationRepository.save(application); - - // Start async processing - HubEntity hub = hubRepository.findByHubId(application.getHubId()); - loginToOdessa(hub, application); - startAsyncNdgProcessing(applicationId); - log.info("NDG check initiation completed. applicationId: {}", applicationId); - return ndgResponse; - } - - // private HubEntity loginToOdessa(HubEntity hub, ApplicationEntity application) { - // - // int maxRetries = 3; - // int attempt = 0; - // boolean success = false; - // while (attempt < maxRetries && !success) { - // attempt++; - // try { - // //code to generate token with payload having "iat" epoch timestamp and secret key with no expiry and send in below method call - // String authJwtToken = Utils.generateAuthTokenForLoginToOdessa(); - // log.info("Got the auth for login to odessa {}", authJwtToken); - // hub.setAuthToken(authJwtToken); - // hubRepository.save(hub); - // Map body = Collections.emptyMap(); - // ResponseEntity responseLogin = appointmentApiService.loginWithOdessa(authJwtToken, source, context, user, password, body); - // if (responseLogin.getStatusCode() == HttpStatus.OK) { - // log.info("Login successful to odessa. Parsing response."); - // String loginResponseJson = Utils.convertObjectToJson(responseLogin.getBody()); - // AppointmentLoginResponse parsedResponse = parseLoginResponse(loginResponseJson); - // - // // Validate and save token - // if (parsedResponse.getTokenId() != null) { - // hub.setAppointmentAuthTokenId(parsedResponse.getTokenId()); - // hub.setAreaCode(parsedResponse.getAreaCode()); - // hubRepository.save(hub); - // log.info("Saved new authToken and areaCode for Hub."); - // success = true; - // return hub; - // } else { - // throw new RuntimeException("Login response is missing a valid tokenId for login to odessa system, please try again."); - // } - // } - // throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.ERROR_IN_GENERATING_NDG_TRY_AGAIN)); - // } catch (FeignException.Forbidden forbiddenException) { - // log.error("Failed to login to odessa due to some error"); - // - // // Extract raw response body - // String responseBody = forbiddenException.contentUTF8(); // Extract raw JSON response - // - // // Parse JSON to check for "PasswordExpired" - // try { - // ObjectMapper objectMapper = new ObjectMapper(); - // JsonNode rootNode = objectMapper.readTree(responseBody); - // JsonNode errorsNode = rootNode.path("errors"); - // - // if (errorsNode.isArray()) { - // for (JsonNode error : errorsNode) { - // // Check the main errorCode - // if (GepafinConstant.PASSWORD_EXPIRED.equals(error.path("errorCode").asText())) { - // application.setNdgStatus(GepafinConstant.NDG_FAILED); - // applicationRepository.save(application); - // throw new CustomValidationException(Status.FORBIDDEN, Translator.toLocale(GepafinConstant.PASSWORD_EXPIRED_LOGIN_TO_ODESSA)); - // } - // - // // Check inside "subErrors" - // JsonNode subErrorsNode = error.path("subErrors"); - // if (subErrorsNode.isArray()) { - // for (JsonNode subError : subErrorsNode) { - // if (GepafinConstant.PASSWORD_EXPIRED.equals(subError.path("errorCode").asText())) { - // application.setNdgStatus(GepafinConstant.NDG_FAILED); - // applicationRepository.save(application); - // throw new CustomValidationException(Status.FORBIDDEN, Translator.toLocale(GepafinConstant.PASSWORD_EXPIRED_LOGIN_TO_ODESSA)); - // } - // } - // } - // } - // } - // } catch (IOException e) { - // log.error("Error parsing JSON response: {}", e.getMessage()); - // } - // } catch (Exception e) { - // log.error("Failed to authenticate user on Odessa : {}", e.getMessage(), e); - // throw new RuntimeException("Authentication failed on Odessa. try again", e); - // } - // } - // return null; - // } - // - // - // private HubEntity authenticateAndSaveToken(HubEntity hub) { - // - // int maxRetries = 3; - // int attempt = 0; - // boolean success = false; - // while (attempt < maxRetries && !success) { - // attempt++; - // try { - // //code to generate token with payload having "iat" epoch timestamp and secret key with no expiry and send in below method call - // String authJwtToken = Utils.generateAuthTokenForLoginToOdessa(); - // log.info("Got the auth for login to odessa {}", authJwtToken); - // hub.setAuthToken(authJwtToken); - // hubRepository.save(hub); - // // Prepare the request body (adjust if necessary for login API) - // Map body = Collections.emptyMap(); - // // Perform login API call - // ResponseEntity responseLogin = appointmentApiService.loginWithOdessa(authJwtToken, source, context, user, password, body); - // - // // Handle successful login - // if (responseLogin.getStatusCode() == HttpStatus.OK) { - // log.info("Login successful to odessa. Parsing response."); - // String loginResponseJson = Utils.convertObjectToJson(responseLogin.getBody()); - // AppointmentLoginResponse parsedResponse = parseLoginResponse(loginResponseJson); - // - // // Validate and save token - // if (parsedResponse.getTokenId() != null) { - // hub.setAppointmentAuthTokenId(parsedResponse.getTokenId()); - // hub.setAreaCode(parsedResponse.getAreaCode()); - // hubRepository.save(hub); - // - // log.info("Saved new authToken and areaCode for Hub."); - // success = true; - // return hub; - // } else { - // throw new RuntimeException("Login response is missing a valid tokenId for login to odessa system, please try again."); - // } - // } - // // Handle non-OK response - // throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.ERROR_IN_GENERATING_NDG_TRY_AGAIN)); - // } catch (FeignException.Forbidden forbiddenException) { - // log.error("Failed to login to odessa due to some error occurred."); - // - // // Extract raw response body - // String responseBody = forbiddenException.contentUTF8(); // Extract raw JSON response - // - // // Parse JSON to check for "PasswordExpired" - // try { - // ObjectMapper objectMapper = new ObjectMapper(); - // JsonNode rootNode = objectMapper.readTree(responseBody); - // JsonNode errorsNode = rootNode.path("errors"); - // - // if (errorsNode.isArray()) { - // for (JsonNode error : errorsNode) { - // // Check the main errorCode - // if (GepafinConstant.PASSWORD_EXPIRED.equals(error.path("errorCode").asText())) { - // throw new CustomValidationException(Status.FORBIDDEN, Translator.toLocale(GepafinConstant.PASSWORD_EXPIRED_LOGIN_TO_ODESSA)); - // } - // - // // Check inside "subErrors" - // JsonNode subErrorsNode = error.path("subErrors"); - // if (subErrorsNode.isArray()) { - // for (JsonNode subError : subErrorsNode) { - // if (GepafinConstant.PASSWORD_EXPIRED.equals(subError.path("errorCode").asText())) { - // throw new CustomValidationException(Status.FORBIDDEN, Translator.toLocale(GepafinConstant.PASSWORD_EXPIRED_LOGIN_TO_ODESSA)); - // } - // } - // } - // } - // } - // } catch (IOException e) { - // log.error("Error parsing JSON response: {}", e.getMessage()); - // } - // } catch (Exception e) { - // log.error("Failed to authenticate user on Odessa : {}", e.getMessage(), e); - // throw new RuntimeException("Authentication failed on Odessa. try again", e); - // } - // } - // return null; - // } - - private void loginToOdessa(HubEntity hub, ApplicationEntity application) { - log.info("Starting login to Odessa. HubId: {}, ApplicationId: {}", hub.getId(), application.getId()); - performOdessaLogin(hub, application); - } - - private HubEntity authenticateAndSaveToken(HubEntity hub, ApplicationEntity application) { - - return performOdessaLogin(hub, application); - } - - private HubEntity performOdessaLogin(HubEntity hub, ApplicationEntity application) { - - int maxRetries = 3; - int attempt = 0; - while (attempt < maxRetries) { - attempt++; - try { - String authJwtToken = Utils.generateAuthTokenForLoginToOdessa(); - log.info("Got the auth for login to odessa {}", authJwtToken); - hub.setAuthToken(authJwtToken); - hubRepository.save(hub); - Map body = Collections.emptyMap(); - ResponseEntity responseLogin = appointmentApiService.loginWithOdessa(authJwtToken, source, context, user, password, body); - if (responseLogin.getStatusCode() == HttpStatus.OK) { - log.info("Login to Odessa successful. Parsing response. HubId: {}", hub.getId()); - String loginResponseJson = Utils.convertObjectToJson(responseLogin.getBody()); - AppointmentLoginResponse parsedResponse = parseLoginResponse(loginResponseJson); - - if (parsedResponse.getTokenId() != null) { - hub.setAppointmentAuthTokenId(parsedResponse.getTokenId()); - hub.setAreaCode(parsedResponse.getAreaCode()); - hubRepository.save(hub); - log.info("Saved new authToken and areaCode for Hub."); - return hub; - } else { - log.error("Login response from Odessa missing tokenId. HubId: {}", hub.getId()); - throw new RuntimeException("Login response is missing a valid tokenId for login to odessa system, please try again."); - } - } - throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.ERROR_IN_GENERATING_NDG_TRY_AGAIN)); - } catch (FeignException.Forbidden forbiddenException) { - log.error("Failed to login to odessa due to forbidden error."); - - CheckPasswordExpiredOrErrorInResponse(application, forbiddenException); - } catch (Exception e) { - log.error("Failed to authenticate user on Odessa (Attempt {}): {}", attempt, e.getMessage(), e); - } - } - throw new RuntimeException("Max retries exceeded. Failed to login to Odessa."); - } - private void CheckPasswordExpiredOrErrorInResponse(ApplicationEntity application, FeignException.Forbidden forbiddenException) { - - String responseBody = forbiddenException.contentUTF8(); - - try { - ObjectMapper objectMapper = new ObjectMapper(); - JsonNode rootNode = objectMapper.readTree(responseBody); - JsonNode errorsNode = rootNode.path("errors"); - - if (errorsNode.isArray()) { - for (JsonNode error : errorsNode) { - if (GepafinConstant.PASSWORD_EXPIRED.equals(error.path("errorCode").asText())) { - if (application != null) { - application.setNdgStatus(GepafinConstant.NDG_FAILED); - applicationRepository.save(application); - } - log.warn("Detected PASSWORD_EXPIRED error during Odessa login. ApplicationId: {}", application.getId()); - throw new CustomValidationException(Status.FORBIDDEN, Translator.toLocale(GepafinConstant.PASSWORD_EXPIRED_LOGIN_TO_ODESSA)); - } - - JsonNode subErrorsNode = error.path("subErrors"); - if (subErrorsNode.isArray()) { - for (JsonNode subError : subErrorsNode) { - if (GepafinConstant.PASSWORD_EXPIRED.equals(subError.path("errorCode").asText())) { - if (application != null) { - application.setNdgStatus(GepafinConstant.NDG_FAILED); - applicationRepository.save(application); - } - throw new CustomValidationException(Status.FORBIDDEN, Translator.toLocale(GepafinConstant.PASSWORD_EXPIRED_LOGIN_TO_ODESSA)); - } - } - } - } - } - } catch (IOException e) { - log.error("Unexpected exception during Odessa login.Error: {}",e.getMessage(), e); - } - } - - private void startAsyncNdgProcessing(Long applicationId) { - // If already polling for this applicationId, do nothing: - if (executorMap.containsKey(applicationId)) { - log.warn("Async processing already running for applicationId: {}", applicationId); - return; - } - - ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(runnable -> { - Thread t = new Thread(runnable); - t.setName("AsyncNdgProcessing-" + applicationId); - return t; - }); - executorMap.put(applicationId, scheduler); - - // Record the start time so we can stop after 2 hours: - long startTime = System.currentTimeMillis(); - long twoHoursMs = TimeUnit.HOURS.toMillis(2); - long fifteenMin = 15; // in MINUTES - - // We need a reference to cancel the scheduled task from inside itself when we're done: - AtomicReference> futureRef = new AtomicReference<>(); - - Runnable pollingTask = () -> { - try { - // 1) If 2 hours have already passed, mark as FAILED and shut down: - if (System.currentTimeMillis() - startTime > twoHoursMs) { - ApplicationEntity app = applicationService.validateApplication(applicationId); - log.warn("2-hour timeout reached for applicationId {}. Marking NDG_FAILED.", applicationId); - app.setNdgStatus(GepafinConstant.NDG_FAILED); - applicationRepository.save(app); - - futureRef.get().cancel(false); - shutdownScheduler(applicationId); - return; - } - - // 2) Otherwise, call processNdgGeneration once: - processNdgGeneration(applicationId); - - // 3) After return, check if NDG is now set or timed out. If so, cancel & shut down: - ApplicationEntity updated = applicationService.validateApplication(applicationId); - if (isNdgValid(updated.getNdg())) { - log.info("NDG found for applicationId {}. Shutting down scheduler.", applicationId); - futureRef.get().cancel(false); - shutdownScheduler(applicationId); - } else if (updated.getNdgStatus() != null && updated.getNdgStatus().equals(GepafinConstant.NDG_FAILED)) { - log.info("NDG status is NDG_FAILED for applicationId {}. Shutting down scheduler.", applicationId); - futureRef.get().cancel(false); - shutdownScheduler(applicationId); - } - // Otherwise: no NDG yet, not timed out → next run happens in 15 minutes automatically. - } catch (Exception ex) { - log.error("Unexpected error during scheduled polling for applicationId {}: {}", applicationId, ex.getMessage(), ex); - try { - ApplicationEntity checkApp = applicationService.validateApplication(applicationId); - if (System.currentTimeMillis() - startTime > twoHoursMs) { - log.warn("After exception, 2-hour window passed for applicationId {}. Marking NDG_FAILED.", applicationId); - checkApp.setNdgStatus(GepafinConstant.NDG_FAILED); - applicationRepository.save(checkApp); - - futureRef.get().cancel(false); - shutdownScheduler(applicationId); - } - } catch (Exception ignore) { - futureRef.get().cancel(false); - shutdownScheduler(applicationId); - } - } - }; - - // Schedule pollingTask: run now (delay=0), then every fifteen minutes: - ScheduledFuture future = scheduler.scheduleWithFixedDelay(pollingTask, 0, // initial delay = 0 min → run immediately - fifteenMin, // subsequent runs every 15 minutes - TimeUnit.MINUTES); - futureRef.set(future); - } - - private void shutdownScheduler(Long applicationId) { - - ScheduledExecutorService shed = executorMap.remove(applicationId); - if (shed != null) { - shed.shutdownNow(); - } - log.info("Scheduler shut down for applicationId: {}", applicationId); - } - - private void processNdgGeneration(Long applicationId) { - // Validate application, company, and hub - log.info("Starting NDG generation process for applicationId: {}", applicationId); - ApplicationEntity application = applicationService.validateApplication(applicationId); - CompanyEntity company = companyService.validateCompany(application.getCompanyId()); - HubEntity hub = hubRepository.findByHubId(application.getHubId()); - - if (!hub.getUniqueUuid().equals(defaultHubUuid)) { - log.info("Ndg cannot be created for another Hub, it is default for Gepafin."); - throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.NO_NDG_FOR_ANOTHER_HUB)); - } - - try { - // Authenticate and fetch token if required - if (hub.getAppointmentAuthTokenId() == null || hub.getAreaCode() == null) { - authenticateAndSaveToken(hub, application); - } - - String authorizationToken = getBearerToken(hub); - - // Try retrieving NDG by VAT number - AppointmentLoginResponse ndgResponse = retrieveNdgByVatNumber(company.getVatNumber(), authorizationToken, hub, application); - if (isNdgValid(ndgResponse.getNdg())) { - saveNdgAndIdVisura(application, company, ndgResponse.getNdg()); - log.info("NDG successfully generated for applicationId: {}", applicationId); - } else { - log.info("Polling for NDG for applicationId: {}", applicationId); - handleNdgPolling(application, company, hub, authorizationToken); - } - } catch (Exception e) { - log.error("Exception occurred during NDG generation. ApplicationId: {}, CompanyId: {}, HubId: {}, Error: {}", applicationId, company.getId(), hub.getId(), - e.getMessage(), e); - } - } - - private void handleNdgPolling(ApplicationEntity application, CompanyEntity company, HubEntity hub, String authorizationToken) { - - log.info("Starting singleâ€shot NDG polling attempt for applicationId: {}, CompanyId: {}, HubId: {}", application.getId(), company.getId(), hub.getId()); - - long startTime = System.currentTimeMillis(); - long twoHoursMs = TimeUnit.HOURS.toMillis(2); - - try { - // 1) If NDG was already populated (perhaps by another thread), skip polling. - if (application.getNdg() != null) { - log.info("NDG already present for applicationId {}. Exiting singleâ€shot polling.", application.getId()); - return; - } - - // 2) Attempt to create Visura (this may immediately return a valid NDG) - AppointmentLoginResponse visuraResponse = createVisura(company, authorizationToken, hub); - - // 2a) If createVisura gave us a valid NDG, persist & exit - String fetchedNdg = visuraResponse.getNdg(); - if (isNdgValid(fetchedNdg)) { - log.info("Valid NDG retrieved from createVisura(): {} | applicationId: {}", fetchedNdg, application.getId()); - - company.setNdg(fetchedNdg); - application.setNdg(fetchedNdg); - application.setNdgStatus(GepafinConstant.NDG_GENERATED); - application.setStatus(ApplicationStatusTypeEnum.NDG.getValue()); - application.setIdVisura(visuraResponse.getIdVisura()); - - companyRepository.save(company); - applicationRepository.save(application); - - ApplicationEvaluationEntity eval = applicationEvaluationService.validateApplicationEvaluation(application.getApplicationEvaluationId()); - Map placeholders = new HashMap<>(); - placeholders.put("{{call_name}}", application.getCall().getName()); - placeholders.put("{{protocol_number}}", String.valueOf(application.getProtocol().getProtocolNumber())); - notificationDao.sendNotificationToInstructor(placeholders, eval, NotificationTypeEnum.NDG_GENERATION); - notificationDao.sendNotificationToSuperUser(application, placeholders, NotificationTypeEnum.NDG_GENERATION); - - log.info("NDG saved successfully for applicationId: {}", application.getId()); - return; - } - - // 2b) If no immediate NDG, fetch the Visura list JSON and parse out NDG - String visuraListJson = getVisuraList(visuraResponse.getIdVisura(), authorizationToken, application, hub); - log.debug("Parsing NDG from VisuraList JSON for applicationId: {}", application.getId()); - String parsedNdg = parseNdgFromVisuraListResponse(visuraListJson); - - if (isNdgValid(parsedNdg)) { - log.info("Valid NDG parsed from VisuraList: {} | applicationId: {}", parsedNdg, application.getId()); - - company.setNdg(parsedNdg); - application.setNdg(parsedNdg); - application.setNdgStatus(GepafinConstant.NDG_GENERATED); - application.setStatus(ApplicationStatusTypeEnum.NDG.getValue()); - application.setIdVisura(visuraResponse.getIdVisura()); - - companyRepository.save(company); - applicationRepository.save(application); - - ApplicationEvaluationEntity eval = applicationEvaluationService.validateApplicationEvaluation(application.getApplicationEvaluationId()); - Map placeholders = new HashMap<>(); - placeholders.put("{{call_name}}", application.getCall().getName()); - placeholders.put("{{protocol_number}}", String.valueOf(application.getProtocol().getProtocolNumber())); - notificationDao.sendNotificationToInstructor(placeholders, eval, NotificationTypeEnum.NDG_GENERATION); - notificationDao.sendNotificationToSuperUser(application, placeholders, NotificationTypeEnum.NDG_GENERATION); - - log.info("NDG saved successfully for applicationId: {}", application.getId()); - return; - } - - // 3) Neither direct API nor parsing yielded a valid NDG. Check timeout. - if (System.currentTimeMillis() - startTime > twoHoursMs) { - log.warn("NDG polling timed out after 2 hours for applicationId: {}", application.getId()); - application.setNdgStatus(GepafinConstant.NDG_FAILED); - applicationRepository.save(application); - return; - } - - // 4) No NDG yet—just return. The scheduler will retry in 15 minutes. - log.info("No valid NDG yet for applicationId {}. Returning to scheduler—next attempt in 15 minutes.", application.getId()); - } catch (Exception e) { - log.error("Exception during NDG polling for applicationId: {}", application.getId(), e); - // If timeout after exception, mark NDG_FAILED - if (System.currentTimeMillis() - startTime > twoHoursMs) { - log.warn("Example: exiting singleâ€shot polling due to timeout after exception for applicationId: {}", application.getId()); - application.setNdgStatus(GepafinConstant.NDG_FAILED); - applicationRepository.save(application); - } else { - log.info("Exception occurred but not timed out for applicationId {}. Returning to scheduler for next attempt in 15 minutes.", application.getId()); - } - } - - log.info("NDG polling completed for applicationId: {}", application.getId()); - } - - private static String getBearerToken(HubEntity hub) { - - return "Bearer " + hub.getAppointmentAuthTokenId(); - } - - private boolean isNdgValid(String ndg) { - - return ndg != null && !ndg.isEmpty(); - } - - private void saveNdgAndIdVisura(ApplicationEntity application, CompanyEntity company, String ndg) { - - ApplicationEntity oldApplication = Utils.getClonedEntityForData(application); - application.setNdg(ndg); - application.setNdgStatus(GepafinConstant.NDG_GENERATED); - application.setStatus(ApplicationStatusTypeEnum.NDG.getValue()); - company.setNdg(ndg); - companyRepository.save(company); - applicationRepository.save(application); - loggingUtil.addVersionHistory( - VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldApplication).newData(application).build()); - ApplicationEvaluationEntity applicationEvaluationEntity = applicationEvaluationService.validateApplicationEvaluation(application.getApplicationEvaluationId()); -// Map placeHolders = notificationDao.sendNotificationToBeneficiary(application, NotificationTypeEnum.NDG_GENERATION); - Map placeHolders = new HashMap<>(); - placeHolders.put("{{call_name}}", application.getCall().getName()); - placeHolders.put("{{protocol_number}}", String.valueOf(application.getProtocol().getProtocolNumber())); - notificationDao.sendNotificationToInstructor(placeHolders, applicationEvaluationEntity, NotificationTypeEnum.NDG_GENERATION); - notificationDao.sendNotificationToSuperUser(application, placeHolders, NotificationTypeEnum.NDG_GENERATION); - log.info("NDG saved for applicationId: {}, {}", application.getId(), application.getNdg()); - } - - private String getVisuraList(String idVisura, String authorizationToken, ApplicationEntity application, HubEntity hub) { - - log.info("Initiating Visura list retrieval | ApplicationId: {}, HubId: {}, IdVisura: {}", application.getId(), hub.getId(), idVisura); - AppointmentVisuraListRequest visuraListRequest = new AppointmentVisuraListRequest(); - AppointmentVisuraListRequest.VisuraFilter filter = new AppointmentVisuraListRequest.VisuraFilter(); - filter.setIdVisura(idVisura); - visuraListRequest.setFilter(filter); - - try { - String requestJson = Utils.convertObjectToJson(visuraListRequest); - ResponseEntity response = appointmentApiService.getVisuraList(requestJson, authorizationToken); - return Utils.convertObjectToJson(response.getBody()); - } catch (FeignException.Forbidden forbiddenException) { - log.warn("403 Forbidden while fetching Visura list. Attempting token regeneration | ApplicationId: {}, HubId: {}", application.getId(), hub.getId()); - // Regenerate the token and retry - String newAuthorizationToken = regenerateTokenAndSave(hub, application); - return getVisuraList(idVisura, newAuthorizationToken, application, hub); - } catch (Exception e) { - log.error("Error while fetching Visura list | ApplicationId: {}, HubId: {}, Error: {}", application.getId(), hub.getId(), e.getMessage(), e); - throw new RuntimeException("Error fetching Ndg List", e); - } - } - - private AppointmentLoginResponse retrieveNdgByVatNumber(String vatNumber, String authorizationToken, HubEntity hub, ApplicationEntity application) { - - try { - log.info("Initiating NDG retrieval by VAT number | ApplicationId: {}, HubId: {}, VAT: {}", application.getId(), hub.getId(), vatNumber); - // Prepare the NDG request - AppointmentNdgRequest ndgRequest = getAppointmentNdgRequest(vatNumber); - // Call the API to retrieve NDG - ResponseEntity response = appointmentApiService.getNdgByVatNumber(ndgRequest, authorizationToken); - String responseJson = Utils.convertObjectToJson(response.getBody()); - // Parse and return the NDG response - return parseNdgResponse(responseJson); - } catch (FeignException.Forbidden forbiddenException) { - log.error("403 Forbidden during NDG retrieval | ApplicationId: {}, HubId: {}", application.getId(), hub.getId()); - logForbiddenError(); - // Regenerate the token and retry - String newAuthorizationToken = regenerateTokenAndSave(hub, application); - return retrieveNdgByVatNumber(vatNumber, newAuthorizationToken, hub, application); - } catch (Exception e) { - log.error("Error during NDG retrieval | ApplicationId: {}, HubId: {}, Message: {}", application.getId(), hub.getId(), e.getMessage(), e); - throw new RuntimeException("NDG retrieval failed.", e); - } - } - - private String regenerateTokenAndSave(HubEntity hub, ApplicationEntity application) { - - hub = authenticateAndSaveToken(hub, application); - return "Bearer " + hub.getAppointmentAuthTokenId(); - } - - private AppointmentLoginResponse createVisura(CompanyEntity company, String authorizationToken, HubEntity hub) { - - try { - String visuraRequest = getAppointmentVisuraRequest(company, hub.getAreaCode()); - ResponseEntity response = appointmentApiService.createVisura(visuraRequest, authorizationToken); - String responseJson = Utils.convertObjectToJson(response.getBody()); - return parseVisuraResponse(responseJson); - } catch (FeignException.Forbidden forbiddenException) { - logForbiddenError(); - // Regenerate the token and retry - String newAuthorizationToken = regenerateTokenAndSave(hub, null); - return createVisura(company, newAuthorizationToken, hub); - } catch (Exception e) { - log.error("Failed to create Visura for Ndg : {}", e.getMessage()); - throw new RuntimeException("Visura creation failed for Ndg.", e); - } - } - - private static void logForbiddenError() { - - log.error("403 Forbidden received while retrieving NDG. Regenerating token..."); - } - - private static AppointmentNdgRequest getAppointmentNdgRequest(String vatNumber) { - - log.info("Creating Appointment NDG Request | VAT Number: {}", vatNumber); - AppointmentNdgRequest request = new AppointmentNdgRequest(); - AppointmentNdgRequest.Filter filter = new AppointmentNdgRequest.Filter(); - filter.setPartitaIva(vatNumber); - - AppointmentNdgRequest.Pagination pagination = new AppointmentNdgRequest.Pagination(); - pagination.setTargetPage(AppointmentApiConstant.TARGET_PAGE_SIZE); - pagination.setRecordsPerPage(AppointmentApiConstant.RECORD_PER_PAGE_SIZE); - - request.setFilter(filter); - request.setPagination(pagination); - return request; - } - - private static String getAppointmentVisuraRequest(CompanyEntity company, String areaCode) { - - AppointmentVisuraRequest visuraRequest = new AppointmentVisuraRequest(); - AppointmentVisuraRequest.VisuraInput input = new AppointmentVisuraRequest.VisuraInput(); - input.setPartitaIva(company.getVatNumber()); - input.setCodiceFiscale(company.getCodiceFiscale()); - input.setCodArea(areaCode); - input.setVisuraMode(AppointmentApiConstant.VISURA_MODE); - input.setVisuraProvider(AppointmentApiConstant.VISURA_PROVIDER); - input.setCodAgente(AppointmentApiConstant.COD_AGENTE); - input.setAnagraficaLegame(AppointmentApiConstant.IS_ANAGRAFICA_LEGAME); - input.setCreaAnagrafica(AppointmentApiConstant.CREA_ANAGRAFICA); - input.setFromRating(AppointmentApiConstant.IS_FROM_RATING); - input.setSalvaDocumenti(AppointmentApiConstant.SALVA_DOCUMENTI); - input.setVisuraType(AppointmentApiConstant.VISURA_TYPE); - visuraRequest.setInput(input); - return Utils.convertObjectToJson(visuraRequest); - } - - private String parseNdgFromVisuraListResponse(String jsonResponse) { - - try { - ObjectMapper objectMapper = new ObjectMapper(); - JsonNode rootNode = objectMapper.readTree(jsonResponse); - JsonNode dataNode = rootNode.get(GepafinConstant.DATA_STRING); - - if (dataNode != null && dataNode.isArray() && dataNode.size() > 0) { - JsonNode firstEntry = dataNode.get(0); - JsonNode ndgClienteNode = firstEntry.get("ndgCliente"); - if (ndgClienteNode != null && ndgClienteNode.get("code") != null) { - String code = ndgClienteNode.get("code").asText(); - return normalizeNullValue(code); - } - } - log.warn("NDG not found in Visura List API response."); - return null; - } catch (Exception e) { - log.error("Failed to parse NDG from Visura List API response: {}", e.getMessage(), e); - throw new RuntimeException("Error parsing NDG from Visura List API response", e); - } - } - - public AppointmentLoginResponse parseLoginResponse(String jsonResponse) { - - try { - ObjectMapper objectMapper = new ObjectMapper(); - JsonNode rootNode = objectMapper.readTree(jsonResponse); - JsonNode dataNode = rootNode.get(GepafinConstant.DATA_STRING); - - if (dataNode != null) { - AppointmentLoginResponse response = new AppointmentLoginResponse(); - response.setTokenId(dataNode.get("tokenId").asText()); - JsonNode areasNode = dataNode.get("areas"); - if (areasNode != null && areasNode.isArray() && areasNode.size() > 0) { - response.setAreaCode(areasNode.get(0).get("code").asText()); - } - response.setCompanyId(dataNode.get("companyId").asLong()); - return response; - } else { - throw new RuntimeException("Invalid JSON structure: Missing 'data' node."); - } - } catch (Exception e) { - throw new RuntimeException("Failed to parse response from loginApi for odessa: " + e.getMessage(), e); - } - } - - public AppointmentLoginResponse parseVisuraResponse(String jsonResponse) { - - try { - // Log full raw JSON for debug purposes - log.info("Raw Visura JSON Response: {}", jsonResponse); - ObjectMapper objectMapper = new ObjectMapper(); - JsonNode rootNode = objectMapper.readTree(jsonResponse); - JsonNode dataNode = rootNode.get(GepafinConstant.DATA_STRING); - - if (dataNode != null && dataNode.isObject()) { - AppointmentLoginResponse response = new AppointmentLoginResponse(); - JsonNode idVisuraNode = dataNode.get(GepafinConstant.ID_VISURA_STRING); - JsonNode ndgNode = dataNode.get(GepafinConstant.NDG_STRING); - if (idVisuraNode == null || ndgNode == null) { - log.error("Missing expected fields in 'data' node. JSON: {}", dataNode); - } - response.setIdVisura(normalizeNullValue(idVisuraNode != null ? idVisuraNode.asText() : null)); - response.setNdg(normalizeNullValue(ndgNode != null ? ndgNode.asText() : null)); - return response; - } else { - System.err.println("Invalid JSON: 'data' node is missing or not an object."); - throw new RuntimeException("Invalid JSON structure: Missing or malformed 'data' node."); - } - } catch (Exception e) { - System.err.println("Exception while parsing Visura response: " + e.getMessage()); - throw new RuntimeException("Failed to parse response: " + e.getMessage(), e); - } - } - - public AppointmentLoginResponse parseNdgResponse(String jsonResponse) { - - try { - ObjectMapper objectMapper = new ObjectMapper(); - JsonNode rootNode = objectMapper.readTree(jsonResponse); - JsonNode dataArray = rootNode.get(GepafinConstant.DATA_STRING); - if (dataArray == null || !dataArray.isArray() || dataArray.isEmpty()) { - log.info("NDG data is empty or missing in the response."); - AppointmentLoginResponse emptyResponse = new AppointmentLoginResponse(); - emptyResponse.setNdg(null); - return emptyResponse; - } - JsonNode firstDataEntry = dataArray.get(0); - AppointmentLoginResponse response = new AppointmentLoginResponse(); - if (firstDataEntry.has(GepafinConstant.NDG_STRING)) { - response.setNdg(normalizeNullValue(firstDataEntry.get(GepafinConstant.NDG_STRING).asText())); - } - return response; - } catch (Exception e) { - log.error("Failed to parse response: {}", e.getMessage(), e); - throw new RuntimeException("Failed to parse NDG response.", e); - } - } - - private String normalizeNullValue(String value) { - - return (value == null || GepafinConstant.NULL_STRING.equalsIgnoreCase(value.trim())) ? null : value; - } - - public AppointmentCreationResponse createAppointment(Long applicationId, CreateAppointmentRequest createAppointmentRequest) { - // Validate the application - log.info("Starting appointment creation for applicationId: {}", applicationId); - ApplicationEntity application = applicationService.validateApplication(applicationId); - - AppointmentCreationResponse appointmentCreationResponse = new AppointmentCreationResponse(); - - ApplicationEntity oldApplicationData = Utils.getClonedEntityForData(application); - HubEntity hub = hubRepository.findByHubId(application.getHubId()); - - // Check hub UUID and enforce constraints - if (!hub.getUniqueUuid().equals(defaultHubUuid)) { - log.info("Appointment cannot be created for another Hub; default is required for Gepafin."); - throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.NO_APPOINTMENT_FOR_ANOTHER_HUB)); - } - - try { - // Pre-check conditions for appointment creation - if (application.getNdg() != null && !Objects.equals(application.getNdgStatus(), GepafinConstant.NDG_IN_PROGRESS) && application.getAppointmentId() != null) { - appointmentCreationResponse.setAppointmentId(application.getAppointmentId()); - throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.APPOINTMENT_ALREADY_CREATED)); - // return appointmentCreationResponse; - } - - if (application.getNdg() == null && Objects.equals(application.getNdgStatus(), GepafinConstant.NDG_IN_PROGRESS)) { - log.warn("NDG in progress but not available for applicationId: {}", applicationId); - throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.NDG_NOT_FOUND_FOR_APPLICATION)); - } - - // Generate authorization token and fetch template data - String authorizationToken = regenerateTokenAndSave(hub, application); - Long appointmentTemplateId = application.getCall().getAppointmentTemplateId(); - if (appointmentTemplateId == null) { - log.error("Missing appointment template ID for applicationId: {}", applicationId); - throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.APPOINTMENT_CANNOT_BE_CREATED)); - } - ResponseEntity response = appointmentApiService.getAppointmentTemplateForTemplateCreation(authorizationToken, appointmentTemplateId); - - if (response.getStatusCode() != HttpStatus.OK) { - log.error("Failed to retrieve appointment template for appointment creation. Status: {}", response.getStatusCode()); - throw new IllegalStateException("Failed to retrieve appointment template for appointment creation"); - } - - // Parse template data - String responseDataForTemplate = Utils.convertObjectToJson(response.getBody()); - AppointmentCreationRequest templateRichiestaData = parseTemplateResponseData(responseDataForTemplate); - - // Build the appointment request body - AppointmentCreationRequest appointmentCreationRequest = buildAppointmentCreationRequest(applicationId, createAppointmentRequest, appointmentTemplateId, - templateRichiestaData); - log.info("AppointmentCreationRequest : {}", appointmentCreationRequest); - String appointmentRequestBody = Utils.convertObjectToJson(appointmentCreationRequest); - - // Make API call to create the appointment - log.info("Context:{}, Authorization Token : {}, RequestBody : {}", context, authorizationToken, appointmentRequestBody); - ResponseEntity appointmentResponse = appointmentApiService.createAppointment(authorizationToken, context, appointmentRequestBody); - String appointmentId = extractAppointmentIdFromResponse(appointmentResponse); - - if (appointmentId == null) { - log.error("Failed to extract appointment ID from response for applicationId: {}", applicationId); - throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.APPOINTMENT_NOT_CREATED)); - } - // Update application with the appointment ID - application.setAppointmentId(appointmentId); - application.setStatus(ApplicationStatusTypeEnum.APPOINTMENT.getValue()); - applicationRepository.save(application); - - // Log version history - loggingUtil.addVersionHistory( - VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldApplicationData).newData(application).build()); - - appointmentCreationResponse.setAppointmentId(appointmentId); - return appointmentCreationResponse; - - } catch (FeignException.Forbidden forbiddenException) { - log.error("403 Forbidden received while retrieving template. Attempting to regenerate token and retry. Application ID: {}", applicationId); - regenerateTokenAndSave(hub, application); - return createAppointment(applicationId, createAppointmentRequest); - } - } - - private String extractAppointmentIdFromResponse(ResponseEntity appointmentResponse) { - - if (appointmentResponse.getBody() != null) { - log.info("Appointment API Response : {}", appointmentResponse.getBody()); - try { - Map responseBody = (Map) appointmentResponse.getBody(); - // 1. Try to get appointment ID from data.id - if (responseBody.containsKey(GepafinConstant.DATA_STRING)) { - Map data = (Map) responseBody.get(GepafinConstant.DATA_STRING); - if (data != null && data.containsKey(GepafinConstant.ID_STRING)) { - return data.get(GepafinConstant.ID_STRING).toString(); - } - } - // 2. If ID not present, check errors[0].cause.errorDescription - if (responseBody.containsKey(GepafinConstant.ERROR_STRING)) { - List> errors = (List>) responseBody.get(GepafinConstant.ERROR_STRING); - if (errors != null && !errors.isEmpty()) { - Map firstError = errors.get(0); - if (firstError.containsKey(GepafinConstant.CAUSE_STRING)) { - Map cause = (Map) firstError.get(GepafinConstant.CAUSE_STRING); - if (cause != null && cause.containsKey(GepafinConstant.ERROR_DESCRIPTION_STRING)) { - String errorDescription = cause.get(GepafinConstant.ERROR_DESCRIPTION_STRING).toString(); - log.warn("Appointment creation failed: {}", errorDescription); - } - } - } - } - } catch (Exception e) { - log.error("Error while extracting appointment ID or parsing error message", e); - } - } - return null; - } - - public AppointmentCreationRequest parseTemplateResponseData(String jsonResponse) { - - try { - - ObjectMapper objectMapper = new ObjectMapper(); - JsonNode rootNode = objectMapper.readTree(jsonResponse); - JsonNode richiesteClienteArray = rootNode.path(GepafinConstant.DATA_STRING).path(GepafinConstant.RICHIESTE_CLIENTE_STRING); - - // Initialize the result object - AppointmentCreationRequest appointmentCreationRequest = new AppointmentCreationRequest(); - AppointmentCreationRequest.Input input = new AppointmentCreationRequest.Input(); - List richiestaClienteList = new ArrayList<>(); - if (!richiesteClienteArray.isArray()) { - log.warn("richiesteCliente array is missing or not an array."); - return new AppointmentCreationRequest(); - } - for (JsonNode richiestaNode : richiesteClienteArray) { - if (richiestaNode.isNull()) - continue; - - AppointmentCreationRequest.RichiestaCliente richiestaCliente = new AppointmentCreationRequest.RichiestaCliente(); - JsonNode prodottoNode = richiestaNode.path(AppointmentApiConstant.PRODOTTO); - String prodottoCode = prodottoNode.path(AppointmentApiConstant.PRODOTTO_CODE).asText(); - - richiestaCliente.setCodProdotto(prodottoCode); - richiestaCliente.setIdMotivazione(getIntValue(richiestaNode)); - richiestaCliente.setCodAbi(getTextValue(richiestaNode, AppointmentApiConstant.COD_ABI)); - richiestaCliente.setCodCab(getTextValue(richiestaNode, AppointmentApiConstant.COD_CAB)); - richiestaCliente.setIdNota(getTextValue(richiestaNode, AppointmentApiConstant.ID_NOTA)); - richiestaCliente.setImportoAgevolato(getTextValue(richiestaNode, AppointmentApiConstant.IMPORTO_AGEVOLATO)); - richiestaCliente.setImportoMedioLungoTermine(getTextValue(richiestaNode, AppointmentApiConstant.IMPORTO_MEDIOLUNGO_TERMINE)); - richiestaCliente.setCodTipoProdotto(getTextValue(richiestaNode, AppointmentApiConstant.COD_TIPO_PRODOTTO)); - richiestaCliente.setCodCategoriaProdotto(getTextValue(richiestaNode, AppointmentApiConstant.COD_CATEGORIA_PRODOTTO)); - richiestaCliente.setCodFormaTecnica(getTextValue(richiestaNode, AppointmentApiConstant.COD_FORMATECNICA)); - richiestaCliente.setCodOperazione(getTextValue(richiestaNode, AppointmentApiConstant.COD_OPERAZIONE)); - - richiestaClienteList.add(richiestaCliente); - } - - input.setRichiestaCliente(richiestaClienteList); - appointmentCreationRequest.setInput(input); - - return appointmentCreationRequest; - - } catch (JsonProcessingException e) { - log.error("JSON processing error: {}", e.getMessage(), e); - throw new IllegalStateException("Invalid JSON structure in template response", e); - } - } - - private String getTextValue(JsonNode node, String fieldName) { - - return node.path(fieldName).isTextual() ? node.path(fieldName).asText() : null; - } - - private int getIntValue(JsonNode node) { - - return node.path(AppointmentApiConstant.MOTIVAZIONE).path(AppointmentApiConstant.MOTIVAZIONE_ID).asInt(); - } - - public AppointmentCreationRequest buildAppointmentCreationRequest(Long applicationId, CreateAppointmentRequest createAppointmentRequest, Long areaCode, - AppointmentCreationRequest templateRichiestaData) { - - ApplicationEntity application = applicationService.validateApplication(applicationId); - CreateAppointmentRequest.Nota nota = createAppointmentRequest.getNota(); - - AppointmentCreationRequest appointmentCreationRequest = new AppointmentCreationRequest(); - AppointmentCreationRequest.Input input = new AppointmentCreationRequest.Input(); - - // Set Input Fields - input.setId(areaCode); - input.setNdg(application.getNdg()); - - // Populate richiestaCliente from template data - List richiestaClienteList = new ArrayList<>(); - for (AppointmentCreationRequest.RichiestaCliente templateRichiesta : templateRichiestaData.getInput().getRichiestaCliente()) { - AppointmentCreationRequest.RichiestaCliente richiestaCliente = new AppointmentCreationRequest.RichiestaCliente(); - BeanUtils.copyProperties(templateRichiesta, richiestaCliente); - - // Add specific `nota` - AppointmentCreationRequest.Nota requestNota = new AppointmentCreationRequest.Nota(); - requestNota.setTitolo(nota.getTitolo()); - requestNota.setTesto(nota.getTesto()); - richiestaCliente.setNota(requestNota); - richiestaCliente.setDurataMesiFinanziamento(createAppointmentRequest.getDurataMesiFinanziamento()); - richiestaCliente.setImportoBreveTermine(createAppointmentRequest.getImportoBreveTermine()); - richiestaClienteList.add(richiestaCliente); - } - - input.setRichiestaCliente(richiestaClienteList); - appointmentCreationRequest.setInput(input); - return appointmentCreationRequest; - } - - public DocumentUploadResponse uploadDocumentToExternalSystem(Long documentId, UploadDocToExternalSystemRequest docToExternalSystemRequest) { - log.info("Initiating upload to external system for documentId: {}", documentId); - // Check if the document is already being processed - DocumentEntity systemDoc = documentDao.validateDocument(documentId); - - ApplicationEntity application = null; - - if (systemDoc != null) { - DocumentSourceTypeEnum sourceType = DocumentSourceTypeEnum.valueOf(systemDoc.getSource()); - - switch (sourceType) { - case APPLICATION: - application = applicationDao.validateApplication(systemDoc.getSourceId()); - break; - case AMENDMENT: - ApplicationAmendmentRequestEntity applicationAmendmentEntity = applicationAmendmentRequestDao.validateApplicationAmendmentRequest(systemDoc.getSourceId()); - application = applicationDao.validateApplication(applicationAmendmentEntity.getApplicationId()); - break; - case EVALUATION: - ApplicationEvaluationEntity applicationEvaluationEntity = applicationEvaluationDao.validateApplicationEvaluation(systemDoc.getSourceId()); - application = applicationDao.validateApplication(applicationEvaluationEntity.getApplicationId()); - break; - - case CALL: - break; - - default: - log.warn("Unhandled document source type: {}", sourceType); - break; - } - } - - Claims claims = tokenProvider.getClaimsFromToken(tokenProvider.extractTokenFromRequest(request)); - Long hubId = Utils.extractHubIdFromPayload(claims.getSubject()); - - // Authenticate the hub before proceeding - HubEntity hub = hubRepository.findByHubId(hubId); - authenticateAndSaveToken(hub, application); - if (systemDoc != null && systemDoc.getDocumentAttachmentId() != null) { - // If the documentAttachmentId is already set, return the response - log.info("Document already uploaded with documentAttachmentId: {}", systemDoc.getDocumentAttachmentId()); - DocumentUploadResponse response = new DocumentUploadResponse(); - response.setDocumentAttachmentId(systemDoc.getDocumentAttachmentId()); - return response; - } - // Check if a thread is already running for this document upload - if (threadForDocumentMap.containsKey(documentId)) { - log.warn("Document upload already running for documentId: {}", documentId); - throw new CustomValidationException(Status.SUCCESS, Translator.toLocale(GepafinConstant.DOCUMENT_UPLOADING_IN_PROGRESS)); - } - // Start the upload process in the background - ExecutorService executor = Executors.newSingleThreadExecutor(runnable -> { - Thread thread = new Thread(runnable); - thread.setName(GepafinConstant.ASYNC_DOCUMENT_UPLOAD_NAME + documentId); - return thread; - }); - threadForDocumentMap.put(documentId, executor); - - ApplicationEntity finalApplication = application; - executor.submit(() -> { - threadLocalHubId.set(hubId); - try { - log.info("Starting async document upload for documentId: {}", documentId); - uploadDocumentToExternalSystemSync(documentId, docToExternalSystemRequest, finalApplication); - } catch (Exception e) { - log.error("Error in async document upload for documentId: {}", documentId, e); - } finally { - // Cleanup resources - ExecutorService executorToShutdown = threadForDocumentMap.remove(documentId); - if (executorToShutdown != null) { - executorToShutdown.shutdown(); - threadLocalHubId.remove(); - } - log.info("Async document upload completed for documentId: {}", documentId); - } - }); - return null; - } - - private void uploadDocumentToExternalSystemSync(Long documentId, UploadDocToExternalSystemRequest docToExternalSystemRequest, ApplicationEntity application) { - log.info("Starting sync document upload for documentId: {}", documentId); - // Synchronous upload logic - DocumentEntity systemDoc = documentDao.validateDocument(documentId); - - Long hubId = threadLocalHubId.get(); - HubEntity hub = hubRepository.findByHubId(hubId); - - if (!hub.getUniqueUuid().equals(defaultHubUuid)) { - log.info("Document cannot be uploaded for another Hub, it is default for Gepafin."); - throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.NO_DOCUMENT_UPLOAD_FOR_ANOTHER_HUB)); - } - - log.info("Got Document in system: {}", systemDoc); - String oldUrl = systemDoc.getFilePath(); - String authorizationToken = getBearerToken(hub); - - try { - File localFile = downloadFileFromS3(oldUrl); - MultipartFile multipartFile = convertFileToMultipartFile(localFile); - - UploadDocToExternalSystemRequest externalSystemRequest = new UploadDocToExternalSystemRequest(); - externalSystemRequest.setInput(getUploadDocumentInput(docToExternalSystemRequest)); - - String uploadDocRequest = Utils.convertObjectToJson(externalSystemRequest); - ResponseEntity uploadedDocumentData = appointmentApiService.uploadDocumentToExternalSystemForAppointment(authorizationToken, context, uploadDocRequest, - multipartFile); - - String responseData = Utils.convertObjectToJson(uploadedDocumentData.getBody()); - DocumentUploadResponse parsedResponse = parseDocumentUploadResponse(responseData); - - if (parsedResponse == null) { - log.error("Upload failed: parsed response is null for documentId: {}", documentId); - throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.ERROR_UPLOADING_DOCUMENT)); - } - - // Save the documentAttachmentId to the database - systemDoc.setDocumentAttachmentId(parsedResponse.getDocumentAttachmentId()); - documentRepository.save(systemDoc); - - log.info("Document uploaded successfully to external system: {}", parsedResponse); - } catch (FeignException.Forbidden forbiddenException) { - log.error("403 Forbidden from external system during upload for documentId: {}. Retrying with new token...", documentId); - regenerateTokenAndSave(hub, application); - uploadDocumentToExternalSystemSync(documentId, docToExternalSystemRequest, application); - } catch (Exception e) { - log.error("Exception during document upload: {}", e.getMessage(), e); - throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.EXTERNAL_DOCUMENT_UPLOAD_FAILURE_MSG)); - } - } - - private UploadDocToExternalSystemRequest.Input getUploadDocumentInput(UploadDocToExternalSystemRequest docToExternalSystemRequest) { - - UploadDocToExternalSystemRequest.Input input = new UploadDocToExternalSystemRequest.Input(); - input.setIdTipoProtocollo(docToExternalSystemRequest.getInput().getIdTipoProtocollo()); - input.setIdClassificazione(docToExternalSystemRequest.getInput().getIdClassificazione()); - input.setFlagDaFirmare(docToExternalSystemRequest.getInput().getFlagDaFirmare()); - input.setDescrizione(docToExternalSystemRequest.getInput().getDescrizione()); - - UploadDocToExternalSystemRequest.Input.Attributes attributes = new UploadDocToExternalSystemRequest.Input.Attributes(); - attributes.setNdg(docToExternalSystemRequest.getInput().getAttributes().getNdg()); - attributes.setEmail(docToExternalSystemRequest.getInput().getAttributes().getEmail()); - - input.setAttributes(attributes); - return input; - } - - public static MultipartFile convertFileToMultipartFile(File file) throws IOException { - - FileInputStream input = new FileInputStream(file); - return new MockMultipartFile(file.getName(), file.getName(), MediaType.APPLICATION_OCTET_STREAM_VALUE, input); - } - - private File downloadFileFromS3(String fileUrl) throws Exception { - - String key = amazonS3Service.extractS3KeyFromUrl(fileUrl); - String fileName = extractFileName(key); - String folderPath = key.substring(0, key.lastIndexOf("/")); - File localFile = new File(GepafinConstant.TEMP_FILE_PATH + fileName); - - try (InputStream s3Stream = amazonS3Service.getFile(folderPath, key); FileOutputStream outputStream = new FileOutputStream(localFile)) { - s3Stream.transferTo(outputStream); - } - - log.info("Downloaded file from old S3 bucket: {}", key); - return localFile; - } - - private String extractFileName(String filePath) { - - String[] parts = filePath.split("/"); - return parts[parts.length - 1]; - - } - - public DocumentUploadResponse parseDocumentUploadResponse(String jsonResponse) { - - try { - ObjectMapper objectMapper = new ObjectMapper(); - JsonNode rootNode = objectMapper.readTree(jsonResponse); - - // Navigate to the "data" node - JsonNode dataNode = rootNode.get(GepafinConstant.DATA_STRING); - if (dataNode != null) { - DocumentUploadResponse response = new DocumentUploadResponse(); - - // Extract "documentAttachmentId" - JsonNode documentAttachmentIdNode = dataNode.get(GepafinConstant.DOCUMENT_ATTACHMENT_ID_STRING); - if (documentAttachmentIdNode != null) { - response.setDocumentAttachmentId(documentAttachmentIdNode.asText()); - } else { - throw new RuntimeException("Invalid JSON structure: Missing 'documentAttachmentId' node."); - } - - return response; - } else { - return null; - } - } catch (Exception e) { - throw new RuntimeException("Failed to parse response: " + e.getMessage(), e); - } - } +package net.gepafin.tendermanagement.dao; + +import com.amazonaws.services.s3.AmazonS3Client; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import feign.FeignException; +import io.jsonwebtoken.Claims; +import jakarta.servlet.http.HttpServletRequest; +import lombok.extern.slf4j.Slf4j; +import net.gepafin.tendermanagement.config.Translator; +import net.gepafin.tendermanagement.config.jwt.TokenProvider; +import net.gepafin.tendermanagement.constants.AppointmentApiConstant; +import net.gepafin.tendermanagement.constants.GepafinConstant; +import net.gepafin.tendermanagement.entities.ApplicationAmendmentRequestEntity; +import net.gepafin.tendermanagement.entities.ApplicationEntity; +import net.gepafin.tendermanagement.entities.ApplicationEvaluationEntity; +import net.gepafin.tendermanagement.entities.CompanyEntity; +import net.gepafin.tendermanagement.entities.DocumentEntity; +import net.gepafin.tendermanagement.entities.HubEntity; +import net.gepafin.tendermanagement.enums.*; +import net.gepafin.tendermanagement.model.request.AppointmentCreationRequest; +import net.gepafin.tendermanagement.model.request.AppointmentNdgRequest; +import net.gepafin.tendermanagement.model.request.AppointmentVisuraListRequest; +import net.gepafin.tendermanagement.model.request.AppointmentVisuraRequest; +import net.gepafin.tendermanagement.model.request.CreateAppointmentRequest; +import net.gepafin.tendermanagement.model.request.UploadDocToExternalSystemRequest; +import net.gepafin.tendermanagement.model.request.VersionHistoryRequest; +import net.gepafin.tendermanagement.model.response.AppointmentCreationResponse; +import net.gepafin.tendermanagement.model.response.AppointmentLoginResponse; +import net.gepafin.tendermanagement.model.response.DocumentUploadResponse; +import net.gepafin.tendermanagement.model.response.NdgResponse; +import net.gepafin.tendermanagement.repositories.ApplicationRepository; +import net.gepafin.tendermanagement.repositories.CompanyRepository; +import net.gepafin.tendermanagement.repositories.DocumentRepository; +import net.gepafin.tendermanagement.repositories.HubRepository; +import net.gepafin.tendermanagement.repositories.UserRepository; +import net.gepafin.tendermanagement.service.AmazonS3Service; +import net.gepafin.tendermanagement.service.ApplicationService; +import net.gepafin.tendermanagement.service.CompanyService; +import net.gepafin.tendermanagement.service.feignClient.AppointmentApiService; +import net.gepafin.tendermanagement.service.impl.ApplicationEvaluationServiceImpl; +import net.gepafin.tendermanagement.util.LoggingUtil; +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.springframework.beans.BeanUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.stereotype.Component; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; +import org.springframework.web.multipart.MultipartFile; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.ScheduledExecutorService; + +@Slf4j +@Component +public class AppointmentDao { + + @Value("${appointment.portal.user}") + private String user; + + @Value("${appointment.portal.password}") + private String password; + + @Value("${appointment.portal.source}") + private String source; + + @Value("${appointment.portal.context}") + private String context; + + @Value("${default.hub.uuid}") + private String defaultHubUuid; + + @Value("${aws.s3.url}") + private String s3Url; + + @Value("${aws.s3.bucket.name}") + private String OLD_BUCKET; + + @Autowired + private HubRepository hubRepository; + + @Autowired + private AppointmentApiService appointmentApiService; + + @Autowired + private ApplicationService applicationService; + + @Autowired + private CompanyService companyService; + + @Autowired + private ApplicationRepository applicationRepository; + + @Autowired + private CompanyRepository companyRepository; + + @Autowired + private DocumentDao documentDao; + + @Autowired + private AmazonS3Client s3Client; + + @Autowired + private DocumentRepository documentRepository; + + @Autowired + private HttpServletRequest request; + + @Autowired + private LoggingUtil loggingUtil; + + @Autowired + private TokenProvider tokenProvider; + + @Autowired + private NotificationDao notificationDao; + + @Autowired + private UserRepository userRepository; + + @Autowired + private ApplicationEvaluationServiceImpl applicationEvaluationService; + + @Autowired + private AmazonS3Service amazonS3Service; + + @Autowired + private ApplicationDao applicationDao; + + @Autowired + private ApplicationAmendmentRequestDao applicationAmendmentRequestDao; + + @Autowired + private ApplicationEvaluationDao applicationEvaluationDao; + + private final Map executorMap = new ConcurrentHashMap<>(); + + + private final ConcurrentHashMap threadForDocumentMap = new ConcurrentHashMap<>(); + + private static final ThreadLocal threadLocalHubId = new ThreadLocal<>(); + + public NdgResponse checkNdgForAppointment(Long applicationId) { + log.info("Starting NDG check for appointment. applicationId: {}", applicationId); + ApplicationEntity application = applicationService.validateApplication(applicationId); + ApplicationEntity oldApplication = Utils.getClonedEntityForData(application); + + NdgResponse ndgResponse = new NdgResponse(); + if (application.getNdgStatus() != null && application.getNdgStatus().equalsIgnoreCase(GepafinConstant.NDG_IN_PROGRESS)) { + log.warn("NDG generation already in progress. applicationId: {}", applicationId); + throw new CustomValidationException(Status.SUCCESS, Translator.toLocale(GepafinConstant.NDG_GENERATION_IS_IN_PROGRESS)); + } + + if (application.getNdgStatus() != null && application.getNdgStatus().equalsIgnoreCase(GepafinConstant.NDG_GENERATED) && application.getNdg() != null) { + ndgResponse.setNdg(application.getNdg()); + return ndgResponse; + } + + // Update application status + log.info("Updating NDG status to IN_PROGRESS. applicationId: {}", applicationId); + application.setNdgStatus(GepafinConstant.NDG_IN_PROGRESS); + applicationRepository.save(application); + + loggingUtil.addVersionHistory( + VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldApplication).newData(application).build()); + + + // Start async processing + HubEntity hub = hubRepository.findByHubId(application.getHubId()); + loginToOdessa(hub, application); + startAsyncNdgProcessing(applicationId); + log.info("NDG check initiation completed. applicationId: {}", applicationId); + return ndgResponse; + } + + // private HubEntity loginToOdessa(HubEntity hub, ApplicationEntity application) { + // + // int maxRetries = 3; + // int attempt = 0; + // boolean success = false; + // while (attempt < maxRetries && !success) { + // attempt++; + // try { + // //code to generate token with payload having "iat" epoch timestamp and secret key with no expiry and send in below method call + // String authJwtToken = Utils.generateAuthTokenForLoginToOdessa(); + // log.info("Got the auth for login to odessa {}", authJwtToken); + // hub.setAuthToken(authJwtToken); + // hubRepository.save(hub); + // Map body = Collections.emptyMap(); + // ResponseEntity responseLogin = appointmentApiService.loginWithOdessa(authJwtToken, source, context, user, password, body); + // if (responseLogin.getStatusCode() == HttpStatus.OK) { + // log.info("Login successful to odessa. Parsing response."); + // String loginResponseJson = Utils.convertObjectToJson(responseLogin.getBody()); + // AppointmentLoginResponse parsedResponse = parseLoginResponse(loginResponseJson); + // + // // Validate and save token + // if (parsedResponse.getTokenId() != null) { + // hub.setAppointmentAuthTokenId(parsedResponse.getTokenId()); + // hub.setAreaCode(parsedResponse.getAreaCode()); + // hubRepository.save(hub); + // log.info("Saved new authToken and areaCode for Hub."); + // success = true; + // return hub; + // } else { + // throw new RuntimeException("Login response is missing a valid tokenId for login to odessa system, please try again."); + // } + // } + // throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.ERROR_IN_GENERATING_NDG_TRY_AGAIN)); + // } catch (FeignException.Forbidden forbiddenException) { + // log.error("Failed to login to odessa due to some error"); + // + // // Extract raw response body + // String responseBody = forbiddenException.contentUTF8(); // Extract raw JSON response + // + // // Parse JSON to check for "PasswordExpired" + // try { + // ObjectMapper objectMapper = new ObjectMapper(); + // JsonNode rootNode = objectMapper.readTree(responseBody); + // JsonNode errorsNode = rootNode.path("errors"); + // + // if (errorsNode.isArray()) { + // for (JsonNode error : errorsNode) { + // // Check the main errorCode + // if (GepafinConstant.PASSWORD_EXPIRED.equals(error.path("errorCode").asText())) { + // application.setNdgStatus(GepafinConstant.NDG_FAILED); + // applicationRepository.save(application); + // throw new CustomValidationException(Status.FORBIDDEN, Translator.toLocale(GepafinConstant.PASSWORD_EXPIRED_LOGIN_TO_ODESSA)); + // } + // + // // Check inside "subErrors" + // JsonNode subErrorsNode = error.path("subErrors"); + // if (subErrorsNode.isArray()) { + // for (JsonNode subError : subErrorsNode) { + // if (GepafinConstant.PASSWORD_EXPIRED.equals(subError.path("errorCode").asText())) { + // application.setNdgStatus(GepafinConstant.NDG_FAILED); + // applicationRepository.save(application); + // throw new CustomValidationException(Status.FORBIDDEN, Translator.toLocale(GepafinConstant.PASSWORD_EXPIRED_LOGIN_TO_ODESSA)); + // } + // } + // } + // } + // } + // } catch (IOException e) { + // log.error("Error parsing JSON response: {}", e.getMessage()); + // } + // } catch (Exception e) { + // log.error("Failed to authenticate user on Odessa : {}", e.getMessage(), e); + // throw new RuntimeException("Authentication failed on Odessa. try again", e); + // } + // } + // return null; + // } + // + // + // private HubEntity authenticateAndSaveToken(HubEntity hub) { + // + // int maxRetries = 3; + // int attempt = 0; + // boolean success = false; + // while (attempt < maxRetries && !success) { + // attempt++; + // try { + // //code to generate token with payload having "iat" epoch timestamp and secret key with no expiry and send in below method call + // String authJwtToken = Utils.generateAuthTokenForLoginToOdessa(); + // log.info("Got the auth for login to odessa {}", authJwtToken); + // hub.setAuthToken(authJwtToken); + // hubRepository.save(hub); + // // Prepare the request body (adjust if necessary for login API) + // Map body = Collections.emptyMap(); + // // Perform login API call + // ResponseEntity responseLogin = appointmentApiService.loginWithOdessa(authJwtToken, source, context, user, password, body); + // + // // Handle successful login + // if (responseLogin.getStatusCode() == HttpStatus.OK) { + // log.info("Login successful to odessa. Parsing response."); + // String loginResponseJson = Utils.convertObjectToJson(responseLogin.getBody()); + // AppointmentLoginResponse parsedResponse = parseLoginResponse(loginResponseJson); + // + // // Validate and save token + // if (parsedResponse.getTokenId() != null) { + // hub.setAppointmentAuthTokenId(parsedResponse.getTokenId()); + // hub.setAreaCode(parsedResponse.getAreaCode()); + // hubRepository.save(hub); + // + // log.info("Saved new authToken and areaCode for Hub."); + // success = true; + // return hub; + // } else { + // throw new RuntimeException("Login response is missing a valid tokenId for login to odessa system, please try again."); + // } + // } + // // Handle non-OK response + // throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.ERROR_IN_GENERATING_NDG_TRY_AGAIN)); + // } catch (FeignException.Forbidden forbiddenException) { + // log.error("Failed to login to odessa due to some error occurred."); + // + // // Extract raw response body + // String responseBody = forbiddenException.contentUTF8(); // Extract raw JSON response + // + // // Parse JSON to check for "PasswordExpired" + // try { + // ObjectMapper objectMapper = new ObjectMapper(); + // JsonNode rootNode = objectMapper.readTree(responseBody); + // JsonNode errorsNode = rootNode.path("errors"); + // + // if (errorsNode.isArray()) { + // for (JsonNode error : errorsNode) { + // // Check the main errorCode + // if (GepafinConstant.PASSWORD_EXPIRED.equals(error.path("errorCode").asText())) { + // throw new CustomValidationException(Status.FORBIDDEN, Translator.toLocale(GepafinConstant.PASSWORD_EXPIRED_LOGIN_TO_ODESSA)); + // } + // + // // Check inside "subErrors" + // JsonNode subErrorsNode = error.path("subErrors"); + // if (subErrorsNode.isArray()) { + // for (JsonNode subError : subErrorsNode) { + // if (GepafinConstant.PASSWORD_EXPIRED.equals(subError.path("errorCode").asText())) { + // throw new CustomValidationException(Status.FORBIDDEN, Translator.toLocale(GepafinConstant.PASSWORD_EXPIRED_LOGIN_TO_ODESSA)); + // } + // } + // } + // } + // } + // } catch (IOException e) { + // log.error("Error parsing JSON response: {}", e.getMessage()); + // } + // } catch (Exception e) { + // log.error("Failed to authenticate user on Odessa : {}", e.getMessage(), e); + // throw new RuntimeException("Authentication failed on Odessa. try again", e); + // } + // } + // return null; + // } + + private void loginToOdessa(HubEntity hub, ApplicationEntity application) { + log.info("Starting login to Odessa. HubId: {}, ApplicationId: {}", hub.getId(), application.getId()); + performOdessaLogin(hub, application); + } + + private HubEntity authenticateAndSaveToken(HubEntity hub, ApplicationEntity application) { + + return performOdessaLogin(hub, application); + } + + private HubEntity performOdessaLogin(HubEntity hub, ApplicationEntity application) { + + int maxRetries = 3; + int attempt = 0; + while (attempt < maxRetries) { + attempt++; + try { + String authJwtToken = Utils.generateAuthTokenForLoginToOdessa(); + log.info("Got the auth for login to odessa {}", authJwtToken); + hub.setAuthToken(authJwtToken); + hubRepository.save(hub); + Map body = Collections.emptyMap(); + ResponseEntity responseLogin = appointmentApiService.loginWithOdessa(authJwtToken, source, context, user, password, body); + if (responseLogin.getStatusCode() == HttpStatus.OK) { + log.info("Login to Odessa successful. Parsing response. HubId: {}", hub.getId()); + String loginResponseJson = Utils.convertObjectToJson(responseLogin.getBody()); + AppointmentLoginResponse parsedResponse = parseLoginResponse(loginResponseJson); + + if (parsedResponse.getTokenId() != null) { + hub.setAppointmentAuthTokenId(parsedResponse.getTokenId()); + hub.setAreaCode(parsedResponse.getAreaCode()); + hubRepository.save(hub); + log.info("Saved new authToken and areaCode for Hub."); + return hub; + } else { + log.error("Login response from Odessa missing tokenId. HubId: {}", hub.getId()); + throw new RuntimeException("Login response is missing a valid tokenId for login to odessa system, please try again."); + } + } + throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.ERROR_IN_GENERATING_NDG_TRY_AGAIN)); + } catch (FeignException.Forbidden forbiddenException) { + log.error("Failed to login to odessa due to forbidden error."); + + CheckPasswordExpiredOrErrorInResponse(application, forbiddenException); + } catch (Exception e) { + log.error("Failed to authenticate user on Odessa (Attempt {}): {}", attempt, e.getMessage(), e); + } + } + throw new RuntimeException("Max retries exceeded. Failed to login to Odessa."); + } + private void CheckPasswordExpiredOrErrorInResponse(ApplicationEntity application, FeignException.Forbidden forbiddenException) { + + String responseBody = forbiddenException.contentUTF8(); + + try { + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode rootNode = objectMapper.readTree(responseBody); + JsonNode errorsNode = rootNode.path("errors"); + + if (errorsNode.isArray()) { + for (JsonNode error : errorsNode) { + if (GepafinConstant.PASSWORD_EXPIRED.equals(error.path("errorCode").asText())) { + if (application != null) { + application.setNdgStatus(GepafinConstant.NDG_FAILED); + applicationRepository.save(application); + } + log.warn("Detected PASSWORD_EXPIRED error during Odessa login. ApplicationId: {}", application.getId()); + throw new CustomValidationException(Status.FORBIDDEN, Translator.toLocale(GepafinConstant.PASSWORD_EXPIRED_LOGIN_TO_ODESSA)); + } + + JsonNode subErrorsNode = error.path("subErrors"); + if (subErrorsNode.isArray()) { + for (JsonNode subError : subErrorsNode) { + if (GepafinConstant.PASSWORD_EXPIRED.equals(subError.path("errorCode").asText())) { + if (application != null) { + application.setNdgStatus(GepafinConstant.NDG_FAILED); + applicationRepository.save(application); + } + throw new CustomValidationException(Status.FORBIDDEN, Translator.toLocale(GepafinConstant.PASSWORD_EXPIRED_LOGIN_TO_ODESSA)); + } + } + } + } + } + } catch (IOException e) { + log.error("Unexpected exception during Odessa login.Error: {}",e.getMessage(), e); + } + } + + private void startAsyncNdgProcessing(Long applicationId) { + // If already polling for this applicationId, do nothing: + if (executorMap.containsKey(applicationId)) { + log.warn("Async processing already running for applicationId: {}", applicationId); + return; + } + ServletRequestAttributes requestAttributes = new ServletRequestAttributes(request); + + ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(runnable -> { + Thread t = new Thread(runnable); + t.setName("AsyncNdgProcessing-" + applicationId); + return t; + }); + executorMap.put(applicationId, scheduler); + + // Record the start time so we can stop after 2 hours: + long startTime = System.currentTimeMillis(); + long twoHoursMs = TimeUnit.HOURS.toMillis(2); + long fifteenMin = 15; // in MINUTES + + // We need a reference to cancel the scheduled task from inside itself when we're done: + AtomicReference> futureRef = new AtomicReference<>(); + + Runnable pollingTask = () -> { + RequestContextHolder.setRequestAttributes(requestAttributes, true); + Utils.setHttpServletRequestForThread(request,HttpMethodEnum.POST.getValue(),GepafinConstant.CREATE_NDG, (Long) request.getAttribute(GepafinConstant.USER_ACTION_ID)); + try { + // 1) If 2 hours have already passed, mark as FAILED and shut down: + if (System.currentTimeMillis() - startTime > twoHoursMs) { + ApplicationEntity app = applicationService.validateApplication(applicationId); + log.warn("2-hour timeout reached for applicationId {}. Marking NDG_FAILED.", applicationId); + app.setNdgStatus(GepafinConstant.NDG_FAILED); + applicationRepository.save(app); + + futureRef.get().cancel(false); + shutdownScheduler(applicationId); + return; + } + + // 2) Otherwise, call processNdgGeneration once: + processNdgGeneration(applicationId); + + // 3) After return, check if NDG is now set or timed out. If so, cancel & shut down: + ApplicationEntity updated = applicationService.validateApplication(applicationId); + if (isNdgValid(updated.getNdg())) { + log.info("NDG found for applicationId {}. Shutting down scheduler.", applicationId); + futureRef.get().cancel(false); + shutdownScheduler(applicationId); + } else if (updated.getNdgStatus() != null && updated.getNdgStatus().equals(GepafinConstant.NDG_FAILED)) { + log.info("NDG status is NDG_FAILED for applicationId {}. Shutting down scheduler.", applicationId); + futureRef.get().cancel(false); + shutdownScheduler(applicationId); + } + // Otherwise: no NDG yet, not timed out → next run happens in 15 minutes automatically. + } catch (Exception ex) { + log.error("Unexpected error during scheduled polling for applicationId {}: {}", applicationId, ex.getMessage(), ex); + try { + ApplicationEntity checkApp = applicationService.validateApplication(applicationId); + if (System.currentTimeMillis() - startTime > twoHoursMs) { + log.warn("After exception, 2-hour window passed for applicationId {}. Marking NDG_FAILED.", applicationId); + checkApp.setNdgStatus(GepafinConstant.NDG_FAILED); + applicationRepository.save(checkApp); + + futureRef.get().cancel(false); + shutdownScheduler(applicationId); + } + } catch (Exception ignore) { + futureRef.get().cancel(false); + shutdownScheduler(applicationId); + } + } + }; + + // Schedule pollingTask: run now (delay=0), then every fifteen minutes: + ScheduledFuture future = scheduler.scheduleWithFixedDelay(pollingTask, 0, // initial delay = 0 min → run immediately + fifteenMin, // subsequent runs every 15 minutes + TimeUnit.MINUTES); + futureRef.set(future); + } + + private void shutdownScheduler(Long applicationId) { + + ScheduledExecutorService shed = executorMap.remove(applicationId); + if (shed != null) { + shed.shutdownNow(); + } + log.info("Scheduler shut down for applicationId: {}", applicationId); + } + + private void processNdgGeneration(Long applicationId) { + // Validate application, company, and hub + log.info("Starting NDG generation process for applicationId: {}", applicationId); + ApplicationEntity application = applicationService.validateApplication(applicationId); + CompanyEntity company = companyService.validateCompany(application.getCompanyId()); + HubEntity hub = hubRepository.findByHubId(application.getHubId()); + + if (!hub.getUniqueUuid().equals(defaultHubUuid)) { + log.info("Ndg cannot be created for another Hub, it is default for Gepafin."); + throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.NO_NDG_FOR_ANOTHER_HUB)); + } + + try { + // Authenticate and fetch token if required + if (hub.getAppointmentAuthTokenId() == null || hub.getAreaCode() == null) { + authenticateAndSaveToken(hub, application); + } + + String authorizationToken = getBearerToken(hub); + + // Try retrieving NDG by VAT number + AppointmentLoginResponse ndgResponse = retrieveNdgByVatNumber(company.getVatNumber(), authorizationToken, hub, application); + if (isNdgValid(ndgResponse.getNdg())) { + saveNdgAndIdVisura(application, company, ndgResponse.getNdg()); + log.info("NDG successfully generated for applicationId: {}", applicationId); + } else { + log.info("Polling for NDG for applicationId: {}", applicationId); + handleNdgPolling(application, company, hub, authorizationToken); + } + } catch (Exception e) { + log.error("Exception occurred during NDG generation. ApplicationId: {}, CompanyId: {}, HubId: {}, Error: {}", applicationId, company.getId(), hub.getId(), + e.getMessage(), e); + } + } + + private void handleNdgPolling(ApplicationEntity application, CompanyEntity company, HubEntity hub, String authorizationToken) { + + log.info("Starting singleâ€shot NDG polling attempt for applicationId: {}, CompanyId: {}, HubId: {}", application.getId(), company.getId(), hub.getId()); + ApplicationEntity oldApplication = Utils.getClonedEntityForData(application); + CompanyEntity oldCompanyEntity=Utils.getClonedEntityForData(company); + long startTime = System.currentTimeMillis(); + long twoHoursMs = TimeUnit.HOURS.toMillis(2); + + try { + // 1) If NDG was already populated (perhaps by another thread), skip polling. + if (application.getNdg() != null) { + log.info("NDG already present for applicationId {}. Exiting singleâ€shot polling.", application.getId()); + return; + } + + // 2) Attempt to create Visura (this may immediately return a valid NDG) + AppointmentLoginResponse visuraResponse = createVisura(company, authorizationToken, hub); + + // 2a) If createVisura gave us a valid NDG, persist & exit + String fetchedNdg = visuraResponse.getNdg(); + if (isNdgValid(fetchedNdg)) { + log.info("Valid NDG retrieved from createVisura(): {} | applicationId: {}", fetchedNdg, application.getId()); + + company.setNdg(fetchedNdg); + application.setNdg(fetchedNdg); + application.setNdgStatus(GepafinConstant.NDG_GENERATED); + application.setStatus(ApplicationStatusTypeEnum.NDG.getValue()); + application.setIdVisura(visuraResponse.getIdVisura()); + + companyRepository.save(company); + applicationRepository.save(application); + loggingUtil.addVersionHistory( + VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldApplication).newData(application).build()); + loggingUtil.addVersionHistory( + VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldCompanyEntity).newData(company).build()); + + ApplicationEvaluationEntity eval = applicationEvaluationService.validateApplicationEvaluation(application.getApplicationEvaluationId()); + Map placeholders = new HashMap<>(); + placeholders.put("{{call_name}}", application.getCall().getName()); + placeholders.put("{{protocol_number}}", String.valueOf(application.getProtocol().getProtocolNumber())); + notificationDao.sendNotificationToInstructor(placeholders, eval, NotificationTypeEnum.NDG_GENERATION); + notificationDao.sendNotificationToSuperUser(application, placeholders, NotificationTypeEnum.NDG_GENERATION); + + log.info("NDG saved successfully for applicationId: {}", application.getId()); + return; + } + + // 2b) If no immediate NDG, fetch the Visura list JSON and parse out NDG + String visuraListJson = getVisuraList(visuraResponse.getIdVisura(), authorizationToken, application, hub); + log.debug("Parsing NDG from VisuraList JSON for applicationId: {}", application.getId()); + String parsedNdg = parseNdgFromVisuraListResponse(visuraListJson); + + if (isNdgValid(parsedNdg)) { + log.info("Valid NDG parsed from VisuraList: {} | applicationId: {}", parsedNdg, application.getId()); + + company.setNdg(parsedNdg); + application.setNdg(parsedNdg); + application.setNdgStatus(GepafinConstant.NDG_GENERATED); + application.setStatus(ApplicationStatusTypeEnum.NDG.getValue()); + application.setIdVisura(visuraResponse.getIdVisura()); + + companyRepository.save(company); + applicationRepository.save(application); + loggingUtil.addVersionHistory( + VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldApplication).newData(application).build()); + loggingUtil.addVersionHistory( + VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldCompanyEntity).newData(company).build()); + + ApplicationEvaluationEntity eval = applicationEvaluationService.validateApplicationEvaluation(application.getApplicationEvaluationId()); + Map placeholders = new HashMap<>(); + placeholders.put("{{call_name}}", application.getCall().getName()); + placeholders.put("{{protocol_number}}", String.valueOf(application.getProtocol().getProtocolNumber())); + notificationDao.sendNotificationToInstructor(placeholders, eval, NotificationTypeEnum.NDG_GENERATION); + notificationDao.sendNotificationToSuperUser(application, placeholders, NotificationTypeEnum.NDG_GENERATION); + + log.info("NDG saved successfully for applicationId: {}", application.getId()); + return; + } + + // 3) Neither direct API nor parsing yielded a valid NDG. Check timeout. + if (System.currentTimeMillis() - startTime > twoHoursMs) { + log.warn("NDG polling timed out after 2 hours for applicationId: {}", application.getId()); + application.setNdgStatus(GepafinConstant.NDG_FAILED); + applicationRepository.save(application); + return; + } + + // 4) No NDG yet—just return. The scheduler will retry in 15 minutes. + log.info("No valid NDG yet for applicationId {}. Returning to scheduler—next attempt in 15 minutes.", application.getId()); + } catch (Exception e) { + log.error("Exception during NDG polling for applicationId: {}", application.getId(), e); + // If timeout after exception, mark NDG_FAILED + if (System.currentTimeMillis() - startTime > twoHoursMs) { + log.warn("Example: exiting singleâ€shot polling due to timeout after exception for applicationId: {}", application.getId()); + application.setNdgStatus(GepafinConstant.NDG_FAILED); + applicationRepository.save(application); + } else { + log.info("Exception occurred but not timed out for applicationId {}. Returning to scheduler for next attempt in 15 minutes.", application.getId()); + } + } + + log.info("NDG polling completed for applicationId: {}", application.getId()); + } + + private static String getBearerToken(HubEntity hub) { + + return "Bearer " + hub.getAppointmentAuthTokenId(); + } + + private boolean isNdgValid(String ndg) { + + return ndg != null && !ndg.isEmpty(); + } + + private void saveNdgAndIdVisura(ApplicationEntity application, CompanyEntity company, String ndg) { + + ApplicationEntity oldApplication = Utils.getClonedEntityForData(application); + CompanyEntity oldCompanyEntity=Utils.getClonedEntityForData(company); + application.setNdg(ndg); + application.setNdgStatus(GepafinConstant.NDG_GENERATED); + application.setStatus(ApplicationStatusTypeEnum.NDG.getValue()); + company.setNdg(ndg); + companyRepository.save(company); + applicationRepository.save(application); + loggingUtil.addVersionHistory( + VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldApplication).newData(application).build()); + loggingUtil.addVersionHistory( + VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldCompanyEntity).newData(company).build()); + + ApplicationEvaluationEntity applicationEvaluationEntity = applicationEvaluationService.validateApplicationEvaluation(application.getApplicationEvaluationId()); +// Map placeHolders = notificationDao.sendNotificationToBeneficiary(application, NotificationTypeEnum.NDG_GENERATION); + Map placeHolders = new HashMap<>(); + placeHolders.put("{{call_name}}", application.getCall().getName()); + placeHolders.put("{{protocol_number}}", String.valueOf(application.getProtocol().getProtocolNumber())); + notificationDao.sendNotificationToInstructor(placeHolders, applicationEvaluationEntity, NotificationTypeEnum.NDG_GENERATION); + notificationDao.sendNotificationToSuperUser(application, placeHolders, NotificationTypeEnum.NDG_GENERATION); + log.info("NDG saved for applicationId: {}, {}", application.getId(), application.getNdg()); + } + + private String getVisuraList(String idVisura, String authorizationToken, ApplicationEntity application, HubEntity hub) { + + log.info("Initiating Visura list retrieval | ApplicationId: {}, HubId: {}, IdVisura: {}", application.getId(), hub.getId(), idVisura); + AppointmentVisuraListRequest visuraListRequest = new AppointmentVisuraListRequest(); + AppointmentVisuraListRequest.VisuraFilter filter = new AppointmentVisuraListRequest.VisuraFilter(); + filter.setIdVisura(idVisura); + visuraListRequest.setFilter(filter); + + try { + String requestJson = Utils.convertObjectToJson(visuraListRequest); + ResponseEntity response = appointmentApiService.getVisuraList(requestJson, authorizationToken); + return Utils.convertObjectToJson(response.getBody()); + } catch (FeignException.Forbidden forbiddenException) { + log.warn("403 Forbidden while fetching Visura list. Attempting token regeneration | ApplicationId: {}, HubId: {}", application.getId(), hub.getId()); + // Regenerate the token and retry + String newAuthorizationToken = regenerateTokenAndSave(hub, application); + return getVisuraList(idVisura, newAuthorizationToken, application, hub); + } catch (Exception e) { + log.error("Error while fetching Visura list | ApplicationId: {}, HubId: {}, Error: {}", application.getId(), hub.getId(), e.getMessage(), e); + throw new RuntimeException("Error fetching Ndg List", e); + } + } + + private AppointmentLoginResponse retrieveNdgByVatNumber(String vatNumber, String authorizationToken, HubEntity hub, ApplicationEntity application) { + + try { + log.info("Initiating NDG retrieval by VAT number | ApplicationId: {}, HubId: {}, VAT: {}", application.getId(), hub.getId(), vatNumber); + // Prepare the NDG request + AppointmentNdgRequest ndgRequest = getAppointmentNdgRequest(vatNumber); + // Call the API to retrieve NDG + ResponseEntity response = appointmentApiService.getNdgByVatNumber(ndgRequest, authorizationToken); + String responseJson = Utils.convertObjectToJson(response.getBody()); + // Parse and return the NDG response + return parseNdgResponse(responseJson); + } catch (FeignException.Forbidden forbiddenException) { + log.error("403 Forbidden during NDG retrieval | ApplicationId: {}, HubId: {}", application.getId(), hub.getId()); + logForbiddenError(); + // Regenerate the token and retry + String newAuthorizationToken = regenerateTokenAndSave(hub, application); + return retrieveNdgByVatNumber(vatNumber, newAuthorizationToken, hub, application); + } catch (Exception e) { + log.error("Error during NDG retrieval | ApplicationId: {}, HubId: {}, Message: {}", application.getId(), hub.getId(), e.getMessage(), e); + throw new RuntimeException("NDG retrieval failed.", e); + } + } + + private String regenerateTokenAndSave(HubEntity hub, ApplicationEntity application) { + + hub = authenticateAndSaveToken(hub, application); + return "Bearer " + hub.getAppointmentAuthTokenId(); + } + + private AppointmentLoginResponse createVisura(CompanyEntity company, String authorizationToken, HubEntity hub) { + + try { + String visuraRequest = getAppointmentVisuraRequest(company, hub.getAreaCode()); + ResponseEntity response = appointmentApiService.createVisura(visuraRequest, authorizationToken); + String responseJson = Utils.convertObjectToJson(response.getBody()); + return parseVisuraResponse(responseJson); + } catch (FeignException.Forbidden forbiddenException) { + logForbiddenError(); + // Regenerate the token and retry + String newAuthorizationToken = regenerateTokenAndSave(hub, null); + return createVisura(company, newAuthorizationToken, hub); + } catch (Exception e) { + log.error("Failed to create Visura for Ndg : {}", e.getMessage()); + throw new RuntimeException("Visura creation failed for Ndg.", e); + } + } + + private static void logForbiddenError() { + + log.error("403 Forbidden received while retrieving NDG. Regenerating token..."); + } + + private static AppointmentNdgRequest getAppointmentNdgRequest(String vatNumber) { + + log.info("Creating Appointment NDG Request | VAT Number: {}", vatNumber); + AppointmentNdgRequest request = new AppointmentNdgRequest(); + AppointmentNdgRequest.Filter filter = new AppointmentNdgRequest.Filter(); + filter.setPartitaIva(vatNumber); + + AppointmentNdgRequest.Pagination pagination = new AppointmentNdgRequest.Pagination(); + pagination.setTargetPage(AppointmentApiConstant.TARGET_PAGE_SIZE); + pagination.setRecordsPerPage(AppointmentApiConstant.RECORD_PER_PAGE_SIZE); + + request.setFilter(filter); + request.setPagination(pagination); + return request; + } + + private static String getAppointmentVisuraRequest(CompanyEntity company, String areaCode) { + + AppointmentVisuraRequest visuraRequest = new AppointmentVisuraRequest(); + AppointmentVisuraRequest.VisuraInput input = new AppointmentVisuraRequest.VisuraInput(); + input.setPartitaIva(company.getVatNumber()); + input.setCodiceFiscale(company.getCodiceFiscale()); + input.setCodArea(areaCode); + input.setVisuraMode(AppointmentApiConstant.VISURA_MODE); + input.setVisuraProvider(AppointmentApiConstant.VISURA_PROVIDER); + input.setCodAgente(AppointmentApiConstant.COD_AGENTE); + input.setAnagraficaLegame(AppointmentApiConstant.IS_ANAGRAFICA_LEGAME); + input.setCreaAnagrafica(AppointmentApiConstant.CREA_ANAGRAFICA); + input.setFromRating(AppointmentApiConstant.IS_FROM_RATING); + input.setSalvaDocumenti(AppointmentApiConstant.SALVA_DOCUMENTI); + input.setVisuraType(AppointmentApiConstant.VISURA_TYPE); + visuraRequest.setInput(input); + return Utils.convertObjectToJson(visuraRequest); + } + + private String parseNdgFromVisuraListResponse(String jsonResponse) { + + try { + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode rootNode = objectMapper.readTree(jsonResponse); + JsonNode dataNode = rootNode.get(GepafinConstant.DATA_STRING); + + if (dataNode != null && dataNode.isArray() && dataNode.size() > 0) { + JsonNode firstEntry = dataNode.get(0); + JsonNode ndgClienteNode = firstEntry.get("ndgCliente"); + if (ndgClienteNode != null && ndgClienteNode.get("code") != null) { + String code = ndgClienteNode.get("code").asText(); + return normalizeNullValue(code); + } + } + log.warn("NDG not found in Visura List API response."); + return null; + } catch (Exception e) { + log.error("Failed to parse NDG from Visura List API response: {}", e.getMessage(), e); + throw new RuntimeException("Error parsing NDG from Visura List API response", e); + } + } + + public AppointmentLoginResponse parseLoginResponse(String jsonResponse) { + + try { + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode rootNode = objectMapper.readTree(jsonResponse); + JsonNode dataNode = rootNode.get(GepafinConstant.DATA_STRING); + + if (dataNode != null) { + AppointmentLoginResponse response = new AppointmentLoginResponse(); + response.setTokenId(dataNode.get("tokenId").asText()); + JsonNode areasNode = dataNode.get("areas"); + if (areasNode != null && areasNode.isArray() && areasNode.size() > 0) { + response.setAreaCode(areasNode.get(0).get("code").asText()); + } + response.setCompanyId(dataNode.get("companyId").asLong()); + return response; + } else { + throw new RuntimeException("Invalid JSON structure: Missing 'data' node."); + } + } catch (Exception e) { + throw new RuntimeException("Failed to parse response from loginApi for odessa: " + e.getMessage(), e); + } + } + + public AppointmentLoginResponse parseVisuraResponse(String jsonResponse) { + + try { + // Log full raw JSON for debug purposes + log.info("Raw Visura JSON Response: {}", jsonResponse); + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode rootNode = objectMapper.readTree(jsonResponse); + JsonNode dataNode = rootNode.get(GepafinConstant.DATA_STRING); + + if (dataNode != null && dataNode.isObject()) { + AppointmentLoginResponse response = new AppointmentLoginResponse(); + JsonNode idVisuraNode = dataNode.get(GepafinConstant.ID_VISURA_STRING); + JsonNode ndgNode = dataNode.get(GepafinConstant.NDG_STRING); + if (idVisuraNode == null || ndgNode == null) { + log.error("Missing expected fields in 'data' node. JSON: {}", dataNode); + } + response.setIdVisura(normalizeNullValue(idVisuraNode != null ? idVisuraNode.asText() : null)); + response.setNdg(normalizeNullValue(ndgNode != null ? ndgNode.asText() : null)); + return response; + } else { + System.err.println("Invalid JSON: 'data' node is missing or not an object."); + throw new RuntimeException("Invalid JSON structure: Missing or malformed 'data' node."); + } + } catch (Exception e) { + System.err.println("Exception while parsing Visura response: " + e.getMessage()); + throw new RuntimeException("Failed to parse response: " + e.getMessage(), e); + } + } + + public AppointmentLoginResponse parseNdgResponse(String jsonResponse) { + + try { + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode rootNode = objectMapper.readTree(jsonResponse); + JsonNode dataArray = rootNode.get(GepafinConstant.DATA_STRING); + if (dataArray == null || !dataArray.isArray() || dataArray.isEmpty()) { + log.info("NDG data is empty or missing in the response."); + AppointmentLoginResponse emptyResponse = new AppointmentLoginResponse(); + emptyResponse.setNdg(null); + return emptyResponse; + } + JsonNode firstDataEntry = dataArray.get(0); + AppointmentLoginResponse response = new AppointmentLoginResponse(); + if (firstDataEntry.has(GepafinConstant.NDG_STRING)) { + response.setNdg(normalizeNullValue(firstDataEntry.get(GepafinConstant.NDG_STRING).asText())); + } + return response; + } catch (Exception e) { + log.error("Failed to parse response: {}", e.getMessage(), e); + throw new RuntimeException("Failed to parse NDG response.", e); + } + } + + private String normalizeNullValue(String value) { + + return (value == null || GepafinConstant.NULL_STRING.equalsIgnoreCase(value.trim())) ? null : value; + } + + public AppointmentCreationResponse createAppointment(Long applicationId, CreateAppointmentRequest createAppointmentRequest) { + // Validate the application + log.info("Starting appointment creation for applicationId: {}", applicationId); + ApplicationEntity application = applicationService.validateApplication(applicationId); + + AppointmentCreationResponse appointmentCreationResponse = new AppointmentCreationResponse(); + + ApplicationEntity oldApplicationData = Utils.getClonedEntityForData(application); + HubEntity hub = hubRepository.findByHubId(application.getHubId()); + + // Check hub UUID and enforce constraints + if (!hub.getUniqueUuid().equals(defaultHubUuid)) { + log.info("Appointment cannot be created for another Hub; default is required for Gepafin."); + throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.NO_APPOINTMENT_FOR_ANOTHER_HUB)); + } + + try { + // Pre-check conditions for appointment creation + if (application.getNdg() != null && !Objects.equals(application.getNdgStatus(), GepafinConstant.NDG_IN_PROGRESS) && application.getAppointmentId() != null) { + appointmentCreationResponse.setAppointmentId(application.getAppointmentId()); + throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.APPOINTMENT_ALREADY_CREATED)); + // return appointmentCreationResponse; + } + + if (application.getNdg() == null && Objects.equals(application.getNdgStatus(), GepafinConstant.NDG_IN_PROGRESS)) { + log.warn("NDG in progress but not available for applicationId: {}", applicationId); + throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.NDG_NOT_FOUND_FOR_APPLICATION)); + } + + // Generate authorization token and fetch template data + String authorizationToken = regenerateTokenAndSave(hub, application); + Long appointmentTemplateId = application.getCall().getAppointmentTemplateId(); + if (appointmentTemplateId == null) { + log.error("Missing appointment template ID for applicationId: {}", applicationId); + throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.APPOINTMENT_CANNOT_BE_CREATED)); + } + ResponseEntity response = appointmentApiService.getAppointmentTemplateForTemplateCreation(authorizationToken, appointmentTemplateId); + + if (response.getStatusCode() != HttpStatus.OK) { + log.error("Failed to retrieve appointment template for appointment creation. Status: {}", response.getStatusCode()); + throw new IllegalStateException("Failed to retrieve appointment template for appointment creation"); + } + + // Parse template data + String responseDataForTemplate = Utils.convertObjectToJson(response.getBody()); + AppointmentCreationRequest templateRichiestaData = parseTemplateResponseData(responseDataForTemplate); + + // Build the appointment request body + AppointmentCreationRequest appointmentCreationRequest = buildAppointmentCreationRequest(applicationId, createAppointmentRequest, appointmentTemplateId, + templateRichiestaData); + log.info("AppointmentCreationRequest : {}", appointmentCreationRequest); + String appointmentRequestBody = Utils.convertObjectToJson(appointmentCreationRequest); + + // Make API call to create the appointment + log.info("Context:{}, Authorization Token : {}, RequestBody : {}", context, authorizationToken, appointmentRequestBody); + ResponseEntity appointmentResponse = appointmentApiService.createAppointment(authorizationToken, context, appointmentRequestBody); + String appointmentId = extractAppointmentIdFromResponse(appointmentResponse); + + if (appointmentId == null) { + log.error("Failed to extract appointment ID from response for applicationId: {}", applicationId); + throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.APPOINTMENT_NOT_CREATED)); + } + // Update application with the appointment ID + application.setAppointmentId(appointmentId); + application.setStatus(ApplicationStatusTypeEnum.APPOINTMENT.getValue()); + applicationRepository.save(application); + + // Log version history + loggingUtil.addVersionHistory( + VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldApplicationData).newData(application).build()); + + appointmentCreationResponse.setAppointmentId(appointmentId); + return appointmentCreationResponse; + + } catch (FeignException.Forbidden forbiddenException) { + log.error("403 Forbidden received while retrieving template. Attempting to regenerate token and retry. Application ID: {}", applicationId); + regenerateTokenAndSave(hub, application); + return createAppointment(applicationId, createAppointmentRequest); + } + } + + private String extractAppointmentIdFromResponse(ResponseEntity appointmentResponse) { + + if (appointmentResponse.getBody() != null) { + log.info("Appointment API Response : {}", appointmentResponse.getBody()); + try { + Map responseBody = (Map) appointmentResponse.getBody(); + // 1. Try to get appointment ID from data.id + if (responseBody.containsKey(GepafinConstant.DATA_STRING)) { + Map data = (Map) responseBody.get(GepafinConstant.DATA_STRING); + if (data != null && data.containsKey(GepafinConstant.ID_STRING)) { + return data.get(GepafinConstant.ID_STRING).toString(); + } + } + // 2. If ID not present, check errors[0].cause.errorDescription + if (responseBody.containsKey(GepafinConstant.ERROR_STRING)) { + List> errors = (List>) responseBody.get(GepafinConstant.ERROR_STRING); + if (errors != null && !errors.isEmpty()) { + Map firstError = errors.get(0); + if (firstError.containsKey(GepafinConstant.CAUSE_STRING)) { + Map cause = (Map) firstError.get(GepafinConstant.CAUSE_STRING); + if (cause != null && cause.containsKey(GepafinConstant.ERROR_DESCRIPTION_STRING)) { + String errorDescription = cause.get(GepafinConstant.ERROR_DESCRIPTION_STRING).toString(); + log.warn("Appointment creation failed: {}", errorDescription); + } + } + } + } + } catch (Exception e) { + log.error("Error while extracting appointment ID or parsing error message", e); + } + } + return null; + } + + public AppointmentCreationRequest parseTemplateResponseData(String jsonResponse) { + + try { + + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode rootNode = objectMapper.readTree(jsonResponse); + JsonNode richiesteClienteArray = rootNode.path(GepafinConstant.DATA_STRING).path(GepafinConstant.RICHIESTE_CLIENTE_STRING); + + // Initialize the result object + AppointmentCreationRequest appointmentCreationRequest = new AppointmentCreationRequest(); + AppointmentCreationRequest.Input input = new AppointmentCreationRequest.Input(); + List richiestaClienteList = new ArrayList<>(); + if (!richiesteClienteArray.isArray()) { + log.warn("richiesteCliente array is missing or not an array."); + return new AppointmentCreationRequest(); + } + for (JsonNode richiestaNode : richiesteClienteArray) { + if (richiestaNode.isNull()) + continue; + + AppointmentCreationRequest.RichiestaCliente richiestaCliente = new AppointmentCreationRequest.RichiestaCliente(); + JsonNode prodottoNode = richiestaNode.path(AppointmentApiConstant.PRODOTTO); + String prodottoCode = prodottoNode.path(AppointmentApiConstant.PRODOTTO_CODE).asText(); + + richiestaCliente.setCodProdotto(prodottoCode); + richiestaCliente.setIdMotivazione(getIntValue(richiestaNode)); + richiestaCliente.setCodAbi(getTextValue(richiestaNode, AppointmentApiConstant.COD_ABI)); + richiestaCliente.setCodCab(getTextValue(richiestaNode, AppointmentApiConstant.COD_CAB)); + richiestaCliente.setIdNota(getTextValue(richiestaNode, AppointmentApiConstant.ID_NOTA)); + richiestaCliente.setImportoAgevolato(getTextValue(richiestaNode, AppointmentApiConstant.IMPORTO_AGEVOLATO)); + richiestaCliente.setImportoMedioLungoTermine(getTextValue(richiestaNode, AppointmentApiConstant.IMPORTO_MEDIOLUNGO_TERMINE)); + richiestaCliente.setCodTipoProdotto(getTextValue(richiestaNode, AppointmentApiConstant.COD_TIPO_PRODOTTO)); + richiestaCliente.setCodCategoriaProdotto(getTextValue(richiestaNode, AppointmentApiConstant.COD_CATEGORIA_PRODOTTO)); + richiestaCliente.setCodFormaTecnica(getTextValue(richiestaNode, AppointmentApiConstant.COD_FORMATECNICA)); + richiestaCliente.setCodOperazione(getTextValue(richiestaNode, AppointmentApiConstant.COD_OPERAZIONE)); + + richiestaClienteList.add(richiestaCliente); + } + + input.setRichiestaCliente(richiestaClienteList); + appointmentCreationRequest.setInput(input); + + return appointmentCreationRequest; + + } catch (JsonProcessingException e) { + log.error("JSON processing error: {}", e.getMessage(), e); + throw new IllegalStateException("Invalid JSON structure in template response", e); + } + } + + private String getTextValue(JsonNode node, String fieldName) { + + return node.path(fieldName).isTextual() ? node.path(fieldName).asText() : null; + } + + private int getIntValue(JsonNode node) { + + return node.path(AppointmentApiConstant.MOTIVAZIONE).path(AppointmentApiConstant.MOTIVAZIONE_ID).asInt(); + } + + public AppointmentCreationRequest buildAppointmentCreationRequest(Long applicationId, CreateAppointmentRequest createAppointmentRequest, Long areaCode, + AppointmentCreationRequest templateRichiestaData) { + + ApplicationEntity application = applicationService.validateApplication(applicationId); + CreateAppointmentRequest.Nota nota = createAppointmentRequest.getNota(); + + AppointmentCreationRequest appointmentCreationRequest = new AppointmentCreationRequest(); + AppointmentCreationRequest.Input input = new AppointmentCreationRequest.Input(); + + // Set Input Fields + input.setId(areaCode); + input.setNdg(application.getNdg()); + + // Populate richiestaCliente from template data + List richiestaClienteList = new ArrayList<>(); + for (AppointmentCreationRequest.RichiestaCliente templateRichiesta : templateRichiestaData.getInput().getRichiestaCliente()) { + AppointmentCreationRequest.RichiestaCliente richiestaCliente = new AppointmentCreationRequest.RichiestaCliente(); + BeanUtils.copyProperties(templateRichiesta, richiestaCliente); + + // Add specific `nota` + AppointmentCreationRequest.Nota requestNota = new AppointmentCreationRequest.Nota(); + requestNota.setTitolo(nota.getTitolo()); + requestNota.setTesto(nota.getTesto()); + richiestaCliente.setNota(requestNota); + richiestaCliente.setDurataMesiFinanziamento(createAppointmentRequest.getDurataMesiFinanziamento()); + richiestaCliente.setImportoBreveTermine(createAppointmentRequest.getImportoBreveTermine()); + richiestaClienteList.add(richiestaCliente); + } + + input.setRichiestaCliente(richiestaClienteList); + appointmentCreationRequest.setInput(input); + return appointmentCreationRequest; + } + + public DocumentUploadResponse uploadDocumentToExternalSystem(Long documentId, UploadDocToExternalSystemRequest docToExternalSystemRequest) { + log.info("Initiating upload to external system for documentId: {}", documentId); + // Check if the document is already being processed + DocumentEntity systemDoc = documentDao.validateDocument(documentId); + + ApplicationEntity application = null; + + if (systemDoc != null) { + DocumentSourceTypeEnum sourceType = DocumentSourceTypeEnum.valueOf(systemDoc.getSource()); + + switch (sourceType) { + case APPLICATION: + application = applicationDao.validateApplication(systemDoc.getSourceId()); + break; + case AMENDMENT: + ApplicationAmendmentRequestEntity applicationAmendmentEntity = applicationAmendmentRequestDao.validateApplicationAmendmentRequest(systemDoc.getSourceId()); + application = applicationDao.validateApplication(applicationAmendmentEntity.getApplicationId()); + break; + case EVALUATION: + ApplicationEvaluationEntity applicationEvaluationEntity = applicationEvaluationDao.validateApplicationEvaluation(systemDoc.getSourceId()); + application = applicationDao.validateApplication(applicationEvaluationEntity.getApplicationId()); + break; + + case CALL: + break; + + default: + log.warn("Unhandled document source type: {}", sourceType); + break; + } + } + + Claims claims = tokenProvider.getClaimsFromToken(tokenProvider.extractTokenFromRequest(request)); + Long hubId = Utils.extractHubIdFromPayload(claims.getSubject()); + + // Authenticate the hub before proceeding + HubEntity hub = hubRepository.findByHubId(hubId); + authenticateAndSaveToken(hub, application); + if (systemDoc != null && systemDoc.getDocumentAttachmentId() != null) { + // If the documentAttachmentId is already set, return the response + log.info("Document already uploaded with documentAttachmentId: {}", systemDoc.getDocumentAttachmentId()); + DocumentUploadResponse response = new DocumentUploadResponse(); + response.setDocumentAttachmentId(systemDoc.getDocumentAttachmentId()); + return response; + } + // Check if a thread is already running for this document upload + if (threadForDocumentMap.containsKey(documentId)) { + log.warn("Document upload already running for documentId: {}", documentId); + throw new CustomValidationException(Status.SUCCESS, Translator.toLocale(GepafinConstant.DOCUMENT_UPLOADING_IN_PROGRESS)); + } + // Start the upload process in the background + ExecutorService executor = Executors.newSingleThreadExecutor(runnable -> { + Thread thread = new Thread(runnable); + thread.setName(GepafinConstant.ASYNC_DOCUMENT_UPLOAD_NAME + documentId); + return thread; + }); + threadForDocumentMap.put(documentId, executor); + + ApplicationEntity finalApplication = application; + executor.submit(() -> { + threadLocalHubId.set(hubId); + try { + log.info("Starting async document upload for documentId: {}", documentId); + uploadDocumentToExternalSystemSync(documentId, docToExternalSystemRequest, finalApplication); + } catch (Exception e) { + log.error("Error in async document upload for documentId: {}", documentId, e); + } finally { + // Cleanup resources + ExecutorService executorToShutdown = threadForDocumentMap.remove(documentId); + if (executorToShutdown != null) { + executorToShutdown.shutdown(); + threadLocalHubId.remove(); + } + log.info("Async document upload completed for documentId: {}", documentId); + } + }); + return null; + } + + private void uploadDocumentToExternalSystemSync(Long documentId, UploadDocToExternalSystemRequest docToExternalSystemRequest, ApplicationEntity application) { + log.info("Starting sync document upload for documentId: {}", documentId); + // Synchronous upload logic + DocumentEntity systemDoc = documentDao.validateDocument(documentId); + + Long hubId = threadLocalHubId.get(); + HubEntity hub = hubRepository.findByHubId(hubId); + + if (!hub.getUniqueUuid().equals(defaultHubUuid)) { + log.info("Document cannot be uploaded for another Hub, it is default for Gepafin."); + throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.NO_DOCUMENT_UPLOAD_FOR_ANOTHER_HUB)); + } + + log.info("Got Document in system: {}", systemDoc); + String oldUrl = systemDoc.getFilePath(); + String authorizationToken = getBearerToken(hub); + + try { + File localFile = downloadFileFromS3(oldUrl); + MultipartFile multipartFile = convertFileToMultipartFile(localFile); + + UploadDocToExternalSystemRequest externalSystemRequest = new UploadDocToExternalSystemRequest(); + externalSystemRequest.setInput(getUploadDocumentInput(docToExternalSystemRequest)); + + String uploadDocRequest = Utils.convertObjectToJson(externalSystemRequest); + ResponseEntity uploadedDocumentData = appointmentApiService.uploadDocumentToExternalSystemForAppointment(authorizationToken, context, uploadDocRequest, + multipartFile); + + String responseData = Utils.convertObjectToJson(uploadedDocumentData.getBody()); + DocumentUploadResponse parsedResponse = parseDocumentUploadResponse(responseData); + + if (parsedResponse == null) { + log.error("Upload failed: parsed response is null for documentId: {}", documentId); + throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.ERROR_UPLOADING_DOCUMENT)); + } + + // Save the documentAttachmentId to the database + systemDoc.setDocumentAttachmentId(parsedResponse.getDocumentAttachmentId()); + documentRepository.save(systemDoc); + + log.info("Document uploaded successfully to external system: {}", parsedResponse); + } catch (FeignException.Forbidden forbiddenException) { + log.error("403 Forbidden from external system during upload for documentId: {}. Retrying with new token...", documentId); + regenerateTokenAndSave(hub, application); + uploadDocumentToExternalSystemSync(documentId, docToExternalSystemRequest, application); + } catch (Exception e) { + log.error("Exception during document upload: {}", e.getMessage(), e); + throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.EXTERNAL_DOCUMENT_UPLOAD_FAILURE_MSG)); + } + } + + private UploadDocToExternalSystemRequest.Input getUploadDocumentInput(UploadDocToExternalSystemRequest docToExternalSystemRequest) { + + UploadDocToExternalSystemRequest.Input input = new UploadDocToExternalSystemRequest.Input(); + input.setIdTipoProtocollo(docToExternalSystemRequest.getInput().getIdTipoProtocollo()); + input.setIdClassificazione(docToExternalSystemRequest.getInput().getIdClassificazione()); + input.setFlagDaFirmare(docToExternalSystemRequest.getInput().getFlagDaFirmare()); + input.setDescrizione(docToExternalSystemRequest.getInput().getDescrizione()); + + UploadDocToExternalSystemRequest.Input.Attributes attributes = new UploadDocToExternalSystemRequest.Input.Attributes(); + attributes.setNdg(docToExternalSystemRequest.getInput().getAttributes().getNdg()); + attributes.setEmail(docToExternalSystemRequest.getInput().getAttributes().getEmail()); + + input.setAttributes(attributes); + return input; + } + + public static MultipartFile convertFileToMultipartFile(File file) throws IOException { + + FileInputStream input = new FileInputStream(file); + return new MockMultipartFile(file.getName(), file.getName(), MediaType.APPLICATION_OCTET_STREAM_VALUE, input); + } + + private File downloadFileFromS3(String fileUrl) throws Exception { + + String key = amazonS3Service.extractS3KeyFromUrl(fileUrl); + String fileName = extractFileName(key); + String folderPath = key.substring(0, key.lastIndexOf("/")); + File localFile = new File(GepafinConstant.TEMP_FILE_PATH + fileName); + + try (InputStream s3Stream = amazonS3Service.getFile(folderPath, key); FileOutputStream outputStream = new FileOutputStream(localFile)) { + s3Stream.transferTo(outputStream); + } + + log.info("Downloaded file from old S3 bucket: {}", key); + return localFile; + } + + private String extractFileName(String filePath) { + + String[] parts = filePath.split("/"); + return parts[parts.length - 1]; + + } + + public DocumentUploadResponse parseDocumentUploadResponse(String jsonResponse) { + + try { + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode rootNode = objectMapper.readTree(jsonResponse); + + // Navigate to the "data" node + JsonNode dataNode = rootNode.get(GepafinConstant.DATA_STRING); + if (dataNode != null) { + DocumentUploadResponse response = new DocumentUploadResponse(); + + // Extract "documentAttachmentId" + JsonNode documentAttachmentIdNode = dataNode.get(GepafinConstant.DOCUMENT_ATTACHMENT_ID_STRING); + if (documentAttachmentIdNode != null) { + response.setDocumentAttachmentId(documentAttachmentIdNode.asText()); + } else { + throw new RuntimeException("Invalid JSON structure: Missing 'documentAttachmentId' node."); + } + + return response; + } else { + return null; + } + } catch (Exception e) { + throw new RuntimeException("Failed to parse response: " + e.getMessage(), e); + } + } } \ No newline at end of file diff --git a/src/main/java/net/gepafin/tendermanagement/util/Utils.java b/src/main/java/net/gepafin/tendermanagement/util/Utils.java index 13cf488e..19feb0e2 100644 --- a/src/main/java/net/gepafin/tendermanagement/util/Utils.java +++ b/src/main/java/net/gepafin/tendermanagement/util/Utils.java @@ -1053,15 +1053,16 @@ public class Utils { return headers; } - public static void setHttpServletRequestForThread(String redirectURI, String methodType,HttpServletRequest request,String remoteUser) { + public static void setHttpServletRequestForThread(HttpServletRequest request,String methodType, String remoteUser, Long userActionId) { MockHttpServletRequest mockRequest = new MockHttpServletRequest(); - mockRequest.setRequestURI(redirectURI); + mockRequest.setRequestURI(request.getRequestURI()); mockRequest.setMethod(methodType); - mockRequest.setRemoteUser(remoteUser); - Long userActionId = (Long) request.getAttribute(GepafinConstant.USER_ACTION_ID); - mockRequest.setAttribute(GepafinConstant.USER_ACTION_ID,userActionId ); + mockRequest.setRemoteUser(remoteUser); // or pass it in if needed + mockRequest.setAttribute(GepafinConstant.USER_ACTION_ID, userActionId); + ServletRequestAttributes attributes = new ServletRequestAttributes(mockRequest); RequestContextHolder.setRequestAttributes(attributes, true); } + } From d6dccf8cc2b221d65804788f3eaaa93323c41c9c Mon Sep 17 00:00:00 2001 From: rajesh Date: Fri, 6 Jun 2025 15:08:05 +0530 Subject: [PATCH 16/35] Done ticket GEPAFINBE-227 --- .../tendermanagement/dao/ApplicationDao.java | 32 ++++++++++++++--- .../dao/ApplicationEvaluationDao.java | 12 +++++-- .../tendermanagement/dao/AppointmentDao.java | 12 +++++-- .../dao/EmailNotificationDao.java | 36 +++++++++++++++---- .../tendermanagement/dao/NotificationDao.java | 6 +++- .../tendermanagement/dao/ProtocolDao.java | 8 ++--- .../ApplicationEvaluationScheduler.java | 6 +++- 7 files changed, 91 insertions(+), 21 deletions(-) diff --git a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java index c45171e8..eb3aa66f 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java @@ -1,5 +1,6 @@ package net.gepafin.tendermanagement.dao; +import com.amazonaws.services.dynamodbv2.xspec.S; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; @@ -1153,8 +1154,16 @@ public class ApplicationDao { // 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.DD_MM_YYYY)); + String protocolNumber=applicationEntity.getProtocol().getExternalProtocolNumber(); + if(protocolNumber==null){ + protocolNumber= String.valueOf(applicationEntity.getProtocol().getProtocolNumber()); + } + bodyPlaceholders.put("{{protocol_number}}",protocolNumber); + String protocolDate= DateTimeUtil.formatLocalDateTime(protocol.getCreatedDate(), GepafinConstant.DD_MM_YYYY); + if(protocol.getExternalProtocolDate()!=null){ + protocolDate= DateTimeUtil.formatLocalDateTime(protocol.getExternalProtocolDate(), GepafinConstant.DD_MM_YYYY); + } + bodyPlaceholders.put("{{date}}", protocolDate); bodyPlaceholders.put("{{time}}", DateTimeUtil.parseLocalTimeToString(protocol.getTime(), GepafinConstant.HH_MM_SS)); // Replace placeholders in the subject and body @@ -1204,8 +1213,17 @@ public class ApplicationDao { // 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.DD_MM_YYYY)); + String protocolNumber=applicationEntity.getProtocol().getExternalProtocolNumber(); + + if(protocolNumber==null){ + protocolNumber= String.valueOf(applicationEntity.getProtocol().getProtocolNumber()); + } + String protocolDate= DateTimeUtil.formatLocalDateTime(protocol.getCreatedDate(), GepafinConstant.DD_MM_YYYY); + if(protocol.getExternalProtocolDate()!=null){ + protocolDate= DateTimeUtil.formatLocalDateTime(protocol.getExternalProtocolDate(), GepafinConstant.DD_MM_YYYY); + } + bodyPlaceholders.put("{{protocol_number}}", protocolNumber); + bodyPlaceholders.put("{{date}}",protocolDate); bodyPlaceholders.put("{{time}}", DateTimeUtil.parseLocalTimeToString(protocol.getTime(), GepafinConstant.HH_MM_SS)); // Replace placeholders in the subject and body @@ -2323,7 +2341,11 @@ public class ApplicationDao { bodyPlaceholders.put("{{call_name}}", call.getName()); bodyPlaceholders.put("{{application_id}}", applicationEntity.getId().toString()); bodyPlaceholders.put("{{company_name}}", company.getCompanyName()); - bodyPlaceholders.put("{{protocol_number}}", applicationEntity.getProtocol().getProtocolNumber().toString()); + String protocolNumber=applicationEntity.getProtocol().getExternalProtocolNumber(); + if(protocolNumber==null){ + protocolNumber= String.valueOf(applicationEntity.getProtocol().getProtocolNumber()); + } + bodyPlaceholders.put("{{protocol_number}}", protocolNumber); bodyPlaceholders.put("{{user_action_id}}",emailLogRequest.getUserActionId().toString()); String subject = Utils.replacePlaceholders(systemEmailTemplateResponse.getSubject(), subjectPlaceholders); diff --git a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationEvaluationDao.java b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationEvaluationDao.java index 2daae6ae..864855c4 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationEvaluationDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationEvaluationDao.java @@ -710,7 +710,11 @@ public class ApplicationEvaluationDao { Map placeHolders = new HashMap<>(); placeHolders.put("{{call_name}}", application.getCall().getName()); - placeHolders.put("{{protocol_number}}", String.valueOf(application.getProtocol().getProtocolNumber())); + String protocolNumber=application.getProtocol().getExternalProtocolNumber(); + if(protocolNumber==null){ + protocolNumber= String.valueOf(application.getProtocol().getProtocolNumber()); + } + placeHolders.put("{{protocol_number}}", protocolNumber); notificationDao.sendNotificationToSuperUser(application,placeHolders,NotificationTypeEnum.EVALUATION_CREATION); notificationDao.sendNotificationToInstructor(placeHolders,entity,NotificationTypeEnum.EVALUATION_CREATION); @@ -1997,7 +2001,11 @@ public class ApplicationEvaluationDao { Map placeHolders = new HashMap<>(); placeHolders.put("{{call_name}}", application.getCall().getName()); - placeHolders.put("{{protocol_number}}", String.valueOf(application.getProtocol().getProtocolNumber())); + String protocolNumber=application.getProtocol().getExternalProtocolNumber(); + if(protocolNumber==null){ + protocolNumber= String.valueOf(application.getProtocol().getProtocolNumber()); + } + placeHolders.put("{{protocol_number}}", protocolNumber); notificationDao.sendNotificationToSuperUser(application,placeHolders,NotificationTypeEnum.EVALUATION_RESULT); notificationDao.sendNotificationToInstructor(placeHolders,existingEntity,NotificationTypeEnum.EVALUATION_RESULT); diff --git a/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java b/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java index f82d9906..2c4934db 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java @@ -531,7 +531,11 @@ public class AppointmentDao { Map placeHolders = new HashMap<>(); placeHolders.put("{{call_name}}", application.getCall().getName()); - placeHolders.put("{{protocol_number}}", String.valueOf(application.getProtocol().getProtocolNumber())); + String protocolNumber=application.getProtocol().getExternalProtocolNumber(); + if(protocolNumber==null){ + protocolNumber= String.valueOf(application.getProtocol().getProtocolNumber()); + } + placeHolders.put("{{protocol_number}}", protocolNumber); notificationDao.sendNotificationToInstructor(placeHolders, applicationEvaluationEntity, NotificationTypeEnum.NDG_GENERATION); notificationDao.sendNotificationToSuperUser(application, placeHolders, NotificationTypeEnum.NDG_GENERATION); log.info("NDG saved successfully for applicationId: {}", application.getId()); @@ -584,7 +588,11 @@ public class AppointmentDao { // Map placeHolders = notificationDao.sendNotificationToBeneficiary(application, NotificationTypeEnum.NDG_GENERATION); Map placeHolders = new HashMap<>(); placeHolders.put("{{call_name}}", application.getCall().getName()); - placeHolders.put("{{protocol_number}}", String.valueOf(application.getProtocol().getProtocolNumber())); + String protocolNumber=application.getProtocol().getExternalProtocolNumber(); + if(protocolNumber==null){ + protocolNumber= String.valueOf(application.getProtocol().getProtocolNumber()); + } + placeHolders.put("{{protocol_number}}", protocolNumber); notificationDao.sendNotificationToInstructor(placeHolders, applicationEvaluationEntity, NotificationTypeEnum.NDG_GENERATION); notificationDao.sendNotificationToSuperUser(application, placeHolders, NotificationTypeEnum.NDG_GENERATION); log.info("NDG saved for applicationId: {}, {}", application.getId(), application.getNdg()); diff --git a/src/main/java/net/gepafin/tendermanagement/dao/EmailNotificationDao.java b/src/main/java/net/gepafin/tendermanagement/dao/EmailNotificationDao.java index 3772ad71..e263a9f9 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/EmailNotificationDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/EmailNotificationDao.java @@ -174,8 +174,16 @@ public class EmailNotificationDao { public Map prepareEmailPlaceholders(ApplicationEntity applicationEntity, ApplicationAmendmentRequestEntity applicationAmendmentRequestEntity){ Map bodyPlaceholders = new HashMap<>(); bodyPlaceholders.put("{{call_name}}", applicationEntity.getCall().getName()); - bodyPlaceholders.put("{{protocol_number}}", applicationEntity.getProtocol().getProtocolNumber().toString()); - bodyPlaceholders.put("{{protocol_date}}", DateTimeUtil.formatLocalDateTime(applicationEntity.getProtocol().getCreatedDate(), GepafinConstant.DD_MM_YYYY)); + String protocolNumber=applicationEntity.getProtocol().getExternalProtocolNumber(); + if(protocolNumber==null){ + protocolNumber= String.valueOf(applicationEntity.getProtocol().getProtocolNumber()); + } + bodyPlaceholders.put("{{protocol_number}}", protocolNumber); + String protocolDate= DateTimeUtil.formatLocalDateTime(applicationEntity.getProtocol().getCreatedDate(), GepafinConstant.DD_MM_YYYY); + if(applicationEntity.getProtocol().getExternalProtocolDate()!=null){ + protocolDate= DateTimeUtil.formatLocalDateTime(applicationEntity.getProtocol().getExternalProtocolDate(), GepafinConstant.DD_MM_YYYY); + } + bodyPlaceholders.put("{{protocol_date}}", protocolDate); bodyPlaceholders.put("{{protocol_time}}", DateTimeUtil.parseLocalTimeToString(applicationEntity.getProtocol().getTime(), GepafinConstant.HH_MM_SS)); bodyPlaceholders.put("{{response_days}}", applicationAmendmentRequestEntity.getResponseDays().toString()); @@ -259,8 +267,16 @@ public class EmailNotificationDao { public void sendAdmissibilityNotificationEmailForAdmissibleApplication(ApplicationEntity applicationEntity) { Map bodyPlaceholders = new HashMap<>(); bodyPlaceholders.put("{{call_name}}", applicationEntity.getCall().getName()); - bodyPlaceholders.put("{{protocol_number}}", applicationEntity.getProtocol().getProtocolNumber().toString()); - bodyPlaceholders.put("{{protocol_date}}", DateTimeUtil.formatCreatedDate(applicationEntity.getProtocol().getCreatedDate())); + String protocolNumber=applicationEntity.getProtocol().getExternalProtocolNumber(); + if(protocolNumber==null){ + protocolNumber= String.valueOf(applicationEntity.getProtocol().getProtocolNumber()); + } + bodyPlaceholders.put("{{protocol_number}}", protocolNumber); + String protocolDate= DateTimeUtil.formatLocalDateTime(applicationEntity.getProtocol().getCreatedDate(), GepafinConstant.DD_MM_YYYY); + if(applicationEntity.getProtocol().getExternalProtocolDate()!=null){ + protocolDate= DateTimeUtil.formatLocalDateTime(applicationEntity.getProtocol().getExternalProtocolDate(), GepafinConstant.DD_MM_YYYY); + } + bodyPlaceholders.put("{{protocol_date}}", protocolDate); bodyPlaceholders.put("{{protocol_time}}", DateTimeUtil.parseLocalTimeToString(applicationEntity.getProtocol().getTime(), GepafinConstant.HH_MM_SS)); sendEmail(applicationEntity, SystemEmailTemplatesEntity.SystemEmailTemplatesEntityTypeEnum.ADMISSIBILITY_NOTIFICATION, bodyPlaceholders, null,null); @@ -269,8 +285,16 @@ public class EmailNotificationDao { public void sendInadmissibilityEmailForRejectedApplication(ApplicationEntity applicationEntity,ApplicationEvaluationEntity applicationEvaluationEntity) { Map bodyPlaceholders = new HashMap<>(); bodyPlaceholders.put("{{call_name}}", applicationEntity.getCall().getName()); - bodyPlaceholders.put("{{protocol_number}}", applicationEntity.getProtocol().getProtocolNumber().toString()); - bodyPlaceholders.put("{{protocol_date}}", DateTimeUtil.formatCreatedDate(applicationEntity.getProtocol().getCreatedDate())); + String protocolNumber=applicationEntity.getProtocol().getExternalProtocolNumber(); + if(protocolNumber==null){ + protocolNumber= String.valueOf(applicationEntity.getProtocol().getProtocolNumber()); + } + bodyPlaceholders.put("{{protocol_number}}", protocolNumber); + String protocolDate= DateTimeUtil.formatLocalDateTime(applicationEntity.getProtocol().getCreatedDate(), GepafinConstant.DD_MM_YYYY); + if(applicationEntity.getProtocol().getExternalProtocolDate()!=null){ + protocolDate= DateTimeUtil.formatLocalDateTime(applicationEntity.getProtocol().getExternalProtocolDate(), GepafinConstant.DD_MM_YYYY); + } + bodyPlaceholders.put("{{protocol_date}}", protocolDate); bodyPlaceholders.put("{{protocol_time}}", DateTimeUtil.parseLocalTimeToString(applicationEntity.getProtocol().getTime(), GepafinConstant.HH_MM_SS)); bodyPlaceholders.put("{{form_text}}", applicationEvaluationEntity.getMotivation()); diff --git a/src/main/java/net/gepafin/tendermanagement/dao/NotificationDao.java b/src/main/java/net/gepafin/tendermanagement/dao/NotificationDao.java index 17b86d6e..55ec6665 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/NotificationDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/NotificationDao.java @@ -174,7 +174,11 @@ public class NotificationDao { Map placeHolders = new HashMap<>(); placeHolders.put("{{call_name}}", application.getCall().getName()); - placeHolders.put("{{protocol_number}}", String.valueOf(application.getProtocol().getProtocolNumber())); + String protocolNumber=application.getProtocol().getExternalProtocolNumber(); + if(protocolNumber==null){ + protocolNumber= String.valueOf(application.getProtocol().getProtocolNumber()); + } + placeHolders.put("{{protocol_number}}", protocolNumber); NotificationReq notificationReq = createNotificationReq(notificationTypeEnum.getValue(), placeHolders, application.getUserId(), application.getUserWithCompany(), listOf(application.getCompanyId())); sendNotification(notificationReq); diff --git a/src/main/java/net/gepafin/tendermanagement/dao/ProtocolDao.java b/src/main/java/net/gepafin/tendermanagement/dao/ProtocolDao.java index c90ddf30..61895195 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/ProtocolDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/ProtocolDao.java @@ -170,7 +170,7 @@ public class ProtocolDao { vatNumber=company.getVatNumber(); }else { mittenteValue = tipoCorrispondenteWithoutCompany; - vatNumber = company.getCodiceFiscale(); + vatNumber = company.getCodiceFiscale(); } String companyName = company.getCompanyName() + " " + vatNumber; String callName = application.getCall().getName()+" "+ application.getCall().getId(); @@ -181,15 +181,15 @@ public class ProtocolDao { Map requestBody = new HashMap<>(); requestBody.put(GepafinConstant.PROTOCOL_DOC_URL,docUrl); - requestBody.put(GepafinConstant.PROTOCOL_COMPANY_NAME,companyName); requestBody.put(GepafinConstant.PROTOCOL_DOC_HASH,applicationSignedDocumentEntity.getFileHash()); requestBody.put(GepafinConstant.PROTOCOL_TIPO_PROTOCOLLAZIONE,TIPO_PROTOCOLLAZIONE); + requestBody.put(GepafinConstant.PROTOCOL_COMPANY_NAME_VAT_NUMBER,company.getCompanyName()); Map mittente = new HashMap<>(); - mittente.put(GepafinConstant.PROTOCOL_TIPO_CORRISPONDENTE_COMPANY, mittenteValue); - mittente.put(GepafinConstant.PROTOCOL_COMPANY_NAME_VAT_NUMBER,company.getCompanyName()); + mittente.put(GepafinConstant.PROTOCOL_TIPO_CORRISPONDENTE, mittenteValue); mittente.put(GepafinConstant.PROTOCOL_MEZZO,mezzo); mittente.put(GepafinConstant.PROTOCOL_INDIRIZZO_PEC,pecEmail); mittente.put(GepafinConstant.PROTOCOL_COMPANY_VAT_NUMBER,vatNumber); + mittente.put(GepafinConstant.PROTOCOL_COMPANY_NAME,company.getCompanyName()); List> destinatariObject=new ArrayList<>(); Map destinatari = new HashMap<>(); destinatari.put(GepafinConstant.PROTOCOL_CODICE_UO,codiceUo); diff --git a/src/main/java/net/gepafin/tendermanagement/scheduler/ApplicationEvaluationScheduler.java b/src/main/java/net/gepafin/tendermanagement/scheduler/ApplicationEvaluationScheduler.java index af4cb6be..413efe37 100644 --- a/src/main/java/net/gepafin/tendermanagement/scheduler/ApplicationEvaluationScheduler.java +++ b/src/main/java/net/gepafin/tendermanagement/scheduler/ApplicationEvaluationScheduler.java @@ -91,7 +91,11 @@ public class ApplicationEvaluationScheduler { // Map placeHolders = notificationDao.sendNotificationToBeneficiary(application, NotificationTypeEnum.EVALUATION_EXPIRED); Map placeHolders = new HashMap<>(); placeHolders.put("{{call_name}}", application.getCall().getName()); - placeHolders.put("{{protocol_number}}", String.valueOf(application.getProtocol().getProtocolNumber())); + String protocolNumber=application.getProtocol().getExternalProtocolNumber(); + if(protocolNumber==null){ + protocolNumber= String.valueOf(application.getProtocol().getProtocolNumber()); + } + placeHolders.put("{{protocol_number}}", protocolNumber); notificationDao.sendNotificationToSuperUser(application,placeHolders,NotificationTypeEnum.EVALUATION_EXPIRED); notificationDao.sendNotificationToInstructor(placeHolders,evaluation,NotificationTypeEnum.EVALUATION_EXPIRED); notificationDao.sendNotificationToInstructorManager(placeHolders,evaluation,NotificationTypeEnum.EVALUATION_EXPIRED); From 2f19a266525ddbdc311e487bb3595b4793958745 Mon Sep 17 00:00:00 2001 From: rajesh Date: Fri, 6 Jun 2025 15:46:40 +0530 Subject: [PATCH 17/35] Updated property --- src/main/resources/application.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index cac5e36b..2e40b904 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -83,7 +83,7 @@ sviluppumbria.username=protocollatoresvilumbria password=8e85ea4265e49497d697168fd3d34916 CLASSIFICA=06.004.3 TIPO_PROTOCOLLAZIONE=E -tipoCorrispondenteCompany=PersonaGiuridica +tipoCorrispondenteCompany=PersonaGiuridica tipoCorrispondenteWithoutCompany=Persona mezzo=Altro indirizzoPec=ufficio01@domain.com From 94791fab2066f33d579b7355d702ce274de32a64 Mon Sep 17 00:00:00 2001 From: rajesh Date: Fri, 6 Jun 2025 19:50:26 +0530 Subject: [PATCH 18/35] Done ticket GEPAFINBE-230 --- .../db/changelog/db.changelog-1.0.0.xml | 5 +++++ .../db/dump/updated_form_field_05_06_2025.sql | 19 +++++++++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 src/main/resources/db/dump/updated_form_field_05_06_2025.sql 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 f69ef624..9c01b209 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 @@ -2955,4 +2955,9 @@ + + + + diff --git a/src/main/resources/db/dump/updated_form_field_05_06_2025.sql b/src/main/resources/db/dump/updated_form_field_05_06_2025.sql new file mode 100644 index 00000000..42766b94 --- /dev/null +++ b/src/main/resources/db/dump/updated_form_field_05_06_2025.sql @@ -0,0 +1,19 @@ +UPDATE FORM_FIELD +SET + NAME = 'textinput', + LABEL = 'Campo Email', + DESCRIPTION = 'Per l''inserimento di indirizzi email standard (non PEC)', + SETTINGS = '[{"name":"label","value":"Campo Email"},{"name":"placeholder","value":"nome@esempio.it"},{"name":"reportEnable","value":false},{"name":"reportHeader","value":""},{"name": "isPecEmail", "value":false}]', + VALIDATORS = '{"isRequired": false, "custom": "isEmail"}', + UPDATED_DATE = CURRENT_TIMESTAMP +WHERE SORT_ORDER = 15; + +UPDATE FORM_FIELD +SET + NAME = 'textinput', + LABEL = 'Campo PEC', + DESCRIPTION = 'Specifico per l''inserimento di un indirizzo di Posta Elettronica Certificata', + SETTINGS = '[{"name":"label","value":"Campo PEC"},{"name":"placeholder","value":"nome@pec.it"},{"name":"reportEnable","value":false},{"name":"reportHeader","value":""},{"name": "isPecEmail", "value":false}]', + VALIDATORS = '{"isRequired": false, "custom": "isEmailPEC"}', + UPDATED_DATE = CURRENT_TIMESTAMP +WHERE SORT_ORDER = 16; From a733411b5dd1fa8a371d02612998ad457d10e7ae Mon Sep 17 00:00:00 2001 From: rajesh Date: Fri, 6 Jun 2025 14:28:21 +0530 Subject: [PATCH 19/35] Resolved conflicts --- .../tendermanagement/dao/AppointmentDao.java | 2637 +++++++++-------- .../gepafin/tendermanagement/util/Utils.java | 12 +- 2 files changed, 1337 insertions(+), 1312 deletions(-) diff --git a/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java b/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java index ddbbffbe..f0fd43cb 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java @@ -1,1308 +1,1331 @@ -package net.gepafin.tendermanagement.dao; - -import com.amazonaws.services.s3.AmazonS3Client; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import feign.FeignException; -import io.jsonwebtoken.Claims; -import jakarta.servlet.http.HttpServletRequest; -import lombok.extern.slf4j.Slf4j; -import net.gepafin.tendermanagement.config.Translator; -import net.gepafin.tendermanagement.config.jwt.TokenProvider; -import net.gepafin.tendermanagement.constants.AppointmentApiConstant; -import net.gepafin.tendermanagement.constants.GepafinConstant; -import net.gepafin.tendermanagement.entities.ApplicationAmendmentRequestEntity; -import net.gepafin.tendermanagement.entities.ApplicationEntity; -import net.gepafin.tendermanagement.entities.ApplicationEvaluationEntity; -import net.gepafin.tendermanagement.entities.CompanyEntity; -import net.gepafin.tendermanagement.entities.DocumentEntity; -import net.gepafin.tendermanagement.entities.HubEntity; -import net.gepafin.tendermanagement.enums.*; -import net.gepafin.tendermanagement.model.request.AppointmentCreationRequest; -import net.gepafin.tendermanagement.model.request.AppointmentNdgRequest; -import net.gepafin.tendermanagement.model.request.AppointmentVisuraListRequest; -import net.gepafin.tendermanagement.model.request.AppointmentVisuraRequest; -import net.gepafin.tendermanagement.model.request.CreateAppointmentRequest; -import net.gepafin.tendermanagement.model.request.UploadDocToExternalSystemRequest; -import net.gepafin.tendermanagement.model.request.VersionHistoryRequest; -import net.gepafin.tendermanagement.model.response.AppointmentCreationResponse; -import net.gepafin.tendermanagement.model.response.AppointmentLoginResponse; -import net.gepafin.tendermanagement.model.response.DocumentUploadResponse; -import net.gepafin.tendermanagement.model.response.NdgResponse; -import net.gepafin.tendermanagement.repositories.ApplicationRepository; -import net.gepafin.tendermanagement.repositories.CompanyRepository; -import net.gepafin.tendermanagement.repositories.DocumentRepository; -import net.gepafin.tendermanagement.repositories.HubRepository; -import net.gepafin.tendermanagement.repositories.UserRepository; -import net.gepafin.tendermanagement.service.AmazonS3Service; -import net.gepafin.tendermanagement.service.ApplicationService; -import net.gepafin.tendermanagement.service.CompanyService; -import net.gepafin.tendermanagement.service.feignClient.AppointmentApiService; -import net.gepafin.tendermanagement.service.impl.ApplicationEvaluationServiceImpl; -import net.gepafin.tendermanagement.util.LoggingUtil; -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.springframework.beans.BeanUtils; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; -import org.springframework.mock.web.MockMultipartFile; -import org.springframework.stereotype.Component; -import org.springframework.web.multipart.MultipartFile; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicReference; -import java.util.concurrent.ScheduledExecutorService; - -@Slf4j -@Component -public class AppointmentDao { - - @Value("${appointment.portal.user}") - private String user; - - @Value("${appointment.portal.password}") - private String password; - - @Value("${appointment.portal.source}") - private String source; - - @Value("${appointment.portal.context}") - private String context; - - @Value("${default.hub.uuid}") - private String defaultHubUuid; - - @Value("${aws.s3.url}") - private String s3Url; - - @Value("${aws.s3.bucket.name}") - private String OLD_BUCKET; - - @Autowired - private HubRepository hubRepository; - - @Autowired - private AppointmentApiService appointmentApiService; - - @Autowired - private ApplicationService applicationService; - - @Autowired - private CompanyService companyService; - - @Autowired - private ApplicationRepository applicationRepository; - - @Autowired - private CompanyRepository companyRepository; - - @Autowired - private DocumentDao documentDao; - - @Autowired - private AmazonS3Client s3Client; - - @Autowired - private DocumentRepository documentRepository; - - @Autowired - private HttpServletRequest request; - - @Autowired - private LoggingUtil loggingUtil; - - @Autowired - private TokenProvider tokenProvider; - - @Autowired - private NotificationDao notificationDao; - - @Autowired - private UserRepository userRepository; - - @Autowired - private ApplicationEvaluationServiceImpl applicationEvaluationService; - - @Autowired - private AmazonS3Service amazonS3Service; - - @Autowired - private ApplicationDao applicationDao; - - @Autowired - private ApplicationAmendmentRequestDao applicationAmendmentRequestDao; - - @Autowired - private ApplicationEvaluationDao applicationEvaluationDao; - - private final Map executorMap = new ConcurrentHashMap<>(); - - - private final ConcurrentHashMap threadForDocumentMap = new ConcurrentHashMap<>(); - - private static final ThreadLocal threadLocalHubId = new ThreadLocal<>(); - - public NdgResponse checkNdgForAppointment(Long applicationId) { - log.info("Starting NDG check for appointment. applicationId: {}", applicationId); - ApplicationEntity application = applicationService.validateApplication(applicationId); - NdgResponse ndgResponse = new NdgResponse(); - if (application.getNdgStatus() != null && application.getNdgStatus().equalsIgnoreCase(GepafinConstant.NDG_IN_PROGRESS)) { - log.warn("NDG generation already in progress. applicationId: {}", applicationId); - throw new CustomValidationException(Status.SUCCESS, Translator.toLocale(GepafinConstant.NDG_GENERATION_IS_IN_PROGRESS)); - } - - if (application.getNdgStatus() != null && application.getNdgStatus().equalsIgnoreCase(GepafinConstant.NDG_GENERATED) && application.getNdg() != null) { - ndgResponse.setNdg(application.getNdg()); - return ndgResponse; - } - - // Update application status - log.info("Updating NDG status to IN_PROGRESS. applicationId: {}", applicationId); - application.setNdgStatus(GepafinConstant.NDG_IN_PROGRESS); - applicationRepository.save(application); - - // Start async processing - HubEntity hub = hubRepository.findByHubId(application.getHubId()); - loginToOdessa(hub, application); - startAsyncNdgProcessing(applicationId); - log.info("NDG check initiation completed. applicationId: {}", applicationId); - return ndgResponse; - } - - // private HubEntity loginToOdessa(HubEntity hub, ApplicationEntity application) { - // - // int maxRetries = 3; - // int attempt = 0; - // boolean success = false; - // while (attempt < maxRetries && !success) { - // attempt++; - // try { - // //code to generate token with payload having "iat" epoch timestamp and secret key with no expiry and send in below method call - // String authJwtToken = Utils.generateAuthTokenForLoginToOdessa(); - // log.info("Got the auth for login to odessa {}", authJwtToken); - // hub.setAuthToken(authJwtToken); - // hubRepository.save(hub); - // Map body = Collections.emptyMap(); - // ResponseEntity responseLogin = appointmentApiService.loginWithOdessa(authJwtToken, source, context, user, password, body); - // if (responseLogin.getStatusCode() == HttpStatus.OK) { - // log.info("Login successful to odessa. Parsing response."); - // String loginResponseJson = Utils.convertObjectToJson(responseLogin.getBody()); - // AppointmentLoginResponse parsedResponse = parseLoginResponse(loginResponseJson); - // - // // Validate and save token - // if (parsedResponse.getTokenId() != null) { - // hub.setAppointmentAuthTokenId(parsedResponse.getTokenId()); - // hub.setAreaCode(parsedResponse.getAreaCode()); - // hubRepository.save(hub); - // log.info("Saved new authToken and areaCode for Hub."); - // success = true; - // return hub; - // } else { - // throw new RuntimeException("Login response is missing a valid tokenId for login to odessa system, please try again."); - // } - // } - // throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.ERROR_IN_GENERATING_NDG_TRY_AGAIN)); - // } catch (FeignException.Forbidden forbiddenException) { - // log.error("Failed to login to odessa due to some error"); - // - // // Extract raw response body - // String responseBody = forbiddenException.contentUTF8(); // Extract raw JSON response - // - // // Parse JSON to check for "PasswordExpired" - // try { - // ObjectMapper objectMapper = new ObjectMapper(); - // JsonNode rootNode = objectMapper.readTree(responseBody); - // JsonNode errorsNode = rootNode.path("errors"); - // - // if (errorsNode.isArray()) { - // for (JsonNode error : errorsNode) { - // // Check the main errorCode - // if (GepafinConstant.PASSWORD_EXPIRED.equals(error.path("errorCode").asText())) { - // application.setNdgStatus(GepafinConstant.NDG_FAILED); - // applicationRepository.save(application); - // throw new CustomValidationException(Status.FORBIDDEN, Translator.toLocale(GepafinConstant.PASSWORD_EXPIRED_LOGIN_TO_ODESSA)); - // } - // - // // Check inside "subErrors" - // JsonNode subErrorsNode = error.path("subErrors"); - // if (subErrorsNode.isArray()) { - // for (JsonNode subError : subErrorsNode) { - // if (GepafinConstant.PASSWORD_EXPIRED.equals(subError.path("errorCode").asText())) { - // application.setNdgStatus(GepafinConstant.NDG_FAILED); - // applicationRepository.save(application); - // throw new CustomValidationException(Status.FORBIDDEN, Translator.toLocale(GepafinConstant.PASSWORD_EXPIRED_LOGIN_TO_ODESSA)); - // } - // } - // } - // } - // } - // } catch (IOException e) { - // log.error("Error parsing JSON response: {}", e.getMessage()); - // } - // } catch (Exception e) { - // log.error("Failed to authenticate user on Odessa : {}", e.getMessage(), e); - // throw new RuntimeException("Authentication failed on Odessa. try again", e); - // } - // } - // return null; - // } - // - // - // private HubEntity authenticateAndSaveToken(HubEntity hub) { - // - // int maxRetries = 3; - // int attempt = 0; - // boolean success = false; - // while (attempt < maxRetries && !success) { - // attempt++; - // try { - // //code to generate token with payload having "iat" epoch timestamp and secret key with no expiry and send in below method call - // String authJwtToken = Utils.generateAuthTokenForLoginToOdessa(); - // log.info("Got the auth for login to odessa {}", authJwtToken); - // hub.setAuthToken(authJwtToken); - // hubRepository.save(hub); - // // Prepare the request body (adjust if necessary for login API) - // Map body = Collections.emptyMap(); - // // Perform login API call - // ResponseEntity responseLogin = appointmentApiService.loginWithOdessa(authJwtToken, source, context, user, password, body); - // - // // Handle successful login - // if (responseLogin.getStatusCode() == HttpStatus.OK) { - // log.info("Login successful to odessa. Parsing response."); - // String loginResponseJson = Utils.convertObjectToJson(responseLogin.getBody()); - // AppointmentLoginResponse parsedResponse = parseLoginResponse(loginResponseJson); - // - // // Validate and save token - // if (parsedResponse.getTokenId() != null) { - // hub.setAppointmentAuthTokenId(parsedResponse.getTokenId()); - // hub.setAreaCode(parsedResponse.getAreaCode()); - // hubRepository.save(hub); - // - // log.info("Saved new authToken and areaCode for Hub."); - // success = true; - // return hub; - // } else { - // throw new RuntimeException("Login response is missing a valid tokenId for login to odessa system, please try again."); - // } - // } - // // Handle non-OK response - // throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.ERROR_IN_GENERATING_NDG_TRY_AGAIN)); - // } catch (FeignException.Forbidden forbiddenException) { - // log.error("Failed to login to odessa due to some error occurred."); - // - // // Extract raw response body - // String responseBody = forbiddenException.contentUTF8(); // Extract raw JSON response - // - // // Parse JSON to check for "PasswordExpired" - // try { - // ObjectMapper objectMapper = new ObjectMapper(); - // JsonNode rootNode = objectMapper.readTree(responseBody); - // JsonNode errorsNode = rootNode.path("errors"); - // - // if (errorsNode.isArray()) { - // for (JsonNode error : errorsNode) { - // // Check the main errorCode - // if (GepafinConstant.PASSWORD_EXPIRED.equals(error.path("errorCode").asText())) { - // throw new CustomValidationException(Status.FORBIDDEN, Translator.toLocale(GepafinConstant.PASSWORD_EXPIRED_LOGIN_TO_ODESSA)); - // } - // - // // Check inside "subErrors" - // JsonNode subErrorsNode = error.path("subErrors"); - // if (subErrorsNode.isArray()) { - // for (JsonNode subError : subErrorsNode) { - // if (GepafinConstant.PASSWORD_EXPIRED.equals(subError.path("errorCode").asText())) { - // throw new CustomValidationException(Status.FORBIDDEN, Translator.toLocale(GepafinConstant.PASSWORD_EXPIRED_LOGIN_TO_ODESSA)); - // } - // } - // } - // } - // } - // } catch (IOException e) { - // log.error("Error parsing JSON response: {}", e.getMessage()); - // } - // } catch (Exception e) { - // log.error("Failed to authenticate user on Odessa : {}", e.getMessage(), e); - // throw new RuntimeException("Authentication failed on Odessa. try again", e); - // } - // } - // return null; - // } - - private void loginToOdessa(HubEntity hub, ApplicationEntity application) { - log.info("Starting login to Odessa. HubId: {}, ApplicationId: {}", hub.getId(), application.getId()); - performOdessaLogin(hub, application); - } - - private HubEntity authenticateAndSaveToken(HubEntity hub, ApplicationEntity application) { - - return performOdessaLogin(hub, application); - } - - private HubEntity performOdessaLogin(HubEntity hub, ApplicationEntity application) { - - int maxRetries = 3; - int attempt = 0; - while (attempt < maxRetries) { - attempt++; - try { - String authJwtToken = Utils.generateAuthTokenForLoginToOdessa(); - log.info("Got the auth for login to odessa {}", authJwtToken); - hub.setAuthToken(authJwtToken); - hubRepository.save(hub); - Map body = Collections.emptyMap(); - ResponseEntity responseLogin = appointmentApiService.loginWithOdessa(authJwtToken, source, context, user, password, body); - if (responseLogin.getStatusCode() == HttpStatus.OK) { - log.info("Login to Odessa successful. Parsing response. HubId: {}", hub.getId()); - String loginResponseJson = Utils.convertObjectToJson(responseLogin.getBody()); - AppointmentLoginResponse parsedResponse = parseLoginResponse(loginResponseJson); - - if (parsedResponse.getTokenId() != null) { - hub.setAppointmentAuthTokenId(parsedResponse.getTokenId()); - hub.setAreaCode(parsedResponse.getAreaCode()); - hubRepository.save(hub); - log.info("Saved new authToken and areaCode for Hub."); - return hub; - } else { - log.error("Login response from Odessa missing tokenId. HubId: {}", hub.getId()); - throw new RuntimeException("Login response is missing a valid tokenId for login to odessa system, please try again."); - } - } - throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.ERROR_IN_GENERATING_NDG_TRY_AGAIN)); - } catch (FeignException.Forbidden forbiddenException) { - log.error("Failed to login to odessa due to forbidden error."); - - CheckPasswordExpiredOrErrorInResponse(application, forbiddenException); - } catch (Exception e) { - log.error("Failed to authenticate user on Odessa (Attempt {}): {}", attempt, e.getMessage(), e); - } - } - throw new RuntimeException("Max retries exceeded. Failed to login to Odessa."); - } - private void CheckPasswordExpiredOrErrorInResponse(ApplicationEntity application, FeignException.Forbidden forbiddenException) { - - String responseBody = forbiddenException.contentUTF8(); - - try { - ObjectMapper objectMapper = new ObjectMapper(); - JsonNode rootNode = objectMapper.readTree(responseBody); - JsonNode errorsNode = rootNode.path("errors"); - - if (errorsNode.isArray()) { - for (JsonNode error : errorsNode) { - if (GepafinConstant.PASSWORD_EXPIRED.equals(error.path("errorCode").asText())) { - if (application != null) { - application.setNdgStatus(GepafinConstant.NDG_FAILED); - applicationRepository.save(application); - } - throw new CustomValidationException(Status.FORBIDDEN, Translator.toLocale(GepafinConstant.PASSWORD_EXPIRED_LOGIN_TO_ODESSA)); - } - - JsonNode subErrorsNode = error.path("subErrors"); - if (subErrorsNode.isArray()) { - for (JsonNode subError : subErrorsNode) { - if (GepafinConstant.PASSWORD_EXPIRED.equals(subError.path("errorCode").asText())) { - if (application != null) { - application.setNdgStatus(GepafinConstant.NDG_FAILED); - applicationRepository.save(application); - } - throw new CustomValidationException(Status.FORBIDDEN, Translator.toLocale(GepafinConstant.PASSWORD_EXPIRED_LOGIN_TO_ODESSA)); - } - } - } - } - } - } catch (IOException e) { - log.error("Error parsing JSON response: {}", e.getMessage()); - } - catch (Exception e) { - throw new RuntimeException("Authentication failed on Odessa. try again", e); - } - } - - private void startAsyncNdgProcessing(Long applicationId) { - // If already polling for this applicationId, do nothing: - if (executorMap.containsKey(applicationId)) { - log.warn("Async processing already running for applicationId: {}", applicationId); - return; - } - - ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(runnable -> { - Thread t = new Thread(runnable); - t.setName("AsyncNdgProcessing-" + applicationId); - return t; - }); - executorMap.put(applicationId, scheduler); - - // Record the start time so we can stop after 2 hours: - long startTime = System.currentTimeMillis(); - long twoHoursMs = TimeUnit.HOURS.toMillis(2); - long fifteenMin = 15; // in MINUTES - - // We need a reference to cancel the scheduled task from inside itself when we're done: - AtomicReference> futureRef = new AtomicReference<>(); - - Runnable pollingTask = () -> { - try { - // 1) If 2 hours have already passed, mark as FAILED and shut down: - if (System.currentTimeMillis() - startTime > twoHoursMs) { - ApplicationEntity app = applicationService.validateApplication(applicationId); - log.warn("2-hour timeout reached for applicationId {}. Marking NDG_FAILED.", applicationId); - app.setNdgStatus(GepafinConstant.NDG_FAILED); - applicationRepository.save(app); - - futureRef.get().cancel(false); - shutdownScheduler(applicationId); - return; - } - - // 2) Otherwise, call processNdgGeneration once: - processNdgGeneration(applicationId); - - // 3) After return, check if NDG is now set or timed out. If so, cancel & shut down: - ApplicationEntity updated = applicationService.validateApplication(applicationId); - if (isNdgValid(updated.getNdg())) { - log.info("NDG found for applicationId {}. Shutting down scheduler.", applicationId); - futureRef.get().cancel(false); - shutdownScheduler(applicationId); - } else if (updated.getNdgStatus() != null && updated.getNdgStatus().equals(GepafinConstant.NDG_FAILED)) { - log.info("NDG status is NDG_FAILED for applicationId {}. Shutting down scheduler.", applicationId); - futureRef.get().cancel(false); - shutdownScheduler(applicationId); - } - // Otherwise: no NDG yet, not timed out → next run happens in 15 minutes automatically. - } catch (Exception ex) { - log.error("Unexpected error during scheduled polling for applicationId {}: {}", applicationId, ex.getMessage(), ex); - try { - ApplicationEntity checkApp = applicationService.validateApplication(applicationId); - if (System.currentTimeMillis() - startTime > twoHoursMs) { - log.warn("After exception, 2-hour window passed for applicationId {}. Marking NDG_FAILED.", applicationId); - checkApp.setNdgStatus(GepafinConstant.NDG_FAILED); - applicationRepository.save(checkApp); - - futureRef.get().cancel(false); - shutdownScheduler(applicationId); - } - } catch (Exception ignore) { - futureRef.get().cancel(false); - shutdownScheduler(applicationId); - } - } - }; - - // Schedule pollingTask: run now (delay=0), then every fifteen minutes: - ScheduledFuture future = scheduler.scheduleWithFixedDelay(pollingTask, 0, // initial delay = 0 min → run immediately - fifteenMin, // subsequent runs every 15 minutes - TimeUnit.MINUTES); - futureRef.set(future); - } - - private void shutdownScheduler(Long applicationId) { - - ScheduledExecutorService shed = executorMap.remove(applicationId); - if (shed != null) { - shed.shutdownNow(); - } - log.info("Scheduler shut down for applicationId: {}", applicationId); - } - - private void processNdgGeneration(Long applicationId) { - // Validate application, company, and hub - log.info("Starting NDG generation process for applicationId: {}", applicationId); - ApplicationEntity application = applicationService.validateApplication(applicationId); - CompanyEntity company = companyService.validateCompany(application.getCompanyId()); - HubEntity hub = hubRepository.findByHubId(application.getHubId()); - - if (!hub.getUniqueUuid().equals(defaultHubUuid)) { - log.info("Ndg cannot be created for another Hub, it is default for Gepafin."); - throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.NO_NDG_FOR_ANOTHER_HUB)); - } - - try { - // Authenticate and fetch token if required - if (hub.getAppointmentAuthTokenId() == null || hub.getAreaCode() == null) { - authenticateAndSaveToken(hub, application); - } - - String authorizationToken = getBearerToken(hub); - - // Try retrieving NDG by VAT number - AppointmentLoginResponse ndgResponse = retrieveNdgByVatNumber(company.getVatNumber(), authorizationToken, hub, application); - if (isNdgValid(ndgResponse.getNdg())) { - saveNdgAndIdVisura(application, company, ndgResponse.getNdg()); - log.info("NDG successfully generated for applicationId: {}", applicationId); - } else { - log.info("Polling for NDG for applicationId: {}", applicationId); - handleNdgPolling(application, company, hub, authorizationToken); - } - } catch (Exception e) { - log.error("Exception occurred during NDG generation. ApplicationId: {}, CompanyId: {}, HubId: {}, Error: {}", applicationId, company.getId(), hub.getId(), - e.getMessage(), e); - } - } - - private void handleNdgPolling(ApplicationEntity application, CompanyEntity company, HubEntity hub, String authorizationToken) { - - log.info("Starting singleâ€shot NDG polling attempt for applicationId: {}, CompanyId: {}, HubId: {}", application.getId(), company.getId(), hub.getId()); - - long startTime = System.currentTimeMillis(); - long twoHoursMs = TimeUnit.HOURS.toMillis(2); - - try { - // 1) If NDG was already populated (perhaps by another thread), skip polling. - if (application.getNdg() != null) { - log.info("NDG already present for applicationId {}. Exiting singleâ€shot polling.", application.getId()); - return; - } - - // 2) Attempt to create Visura (this may immediately return a valid NDG) - AppointmentLoginResponse visuraResponse = createVisura(company, authorizationToken, hub); - - // 2a) If createVisura gave us a valid NDG, persist & exit - String fetchedNdg = visuraResponse.getNdg(); - if (isNdgValid(fetchedNdg)) { - log.info("Valid NDG retrieved from createVisura(): {} | applicationId: {}", fetchedNdg, application.getId()); - - company.setNdg(fetchedNdg); - application.setNdg(fetchedNdg); - application.setNdgStatus(GepafinConstant.NDG_GENERATED); - application.setStatus(ApplicationStatusTypeEnum.NDG.getValue()); - application.setIdVisura(visuraResponse.getIdVisura()); - - companyRepository.save(company); - applicationRepository.save(application); - - ApplicationEvaluationEntity eval = applicationEvaluationService.validateApplicationEvaluation(application.getApplicationEvaluationId()); - Map placeholders = new HashMap<>(); - placeholders.put("{{call_name}}", application.getCall().getName()); - placeholders.put("{{protocol_number}}", String.valueOf(application.getProtocol().getProtocolNumber())); - notificationDao.sendNotificationToInstructor(placeholders, eval, NotificationTypeEnum.NDG_GENERATION); - notificationDao.sendNotificationToSuperUser(application, placeholders, NotificationTypeEnum.NDG_GENERATION); - - log.info("NDG saved successfully for applicationId: {}", application.getId()); - return; - } - - // 2b) If no immediate NDG, fetch the Visura list JSON and parse out NDG - String visuraListJson = getVisuraList(visuraResponse.getIdVisura(), authorizationToken, application, hub); - log.debug("Parsing NDG from VisuraList JSON for applicationId: {}", application.getId()); - String parsedNdg = parseNdgFromVisuraListResponse(visuraListJson); - - if (isNdgValid(parsedNdg)) { - log.info("Valid NDG parsed from VisuraList: {} | applicationId: {}", parsedNdg, application.getId()); - - company.setNdg(parsedNdg); - application.setNdg(parsedNdg); - application.setNdgStatus(GepafinConstant.NDG_GENERATED); - application.setStatus(ApplicationStatusTypeEnum.NDG.getValue()); - application.setIdVisura(visuraResponse.getIdVisura()); - - companyRepository.save(company); - applicationRepository.save(application); - - ApplicationEvaluationEntity eval = applicationEvaluationService.validateApplicationEvaluation(application.getApplicationEvaluationId()); - Map placeholders = new HashMap<>(); - placeholders.put("{{call_name}}", application.getCall().getName()); - placeholders.put("{{protocol_number}}", String.valueOf(application.getProtocol().getProtocolNumber())); - notificationDao.sendNotificationToInstructor(placeholders, eval, NotificationTypeEnum.NDG_GENERATION); - notificationDao.sendNotificationToSuperUser(application, placeholders, NotificationTypeEnum.NDG_GENERATION); - - log.info("NDG saved successfully for applicationId: {}", application.getId()); - return; - } - - // 3) Neither direct API nor parsing yielded a valid NDG. Check timeout. - if (System.currentTimeMillis() - startTime > twoHoursMs) { - log.warn("NDG polling timed out after 2 hours for applicationId: {}", application.getId()); - application.setNdgStatus(GepafinConstant.NDG_FAILED); - applicationRepository.save(application); - return; - } - - // 4) No NDG yet—just return. The scheduler will retry in 15 minutes. - log.info("No valid NDG yet for applicationId {}. Returning to scheduler—next attempt in 15 minutes.", application.getId()); - } catch (Exception e) { - log.error("Exception during NDG polling for applicationId: {}", application.getId(), e); - // If timeout after exception, mark NDG_FAILED - if (System.currentTimeMillis() - startTime > twoHoursMs) { - log.warn("Example: exiting singleâ€shot polling due to timeout after exception for applicationId: {}", application.getId()); - application.setNdgStatus(GepafinConstant.NDG_FAILED); - applicationRepository.save(application); - } else { - log.info("Exception occurred but not timed out for applicationId {}. Returning to scheduler for next attempt in 15 minutes.", application.getId()); - } - } - - log.info("NDG polling completed for applicationId: {}", application.getId()); - } - - private static String getBearerToken(HubEntity hub) { - - return "Bearer " + hub.getAppointmentAuthTokenId(); - } - - private boolean isNdgValid(String ndg) { - - return ndg != null && !ndg.isEmpty(); - } - - private void saveNdgAndIdVisura(ApplicationEntity application, CompanyEntity company, String ndg) { - - ApplicationEntity oldApplication = Utils.getClonedEntityForData(application); - application.setNdg(ndg); - application.setNdgStatus(GepafinConstant.NDG_GENERATED); - application.setStatus(ApplicationStatusTypeEnum.NDG.getValue()); - company.setNdg(ndg); - companyRepository.save(company); - applicationRepository.save(application); - loggingUtil.addVersionHistory( - VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldApplication).newData(application).build()); - ApplicationEvaluationEntity applicationEvaluationEntity = applicationEvaluationService.validateApplicationEvaluation(application.getApplicationEvaluationId()); -// Map placeHolders = notificationDao.sendNotificationToBeneficiary(application, NotificationTypeEnum.NDG_GENERATION); - Map placeHolders = new HashMap<>(); - placeHolders.put("{{call_name}}", application.getCall().getName()); - placeHolders.put("{{protocol_number}}", String.valueOf(application.getProtocol().getProtocolNumber())); - notificationDao.sendNotificationToInstructor(placeHolders, applicationEvaluationEntity, NotificationTypeEnum.NDG_GENERATION); - notificationDao.sendNotificationToSuperUser(application, placeHolders, NotificationTypeEnum.NDG_GENERATION); - log.info("NDG saved for applicationId: {}, {}", application.getId(), application.getNdg()); - } - - private String getVisuraList(String idVisura, String authorizationToken, ApplicationEntity application, HubEntity hub) { - - log.info("Initiating Visura list retrieval | ApplicationId: {}, HubId: {}, IdVisura: {}", application.getId(), hub.getId(), idVisura); - AppointmentVisuraListRequest visuraListRequest = new AppointmentVisuraListRequest(); - AppointmentVisuraListRequest.VisuraFilter filter = new AppointmentVisuraListRequest.VisuraFilter(); - filter.setIdVisura(idVisura); - visuraListRequest.setFilter(filter); - - try { - String requestJson = Utils.convertObjectToJson(visuraListRequest); - ResponseEntity response = appointmentApiService.getVisuraList(requestJson, authorizationToken); - return Utils.convertObjectToJson(response.getBody()); - } catch (FeignException.Forbidden forbiddenException) { - log.warn("403 Forbidden while fetching Visura list. Attempting token regeneration | ApplicationId: {}, HubId: {}", application.getId(), hub.getId()); - // Regenerate the token and retry - String newAuthorizationToken = regenerateTokenAndSave(hub, application); - return getVisuraList(idVisura, newAuthorizationToken, application, hub); - } catch (Exception e) { - log.error("Error while fetching Visura list | ApplicationId: {}, HubId: {}, Error: {}", application.getId(), hub.getId(), e.getMessage(), e); - throw new RuntimeException("Error fetching Ndg List", e); - } - } - - private AppointmentLoginResponse retrieveNdgByVatNumber(String vatNumber, String authorizationToken, HubEntity hub, ApplicationEntity application) { - - try { - log.info("Initiating NDG retrieval by VAT number | ApplicationId: {}, HubId: {}, VAT: {}", application.getId(), hub.getId(), vatNumber); - // Prepare the NDG request - AppointmentNdgRequest ndgRequest = getAppointmentNdgRequest(vatNumber); - // Call the API to retrieve NDG - ResponseEntity response = appointmentApiService.getNdgByVatNumber(ndgRequest, authorizationToken); - String responseJson = Utils.convertObjectToJson(response.getBody()); - // Parse and return the NDG response - return parseNdgResponse(responseJson); - } catch (FeignException.Forbidden forbiddenException) { - log.error("403 Forbidden during NDG retrieval | ApplicationId: {}, HubId: {}", application.getId(), hub.getId()); - logForbiddenError(); - // Regenerate the token and retry - String newAuthorizationToken = regenerateTokenAndSave(hub, application); - return retrieveNdgByVatNumber(vatNumber, newAuthorizationToken, hub, application); - } catch (Exception e) { - log.error("Error during NDG retrieval | ApplicationId: {}, HubId: {}, Message: {}", application.getId(), hub.getId(), e.getMessage(), e); - throw new RuntimeException("NDG retrieval failed.", e); - } - } - - private String regenerateTokenAndSave(HubEntity hub, ApplicationEntity application) { - - hub = authenticateAndSaveToken(hub, application); - return "Bearer " + hub.getAppointmentAuthTokenId(); - } - - private AppointmentLoginResponse createVisura(CompanyEntity company, String authorizationToken, HubEntity hub) { - - try { - String visuraRequest = getAppointmentVisuraRequest(company, hub.getAreaCode()); - ResponseEntity response = appointmentApiService.createVisura(visuraRequest, authorizationToken); - String responseJson = Utils.convertObjectToJson(response.getBody()); - return parseVisuraResponse(responseJson); - } catch (FeignException.Forbidden forbiddenException) { - logForbiddenError(); - // Regenerate the token and retry - String newAuthorizationToken = regenerateTokenAndSave(hub, null); - return createVisura(company, newAuthorizationToken, hub); - } catch (Exception e) { - log.error("Failed to create Visura for Ndg : {}", e.getMessage()); - throw new RuntimeException("Visura creation failed for Ndg.", e); - } - } - - private static void logForbiddenError() { - - log.error("403 Forbidden received while retrieving NDG. Regenerating token..."); - } - - private static AppointmentNdgRequest getAppointmentNdgRequest(String vatNumber) { - - log.info("Creating Appointment NDG Request | VAT Number: {}", vatNumber); - AppointmentNdgRequest request = new AppointmentNdgRequest(); - AppointmentNdgRequest.Filter filter = new AppointmentNdgRequest.Filter(); - filter.setPartitaIva(vatNumber); - - AppointmentNdgRequest.Pagination pagination = new AppointmentNdgRequest.Pagination(); - pagination.setTargetPage(AppointmentApiConstant.TARGET_PAGE_SIZE); - pagination.setRecordsPerPage(AppointmentApiConstant.RECORD_PER_PAGE_SIZE); - - request.setFilter(filter); - request.setPagination(pagination); - return request; - } - - private static String getAppointmentVisuraRequest(CompanyEntity company, String areaCode) { - - AppointmentVisuraRequest visuraRequest = new AppointmentVisuraRequest(); - AppointmentVisuraRequest.VisuraInput input = new AppointmentVisuraRequest.VisuraInput(); - input.setPartitaIva(company.getVatNumber()); - input.setCodiceFiscale(company.getCodiceFiscale()); - input.setCodArea(areaCode); - input.setVisuraMode(AppointmentApiConstant.VISURA_MODE); - input.setVisuraProvider(AppointmentApiConstant.VISURA_PROVIDER); - input.setCodAgente(AppointmentApiConstant.COD_AGENTE); - input.setAnagraficaLegame(AppointmentApiConstant.IS_ANAGRAFICA_LEGAME); - input.setCreaAnagrafica(AppointmentApiConstant.CREA_ANAGRAFICA); - input.setFromRating(AppointmentApiConstant.IS_FROM_RATING); - input.setSalvaDocumenti(AppointmentApiConstant.SALVA_DOCUMENTI); - input.setVisuraType(AppointmentApiConstant.VISURA_TYPE); - visuraRequest.setInput(input); - return Utils.convertObjectToJson(visuraRequest); - } - - private String parseNdgFromVisuraListResponse(String jsonResponse) { - - try { - ObjectMapper objectMapper = new ObjectMapper(); - JsonNode rootNode = objectMapper.readTree(jsonResponse); - JsonNode dataNode = rootNode.get(GepafinConstant.DATA_STRING); - - if (dataNode != null && dataNode.isArray() && dataNode.size() > 0) { - JsonNode firstEntry = dataNode.get(0); - JsonNode ndgClienteNode = firstEntry.get("ndgCliente"); - if (ndgClienteNode != null && ndgClienteNode.get("code") != null) { - String code = ndgClienteNode.get("code").asText(); - return normalizeNullValue(code); - } - } - log.warn("NDG not found in Visura List API response."); - return null; - } catch (Exception e) { - log.error("Failed to parse NDG from Visura List API response: {}", e.getMessage(), e); - throw new RuntimeException("Error parsing NDG from Visura List API response", e); - } - } - - public AppointmentLoginResponse parseLoginResponse(String jsonResponse) { - - try { - ObjectMapper objectMapper = new ObjectMapper(); - JsonNode rootNode = objectMapper.readTree(jsonResponse); - JsonNode dataNode = rootNode.get(GepafinConstant.DATA_STRING); - - if (dataNode != null) { - AppointmentLoginResponse response = new AppointmentLoginResponse(); - response.setTokenId(dataNode.get("tokenId").asText()); - JsonNode areasNode = dataNode.get("areas"); - if (areasNode != null && areasNode.isArray() && areasNode.size() > 0) { - response.setAreaCode(areasNode.get(0).get("code").asText()); - } - response.setCompanyId(dataNode.get("companyId").asLong()); - return response; - } else { - throw new RuntimeException("Invalid JSON structure: Missing 'data' node."); - } - } catch (Exception e) { - throw new RuntimeException("Failed to parse response from loginApi for odessa: " + e.getMessage(), e); - } - } - - public AppointmentLoginResponse parseVisuraResponse(String jsonResponse) { - - try { - // Log full raw JSON for debug purposes - log.info("Raw Visura JSON Response: {}", jsonResponse); - ObjectMapper objectMapper = new ObjectMapper(); - JsonNode rootNode = objectMapper.readTree(jsonResponse); - JsonNode dataNode = rootNode.get(GepafinConstant.DATA_STRING); - - if (dataNode != null && dataNode.isObject()) { - AppointmentLoginResponse response = new AppointmentLoginResponse(); - JsonNode idVisuraNode = dataNode.get(GepafinConstant.ID_VISURA_STRING); - JsonNode ndgNode = dataNode.get(GepafinConstant.NDG_STRING); - if (idVisuraNode == null || ndgNode == null) { - log.error("Missing expected fields in 'data' node. JSON: {}", dataNode); - } - response.setIdVisura(normalizeNullValue(idVisuraNode != null ? idVisuraNode.asText() : null)); - response.setNdg(normalizeNullValue(ndgNode != null ? ndgNode.asText() : null)); - return response; - } else { - System.err.println("Invalid JSON: 'data' node is missing or not an object."); - throw new RuntimeException("Invalid JSON structure: Missing or malformed 'data' node."); - } - } catch (Exception e) { - System.err.println("Exception while parsing Visura response: " + e.getMessage()); - throw new RuntimeException("Failed to parse response: " + e.getMessage(), e); - } - } - - public AppointmentLoginResponse parseNdgResponse(String jsonResponse) { - - try { - ObjectMapper objectMapper = new ObjectMapper(); - JsonNode rootNode = objectMapper.readTree(jsonResponse); - JsonNode dataArray = rootNode.get(GepafinConstant.DATA_STRING); - if (dataArray == null || !dataArray.isArray() || dataArray.isEmpty()) { - log.info("NDG data is empty or missing in the response."); - AppointmentLoginResponse emptyResponse = new AppointmentLoginResponse(); - emptyResponse.setNdg(null); - return emptyResponse; - } - JsonNode firstDataEntry = dataArray.get(0); - AppointmentLoginResponse response = new AppointmentLoginResponse(); - if (firstDataEntry.has(GepafinConstant.NDG_STRING)) { - response.setNdg(normalizeNullValue(firstDataEntry.get(GepafinConstant.NDG_STRING).asText())); - } - return response; - } catch (Exception e) { - log.error("Failed to parse response: {}", e.getMessage(), e); - throw new RuntimeException("Failed to parse NDG response.", e); - } - } - - private String normalizeNullValue(String value) { - - return (value == null || GepafinConstant.NULL_STRING.equalsIgnoreCase(value.trim())) ? null : value; - } - - public AppointmentCreationResponse createAppointment(Long applicationId, CreateAppointmentRequest createAppointmentRequest) { - // Validate the application - log.info("Starting appointment creation for applicationId: {}", applicationId); - ApplicationEntity application = applicationService.validateApplication(applicationId); - - AppointmentCreationResponse appointmentCreationResponse = new AppointmentCreationResponse(); - - ApplicationEntity oldApplicationData = Utils.getClonedEntityForData(application); - HubEntity hub = hubRepository.findByHubId(application.getHubId()); - - // Check hub UUID and enforce constraints - if (!hub.getUniqueUuid().equals(defaultHubUuid)) { - log.info("Appointment cannot be created for another Hub; default is required for Gepafin."); - throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.NO_APPOINTMENT_FOR_ANOTHER_HUB)); - } - - try { - // Pre-check conditions for appointment creation - if (application.getNdg() != null && !Objects.equals(application.getNdgStatus(), GepafinConstant.NDG_IN_PROGRESS) && application.getAppointmentId() != null) { - appointmentCreationResponse.setAppointmentId(application.getAppointmentId()); - throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.APPOINTMENT_ALREADY_CREATED)); - // return appointmentCreationResponse; - } - - if (application.getNdg() == null && Objects.equals(application.getNdgStatus(), GepafinConstant.NDG_IN_PROGRESS)) { - log.warn("NDG in progress but not available for applicationId: {}", applicationId); - throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.NDG_NOT_FOUND_FOR_APPLICATION)); - } - - // Generate authorization token and fetch template data - String authorizationToken = regenerateTokenAndSave(hub, application); - Long appointmentTemplateId = application.getCall().getAppointmentTemplateId(); - if (appointmentTemplateId == null) { - log.error("Missing appointment template ID for applicationId: {}", applicationId); - throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.APPOINTMENT_CANNOT_BE_CREATED)); - } - ResponseEntity response = appointmentApiService.getAppointmentTemplateForTemplateCreation(authorizationToken, appointmentTemplateId); - - if (response.getStatusCode() != HttpStatus.OK) { - log.error("Failed to retrieve appointment template for appointment creation. Status: {}", response.getStatusCode()); - throw new IllegalStateException("Failed to retrieve appointment template for appointment creation"); - } - - // Parse template data - String responseDataForTemplate = Utils.convertObjectToJson(response.getBody()); - AppointmentCreationRequest templateRichiestaData = parseTemplateResponseData(responseDataForTemplate); - - // Build the appointment request body - AppointmentCreationRequest appointmentCreationRequest = buildAppointmentCreationRequest(applicationId, createAppointmentRequest, appointmentTemplateId, - templateRichiestaData); - log.info("AppointmentCreationRequest : {}", appointmentCreationRequest); - String appointmentRequestBody = Utils.convertObjectToJson(appointmentCreationRequest); - - // Make API call to create the appointment - log.info("Context:{}, Authorization Token : {}, RequestBody : {}", context, authorizationToken, appointmentRequestBody); - ResponseEntity appointmentResponse = appointmentApiService.createAppointment(authorizationToken, context, appointmentRequestBody); - String appointmentId = extractAppointmentIdFromResponse(appointmentResponse); - - if (appointmentId == null) { - log.error("Failed to extract appointment ID from response for applicationId: {}", applicationId); - throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.APPOINTMENT_NOT_CREATED)); - } - // Update application with the appointment ID - application.setAppointmentId(appointmentId); - application.setStatus(ApplicationStatusTypeEnum.APPOINTMENT.getValue()); - applicationRepository.save(application); - - // Log version history - loggingUtil.addVersionHistory( - VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldApplicationData).newData(application).build()); - - appointmentCreationResponse.setAppointmentId(appointmentId); - return appointmentCreationResponse; - - } catch (FeignException.Forbidden forbiddenException) { - log.error("403 Forbidden received while retrieving template. Attempting to regenerate token and retry. Application ID: {}", applicationId); - regenerateTokenAndSave(hub, application); - return createAppointment(applicationId, createAppointmentRequest); - } - } - - private String extractAppointmentIdFromResponse(ResponseEntity appointmentResponse) { - - if (appointmentResponse.getBody() != null) { - log.info("Appointment API Response : {}", appointmentResponse.getBody()); - try { - Map responseBody = (Map) appointmentResponse.getBody(); - // 1. Try to get appointment ID from data.id - if (responseBody.containsKey(GepafinConstant.DATA_STRING)) { - Map data = (Map) responseBody.get(GepafinConstant.DATA_STRING); - if (data != null && data.containsKey(GepafinConstant.ID_STRING)) { - return data.get(GepafinConstant.ID_STRING).toString(); - } - } - // 2. If ID not present, check errors[0].cause.errorDescription - if (responseBody.containsKey(GepafinConstant.ERROR_STRING)) { - List> errors = (List>) responseBody.get(GepafinConstant.ERROR_STRING); - if (errors != null && !errors.isEmpty()) { - Map firstError = errors.get(0); - if (firstError.containsKey(GepafinConstant.CAUSE_STRING)) { - Map cause = (Map) firstError.get(GepafinConstant.CAUSE_STRING); - if (cause != null && cause.containsKey(GepafinConstant.ERROR_DESCRIPTION_STRING)) { - String errorDescription = cause.get(GepafinConstant.ERROR_DESCRIPTION_STRING).toString(); - log.warn("Appointment creation failed: {}", errorDescription); - } - } - } - } - } catch (Exception e) { - log.error("Error while extracting appointment ID or parsing error message", e); - } - } - return null; - } - - public AppointmentCreationRequest parseTemplateResponseData(String jsonResponse) { - - try { - - ObjectMapper objectMapper = new ObjectMapper(); - JsonNode rootNode = objectMapper.readTree(jsonResponse); - JsonNode richiesteClienteArray = rootNode.path(GepafinConstant.DATA_STRING).path(GepafinConstant.RICHIESTE_CLIENTE_STRING); - - // Initialize the result object - AppointmentCreationRequest appointmentCreationRequest = new AppointmentCreationRequest(); - AppointmentCreationRequest.Input input = new AppointmentCreationRequest.Input(); - List richiestaClienteList = new ArrayList<>(); - if (!richiesteClienteArray.isArray()) { - log.warn("richiesteCliente array is missing or not an array."); - return new AppointmentCreationRequest(); - } - for (JsonNode richiestaNode : richiesteClienteArray) { - if (richiestaNode.isNull()) - continue; - - AppointmentCreationRequest.RichiestaCliente richiestaCliente = new AppointmentCreationRequest.RichiestaCliente(); - JsonNode prodottoNode = richiestaNode.path(AppointmentApiConstant.PRODOTTO); - String prodottoCode = prodottoNode.path(AppointmentApiConstant.PRODOTTO_CODE).asText(); - - richiestaCliente.setCodProdotto(prodottoCode); - richiestaCliente.setIdMotivazione(getIntValue(richiestaNode)); - richiestaCliente.setCodAbi(getTextValue(richiestaNode, AppointmentApiConstant.COD_ABI)); - richiestaCliente.setCodCab(getTextValue(richiestaNode, AppointmentApiConstant.COD_CAB)); - richiestaCliente.setIdNota(getTextValue(richiestaNode, AppointmentApiConstant.ID_NOTA)); - richiestaCliente.setImportoAgevolato(getTextValue(richiestaNode, AppointmentApiConstant.IMPORTO_AGEVOLATO)); - richiestaCliente.setImportoMedioLungoTermine(getTextValue(richiestaNode, AppointmentApiConstant.IMPORTO_MEDIOLUNGO_TERMINE)); - richiestaCliente.setCodTipoProdotto(getTextValue(richiestaNode, AppointmentApiConstant.COD_TIPO_PRODOTTO)); - richiestaCliente.setCodCategoriaProdotto(getTextValue(richiestaNode, AppointmentApiConstant.COD_CATEGORIA_PRODOTTO)); - richiestaCliente.setCodFormaTecnica(getTextValue(richiestaNode, AppointmentApiConstant.COD_FORMATECNICA)); - richiestaCliente.setCodOperazione(getTextValue(richiestaNode, AppointmentApiConstant.COD_OPERAZIONE)); - - richiestaClienteList.add(richiestaCliente); - } - - input.setRichiestaCliente(richiestaClienteList); - appointmentCreationRequest.setInput(input); - - return appointmentCreationRequest; - - } catch (JsonProcessingException e) { - log.error("JSON processing error: {}", e.getMessage(), e); - throw new IllegalStateException("Invalid JSON structure in template response", e); - } - } - - private String getTextValue(JsonNode node, String fieldName) { - - return node.path(fieldName).isTextual() ? node.path(fieldName).asText() : null; - } - - private int getIntValue(JsonNode node) { - - return node.path(AppointmentApiConstant.MOTIVAZIONE).path(AppointmentApiConstant.MOTIVAZIONE_ID).asInt(); - } - - public AppointmentCreationRequest buildAppointmentCreationRequest(Long applicationId, CreateAppointmentRequest createAppointmentRequest, Long areaCode, - AppointmentCreationRequest templateRichiestaData) { - - ApplicationEntity application = applicationService.validateApplication(applicationId); - CreateAppointmentRequest.Nota nota = createAppointmentRequest.getNota(); - - AppointmentCreationRequest appointmentCreationRequest = new AppointmentCreationRequest(); - AppointmentCreationRequest.Input input = new AppointmentCreationRequest.Input(); - - // Set Input Fields - input.setId(areaCode); - input.setNdg(application.getNdg()); - - // Populate richiestaCliente from template data - List richiestaClienteList = new ArrayList<>(); - for (AppointmentCreationRequest.RichiestaCliente templateRichiesta : templateRichiestaData.getInput().getRichiestaCliente()) { - AppointmentCreationRequest.RichiestaCliente richiestaCliente = new AppointmentCreationRequest.RichiestaCliente(); - BeanUtils.copyProperties(templateRichiesta, richiestaCliente); - - // Add specific `nota` - AppointmentCreationRequest.Nota requestNota = new AppointmentCreationRequest.Nota(); - requestNota.setTitolo(nota.getTitolo()); - requestNota.setTesto(nota.getTesto()); - richiestaCliente.setNota(requestNota); - richiestaCliente.setDurataMesiFinanziamento(createAppointmentRequest.getDurataMesiFinanziamento()); - richiestaCliente.setImportoBreveTermine(createAppointmentRequest.getImportoBreveTermine()); - richiestaClienteList.add(richiestaCliente); - } - - input.setRichiestaCliente(richiestaClienteList); - appointmentCreationRequest.setInput(input); - return appointmentCreationRequest; - } - - public DocumentUploadResponse uploadDocumentToExternalSystem(Long documentId, UploadDocToExternalSystemRequest docToExternalSystemRequest) { - log.info("Initiating upload to external system for documentId: {}", documentId); - // Check if the document is already being processed - DocumentEntity systemDoc = documentDao.validateDocument(documentId); - - ApplicationEntity application = null; - - if (systemDoc != null) { - DocumentSourceTypeEnum sourceType = DocumentSourceTypeEnum.valueOf(systemDoc.getSource()); - - switch (sourceType) { - case APPLICATION: - application = applicationDao.validateApplication(systemDoc.getSourceId()); - break; - case AMENDMENT: - ApplicationAmendmentRequestEntity applicationAmendmentEntity = applicationAmendmentRequestDao.validateApplicationAmendmentRequest(systemDoc.getSourceId()); - application = applicationDao.validateApplication(applicationAmendmentEntity.getApplicationId()); - break; - case EVALUATION: - ApplicationEvaluationEntity applicationEvaluationEntity = applicationEvaluationDao.validateApplicationEvaluation(systemDoc.getSourceId()); - application = applicationDao.validateApplication(applicationEvaluationEntity.getApplicationId()); - break; - - case CALL: - break; - - default: - log.warn("Unhandled document source type: {}", sourceType); - break; - } - } - - Claims claims = tokenProvider.getClaimsFromToken(tokenProvider.extractTokenFromRequest(request)); - Long hubId = Utils.extractHubIdFromPayload(claims.getSubject()); - - // Authenticate the hub before proceeding - HubEntity hub = hubRepository.findByHubId(hubId); - authenticateAndSaveToken(hub, application); - if (systemDoc != null && systemDoc.getDocumentAttachmentId() != null) { - // If the documentAttachmentId is already set, return the response - log.info("Document already uploaded with documentAttachmentId: {}", systemDoc.getDocumentAttachmentId()); - DocumentUploadResponse response = new DocumentUploadResponse(); - response.setDocumentAttachmentId(systemDoc.getDocumentAttachmentId()); - return response; - } - // Check if a thread is already running for this document upload - if (threadForDocumentMap.containsKey(documentId)) { - log.warn("Document upload already running for documentId: {}", documentId); - throw new CustomValidationException(Status.SUCCESS, Translator.toLocale(GepafinConstant.DOCUMENT_UPLOADING_IN_PROGRESS)); - } - // Start the upload process in the background - ExecutorService executor = Executors.newSingleThreadExecutor(runnable -> { - Thread thread = new Thread(runnable); - thread.setName(GepafinConstant.ASYNC_DOCUMENT_UPLOAD_NAME + documentId); - return thread; - }); - threadForDocumentMap.put(documentId, executor); - - ApplicationEntity finalApplication = application; - executor.submit(() -> { - threadLocalHubId.set(hubId); - try { - log.info("Starting async document upload for documentId: {}", documentId); - uploadDocumentToExternalSystemSync(documentId, docToExternalSystemRequest, finalApplication); - } catch (Exception e) { - log.error("Error in async document upload for documentId: {}", documentId, e); - } finally { - // Cleanup resources - ExecutorService executorToShutdown = threadForDocumentMap.remove(documentId); - if (executorToShutdown != null) { - executorToShutdown.shutdown(); - threadLocalHubId.remove(); - } - log.info("Async document upload completed for documentId: {}", documentId); - } - }); - return null; - } - - private void uploadDocumentToExternalSystemSync(Long documentId, UploadDocToExternalSystemRequest docToExternalSystemRequest, ApplicationEntity application) { - // Synchronous upload logic - DocumentEntity systemDoc = documentDao.validateDocument(documentId); - - Long hubId = threadLocalHubId.get(); - HubEntity hub = hubRepository.findByHubId(hubId); - - if (!hub.getUniqueUuid().equals(defaultHubUuid)) { - log.info("Document cannot be uploaded for another Hub, it is default for Gepafin."); - throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.NO_DOCUMENT_UPLOAD_FOR_ANOTHER_HUB)); - } - - log.info("Got Document in system: {}", systemDoc); - String oldUrl = systemDoc.getFilePath(); - String authorizationToken = getBearerToken(hub); - - try { - File localFile = downloadFileFromS3(oldUrl); - MultipartFile multipartFile = convertFileToMultipartFile(localFile); - - UploadDocToExternalSystemRequest externalSystemRequest = new UploadDocToExternalSystemRequest(); - externalSystemRequest.setInput(getUploadDocumentInput(docToExternalSystemRequest)); - - String uploadDocRequest = Utils.convertObjectToJson(externalSystemRequest); - ResponseEntity uploadedDocumentData = appointmentApiService.uploadDocumentToExternalSystemForAppointment(authorizationToken, context, uploadDocRequest, - multipartFile); - - String responseData = Utils.convertObjectToJson(uploadedDocumentData.getBody()); - DocumentUploadResponse parsedResponse = parseDocumentUploadResponse(responseData); - - if (parsedResponse == null) { - log.error("Upload failed: parsed response is null for documentId: {}", documentId); - throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.ERROR_UPLOADING_DOCUMENT)); - } - - // Save the documentAttachmentId to the database - systemDoc.setDocumentAttachmentId(parsedResponse.getDocumentAttachmentId()); - documentRepository.save(systemDoc); - - log.info("Document uploaded successfully to external system: {}", parsedResponse); - } catch (FeignException.Forbidden forbiddenException) { - log.error("403 Forbidden from external system during upload for documentId: {}. Retrying with new token...", documentId); - regenerateTokenAndSave(hub, application); - uploadDocumentToExternalSystemSync(documentId, docToExternalSystemRequest, application); - } catch (Exception e) { - log.error("Exception during document upload: {}", e.getMessage(), e); - throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.EXTERNAL_DOCUMENT_UPLOAD_FAILURE_MSG)); - } - } - - private UploadDocToExternalSystemRequest.Input getUploadDocumentInput(UploadDocToExternalSystemRequest docToExternalSystemRequest) { - - UploadDocToExternalSystemRequest.Input input = new UploadDocToExternalSystemRequest.Input(); - input.setIdTipoProtocollo(docToExternalSystemRequest.getInput().getIdTipoProtocollo()); - input.setIdClassificazione(docToExternalSystemRequest.getInput().getIdClassificazione()); - input.setFlagDaFirmare(docToExternalSystemRequest.getInput().getFlagDaFirmare()); - input.setDescrizione(docToExternalSystemRequest.getInput().getDescrizione()); - - UploadDocToExternalSystemRequest.Input.Attributes attributes = new UploadDocToExternalSystemRequest.Input.Attributes(); - attributes.setNdg(docToExternalSystemRequest.getInput().getAttributes().getNdg()); - attributes.setEmail(docToExternalSystemRequest.getInput().getAttributes().getEmail()); - - input.setAttributes(attributes); - return input; - } - - public static MultipartFile convertFileToMultipartFile(File file) throws IOException { - - FileInputStream input = new FileInputStream(file); - return new MockMultipartFile(file.getName(), file.getName(), MediaType.APPLICATION_OCTET_STREAM_VALUE, input); - } - - private File downloadFileFromS3(String fileUrl) throws Exception { - - String key = amazonS3Service.extractS3KeyFromUrl(fileUrl); - String fileName = extractFileName(key); - String folderPath = key.substring(0, key.lastIndexOf("/")); - File localFile = new File(GepafinConstant.TEMP_FILE_PATH + fileName); - - try (InputStream s3Stream = amazonS3Service.getFile(folderPath, key); FileOutputStream outputStream = new FileOutputStream(localFile)) { - s3Stream.transferTo(outputStream); - } - - log.info("Downloaded file from old S3 bucket: {}", key); - return localFile; - } - - private String extractFileName(String filePath) { - - String[] parts = filePath.split("/"); - return parts[parts.length - 1]; - - } - - public DocumentUploadResponse parseDocumentUploadResponse(String jsonResponse) { - - try { - ObjectMapper objectMapper = new ObjectMapper(); - JsonNode rootNode = objectMapper.readTree(jsonResponse); - - // Navigate to the "data" node - JsonNode dataNode = rootNode.get(GepafinConstant.DATA_STRING); - if (dataNode != null) { - DocumentUploadResponse response = new DocumentUploadResponse(); - - // Extract "documentAttachmentId" - JsonNode documentAttachmentIdNode = dataNode.get(GepafinConstant.DOCUMENT_ATTACHMENT_ID_STRING); - if (documentAttachmentIdNode != null) { - response.setDocumentAttachmentId(documentAttachmentIdNode.asText()); - } else { - throw new RuntimeException("Invalid JSON structure: Missing 'documentAttachmentId' node."); - } - - return response; - } else { - return null; - } - } catch (Exception e) { - throw new RuntimeException("Failed to parse response: " + e.getMessage(), e); - } - } +package net.gepafin.tendermanagement.dao; + +import com.amazonaws.services.s3.AmazonS3Client; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import feign.FeignException; +import io.jsonwebtoken.Claims; +import jakarta.servlet.http.HttpServletRequest; +import lombok.extern.slf4j.Slf4j; +import net.gepafin.tendermanagement.config.Translator; +import net.gepafin.tendermanagement.config.jwt.TokenProvider; +import net.gepafin.tendermanagement.constants.AppointmentApiConstant; +import net.gepafin.tendermanagement.constants.GepafinConstant; +import net.gepafin.tendermanagement.entities.ApplicationAmendmentRequestEntity; +import net.gepafin.tendermanagement.entities.ApplicationEntity; +import net.gepafin.tendermanagement.entities.ApplicationEvaluationEntity; +import net.gepafin.tendermanagement.entities.CompanyEntity; +import net.gepafin.tendermanagement.entities.DocumentEntity; +import net.gepafin.tendermanagement.entities.HubEntity; +import net.gepafin.tendermanagement.enums.*; +import net.gepafin.tendermanagement.model.request.AppointmentCreationRequest; +import net.gepafin.tendermanagement.model.request.AppointmentNdgRequest; +import net.gepafin.tendermanagement.model.request.AppointmentVisuraListRequest; +import net.gepafin.tendermanagement.model.request.AppointmentVisuraRequest; +import net.gepafin.tendermanagement.model.request.CreateAppointmentRequest; +import net.gepafin.tendermanagement.model.request.UploadDocToExternalSystemRequest; +import net.gepafin.tendermanagement.model.request.VersionHistoryRequest; +import net.gepafin.tendermanagement.model.response.AppointmentCreationResponse; +import net.gepafin.tendermanagement.model.response.AppointmentLoginResponse; +import net.gepafin.tendermanagement.model.response.DocumentUploadResponse; +import net.gepafin.tendermanagement.model.response.NdgResponse; +import net.gepafin.tendermanagement.repositories.ApplicationRepository; +import net.gepafin.tendermanagement.repositories.CompanyRepository; +import net.gepafin.tendermanagement.repositories.DocumentRepository; +import net.gepafin.tendermanagement.repositories.HubRepository; +import net.gepafin.tendermanagement.repositories.UserRepository; +import net.gepafin.tendermanagement.service.AmazonS3Service; +import net.gepafin.tendermanagement.service.ApplicationService; +import net.gepafin.tendermanagement.service.CompanyService; +import net.gepafin.tendermanagement.service.feignClient.AppointmentApiService; +import net.gepafin.tendermanagement.service.impl.ApplicationEvaluationServiceImpl; +import net.gepafin.tendermanagement.util.LoggingUtil; +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.springframework.beans.BeanUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.stereotype.Component; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; +import org.springframework.web.multipart.MultipartFile; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.ScheduledExecutorService; + +@Slf4j +@Component +public class AppointmentDao { + + @Value("${appointment.portal.user}") + private String user; + + @Value("${appointment.portal.password}") + private String password; + + @Value("${appointment.portal.source}") + private String source; + + @Value("${appointment.portal.context}") + private String context; + + @Value("${default.hub.uuid}") + private String defaultHubUuid; + + @Value("${aws.s3.url}") + private String s3Url; + + @Value("${aws.s3.bucket.name}") + private String OLD_BUCKET; + + @Autowired + private HubRepository hubRepository; + + @Autowired + private AppointmentApiService appointmentApiService; + + @Autowired + private ApplicationService applicationService; + + @Autowired + private CompanyService companyService; + + @Autowired + private ApplicationRepository applicationRepository; + + @Autowired + private CompanyRepository companyRepository; + + @Autowired + private DocumentDao documentDao; + + @Autowired + private AmazonS3Client s3Client; + + @Autowired + private DocumentRepository documentRepository; + + @Autowired + private HttpServletRequest request; + + @Autowired + private LoggingUtil loggingUtil; + + @Autowired + private TokenProvider tokenProvider; + + @Autowired + private NotificationDao notificationDao; + + @Autowired + private UserRepository userRepository; + + @Autowired + private ApplicationEvaluationServiceImpl applicationEvaluationService; + + @Autowired + private AmazonS3Service amazonS3Service; + + @Autowired + private ApplicationDao applicationDao; + + @Autowired + private ApplicationAmendmentRequestDao applicationAmendmentRequestDao; + + @Autowired + private ApplicationEvaluationDao applicationEvaluationDao; + + private final Map executorMap = new ConcurrentHashMap<>(); + + + private final ConcurrentHashMap threadForDocumentMap = new ConcurrentHashMap<>(); + + private static final ThreadLocal threadLocalHubId = new ThreadLocal<>(); + + public NdgResponse checkNdgForAppointment(Long applicationId) { + log.info("Starting NDG check for appointment. applicationId: {}", applicationId); + ApplicationEntity application = applicationService.validateApplication(applicationId); + ApplicationEntity oldApplication = Utils.getClonedEntityForData(application); + + NdgResponse ndgResponse = new NdgResponse(); + if (application.getNdgStatus() != null && application.getNdgStatus().equalsIgnoreCase(GepafinConstant.NDG_IN_PROGRESS)) { + log.warn("NDG generation already in progress. applicationId: {}", applicationId); + throw new CustomValidationException(Status.SUCCESS, Translator.toLocale(GepafinConstant.NDG_GENERATION_IS_IN_PROGRESS)); + } + + if (application.getNdgStatus() != null && application.getNdgStatus().equalsIgnoreCase(GepafinConstant.NDG_GENERATED) && application.getNdg() != null) { + ndgResponse.setNdg(application.getNdg()); + return ndgResponse; + } + + // Update application status + log.info("Updating NDG status to IN_PROGRESS. applicationId: {}", applicationId); + application.setNdgStatus(GepafinConstant.NDG_IN_PROGRESS); + applicationRepository.save(application); + + loggingUtil.addVersionHistory( + VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldApplication).newData(application).build()); + + + // Start async processing + HubEntity hub = hubRepository.findByHubId(application.getHubId()); + loginToOdessa(hub, application); + startAsyncNdgProcessing(applicationId); + log.info("NDG check initiation completed. applicationId: {}", applicationId); + return ndgResponse; + } + + // private HubEntity loginToOdessa(HubEntity hub, ApplicationEntity application) { + // + // int maxRetries = 3; + // int attempt = 0; + // boolean success = false; + // while (attempt < maxRetries && !success) { + // attempt++; + // try { + // //code to generate token with payload having "iat" epoch timestamp and secret key with no expiry and send in below method call + // String authJwtToken = Utils.generateAuthTokenForLoginToOdessa(); + // log.info("Got the auth for login to odessa {}", authJwtToken); + // hub.setAuthToken(authJwtToken); + // hubRepository.save(hub); + // Map body = Collections.emptyMap(); + // ResponseEntity responseLogin = appointmentApiService.loginWithOdessa(authJwtToken, source, context, user, password, body); + // if (responseLogin.getStatusCode() == HttpStatus.OK) { + // log.info("Login successful to odessa. Parsing response."); + // String loginResponseJson = Utils.convertObjectToJson(responseLogin.getBody()); + // AppointmentLoginResponse parsedResponse = parseLoginResponse(loginResponseJson); + // + // // Validate and save token + // if (parsedResponse.getTokenId() != null) { + // hub.setAppointmentAuthTokenId(parsedResponse.getTokenId()); + // hub.setAreaCode(parsedResponse.getAreaCode()); + // hubRepository.save(hub); + // log.info("Saved new authToken and areaCode for Hub."); + // success = true; + // return hub; + // } else { + // throw new RuntimeException("Login response is missing a valid tokenId for login to odessa system, please try again."); + // } + // } + // throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.ERROR_IN_GENERATING_NDG_TRY_AGAIN)); + // } catch (FeignException.Forbidden forbiddenException) { + // log.error("Failed to login to odessa due to some error"); + // + // // Extract raw response body + // String responseBody = forbiddenException.contentUTF8(); // Extract raw JSON response + // + // // Parse JSON to check for "PasswordExpired" + // try { + // ObjectMapper objectMapper = new ObjectMapper(); + // JsonNode rootNode = objectMapper.readTree(responseBody); + // JsonNode errorsNode = rootNode.path("errors"); + // + // if (errorsNode.isArray()) { + // for (JsonNode error : errorsNode) { + // // Check the main errorCode + // if (GepafinConstant.PASSWORD_EXPIRED.equals(error.path("errorCode").asText())) { + // application.setNdgStatus(GepafinConstant.NDG_FAILED); + // applicationRepository.save(application); + // throw new CustomValidationException(Status.FORBIDDEN, Translator.toLocale(GepafinConstant.PASSWORD_EXPIRED_LOGIN_TO_ODESSA)); + // } + // + // // Check inside "subErrors" + // JsonNode subErrorsNode = error.path("subErrors"); + // if (subErrorsNode.isArray()) { + // for (JsonNode subError : subErrorsNode) { + // if (GepafinConstant.PASSWORD_EXPIRED.equals(subError.path("errorCode").asText())) { + // application.setNdgStatus(GepafinConstant.NDG_FAILED); + // applicationRepository.save(application); + // throw new CustomValidationException(Status.FORBIDDEN, Translator.toLocale(GepafinConstant.PASSWORD_EXPIRED_LOGIN_TO_ODESSA)); + // } + // } + // } + // } + // } + // } catch (IOException e) { + // log.error("Error parsing JSON response: {}", e.getMessage()); + // } + // } catch (Exception e) { + // log.error("Failed to authenticate user on Odessa : {}", e.getMessage(), e); + // throw new RuntimeException("Authentication failed on Odessa. try again", e); + // } + // } + // return null; + // } + // + // + // private HubEntity authenticateAndSaveToken(HubEntity hub) { + // + // int maxRetries = 3; + // int attempt = 0; + // boolean success = false; + // while (attempt < maxRetries && !success) { + // attempt++; + // try { + // //code to generate token with payload having "iat" epoch timestamp and secret key with no expiry and send in below method call + // String authJwtToken = Utils.generateAuthTokenForLoginToOdessa(); + // log.info("Got the auth for login to odessa {}", authJwtToken); + // hub.setAuthToken(authJwtToken); + // hubRepository.save(hub); + // // Prepare the request body (adjust if necessary for login API) + // Map body = Collections.emptyMap(); + // // Perform login API call + // ResponseEntity responseLogin = appointmentApiService.loginWithOdessa(authJwtToken, source, context, user, password, body); + // + // // Handle successful login + // if (responseLogin.getStatusCode() == HttpStatus.OK) { + // log.info("Login successful to odessa. Parsing response."); + // String loginResponseJson = Utils.convertObjectToJson(responseLogin.getBody()); + // AppointmentLoginResponse parsedResponse = parseLoginResponse(loginResponseJson); + // + // // Validate and save token + // if (parsedResponse.getTokenId() != null) { + // hub.setAppointmentAuthTokenId(parsedResponse.getTokenId()); + // hub.setAreaCode(parsedResponse.getAreaCode()); + // hubRepository.save(hub); + // + // log.info("Saved new authToken and areaCode for Hub."); + // success = true; + // return hub; + // } else { + // throw new RuntimeException("Login response is missing a valid tokenId for login to odessa system, please try again."); + // } + // } + // // Handle non-OK response + // throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.ERROR_IN_GENERATING_NDG_TRY_AGAIN)); + // } catch (FeignException.Forbidden forbiddenException) { + // log.error("Failed to login to odessa due to some error occurred."); + // + // // Extract raw response body + // String responseBody = forbiddenException.contentUTF8(); // Extract raw JSON response + // + // // Parse JSON to check for "PasswordExpired" + // try { + // ObjectMapper objectMapper = new ObjectMapper(); + // JsonNode rootNode = objectMapper.readTree(responseBody); + // JsonNode errorsNode = rootNode.path("errors"); + // + // if (errorsNode.isArray()) { + // for (JsonNode error : errorsNode) { + // // Check the main errorCode + // if (GepafinConstant.PASSWORD_EXPIRED.equals(error.path("errorCode").asText())) { + // throw new CustomValidationException(Status.FORBIDDEN, Translator.toLocale(GepafinConstant.PASSWORD_EXPIRED_LOGIN_TO_ODESSA)); + // } + // + // // Check inside "subErrors" + // JsonNode subErrorsNode = error.path("subErrors"); + // if (subErrorsNode.isArray()) { + // for (JsonNode subError : subErrorsNode) { + // if (GepafinConstant.PASSWORD_EXPIRED.equals(subError.path("errorCode").asText())) { + // throw new CustomValidationException(Status.FORBIDDEN, Translator.toLocale(GepafinConstant.PASSWORD_EXPIRED_LOGIN_TO_ODESSA)); + // } + // } + // } + // } + // } + // } catch (IOException e) { + // log.error("Error parsing JSON response: {}", e.getMessage()); + // } + // } catch (Exception e) { + // log.error("Failed to authenticate user on Odessa : {}", e.getMessage(), e); + // throw new RuntimeException("Authentication failed on Odessa. try again", e); + // } + // } + // return null; + // } + + private void loginToOdessa(HubEntity hub, ApplicationEntity application) { + log.info("Starting login to Odessa. HubId: {}, ApplicationId: {}", hub.getId(), application.getId()); + performOdessaLogin(hub, application); + } + + private HubEntity authenticateAndSaveToken(HubEntity hub, ApplicationEntity application) { + + return performOdessaLogin(hub, application); + } + + private HubEntity performOdessaLogin(HubEntity hub, ApplicationEntity application) { + + int maxRetries = 3; + int attempt = 0; + while (attempt < maxRetries) { + attempt++; + try { + String authJwtToken = Utils.generateAuthTokenForLoginToOdessa(); + log.info("Got the auth for login to odessa {}", authJwtToken); + hub.setAuthToken(authJwtToken); + hubRepository.save(hub); + Map body = Collections.emptyMap(); + ResponseEntity responseLogin = appointmentApiService.loginWithOdessa(authJwtToken, source, context, user, password, body); + if (responseLogin.getStatusCode() == HttpStatus.OK) { + log.info("Login to Odessa successful. Parsing response. HubId: {}", hub.getId()); + String loginResponseJson = Utils.convertObjectToJson(responseLogin.getBody()); + AppointmentLoginResponse parsedResponse = parseLoginResponse(loginResponseJson); + + if (parsedResponse.getTokenId() != null) { + hub.setAppointmentAuthTokenId(parsedResponse.getTokenId()); + hub.setAreaCode(parsedResponse.getAreaCode()); + hubRepository.save(hub); + log.info("Saved new authToken and areaCode for Hub."); + return hub; + } else { + log.error("Login response from Odessa missing tokenId. HubId: {}", hub.getId()); + throw new RuntimeException("Login response is missing a valid tokenId for login to odessa system, please try again."); + } + } + throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.ERROR_IN_GENERATING_NDG_TRY_AGAIN)); + } catch (FeignException.Forbidden forbiddenException) { + log.error("Failed to login to odessa due to forbidden error."); + + CheckPasswordExpiredOrErrorInResponse(application, forbiddenException); + } catch (Exception e) { + log.error("Failed to authenticate user on Odessa (Attempt {}): {}", attempt, e.getMessage(), e); + } + } + throw new RuntimeException("Max retries exceeded. Failed to login to Odessa."); + } + private void CheckPasswordExpiredOrErrorInResponse(ApplicationEntity application, FeignException.Forbidden forbiddenException) { + + String responseBody = forbiddenException.contentUTF8(); + + try { + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode rootNode = objectMapper.readTree(responseBody); + JsonNode errorsNode = rootNode.path("errors"); + + if (errorsNode.isArray()) { + for (JsonNode error : errorsNode) { + if (GepafinConstant.PASSWORD_EXPIRED.equals(error.path("errorCode").asText())) { + if (application != null) { + application.setNdgStatus(GepafinConstant.NDG_FAILED); + applicationRepository.save(application); + } + log.warn("Detected PASSWORD_EXPIRED error during Odessa login. ApplicationId: {}", application.getId()); + throw new CustomValidationException(Status.FORBIDDEN, Translator.toLocale(GepafinConstant.PASSWORD_EXPIRED_LOGIN_TO_ODESSA)); + } + + JsonNode subErrorsNode = error.path("subErrors"); + if (subErrorsNode.isArray()) { + for (JsonNode subError : subErrorsNode) { + if (GepafinConstant.PASSWORD_EXPIRED.equals(subError.path("errorCode").asText())) { + if (application != null) { + application.setNdgStatus(GepafinConstant.NDG_FAILED); + applicationRepository.save(application); + } + throw new CustomValidationException(Status.FORBIDDEN, Translator.toLocale(GepafinConstant.PASSWORD_EXPIRED_LOGIN_TO_ODESSA)); + } + } + } + } + } + } catch (IOException e) { + log.error("Unexpected exception during Odessa login.Error: {}",e.getMessage(), e); + } + } + + private void startAsyncNdgProcessing(Long applicationId) { + // If already polling for this applicationId, do nothing: + if (executorMap.containsKey(applicationId)) { + log.warn("Async processing already running for applicationId: {}", applicationId); + return; + } + ServletRequestAttributes requestAttributes = new ServletRequestAttributes(request); + + ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(runnable -> { + Thread t = new Thread(runnable); + t.setName("AsyncNdgProcessing-" + applicationId); + return t; + }); + executorMap.put(applicationId, scheduler); + + // Record the start time so we can stop after 2 hours: + long startTime = System.currentTimeMillis(); + long twoHoursMs = TimeUnit.HOURS.toMillis(2); + long fifteenMin = 15; // in MINUTES + + // We need a reference to cancel the scheduled task from inside itself when we're done: + AtomicReference> futureRef = new AtomicReference<>(); + + Runnable pollingTask = () -> { + RequestContextHolder.setRequestAttributes(requestAttributes, true); + Utils.setHttpServletRequestForThread(request,HttpMethodEnum.POST.getValue(),GepafinConstant.CREATE_NDG, (Long) request.getAttribute(GepafinConstant.USER_ACTION_ID)); + try { + // 1) If 2 hours have already passed, mark as FAILED and shut down: + if (System.currentTimeMillis() - startTime > twoHoursMs) { + ApplicationEntity app = applicationService.validateApplication(applicationId); + log.warn("2-hour timeout reached for applicationId {}. Marking NDG_FAILED.", applicationId); + app.setNdgStatus(GepafinConstant.NDG_FAILED); + applicationRepository.save(app); + + futureRef.get().cancel(false); + shutdownScheduler(applicationId); + return; + } + + // 2) Otherwise, call processNdgGeneration once: + processNdgGeneration(applicationId); + + // 3) After return, check if NDG is now set or timed out. If so, cancel & shut down: + ApplicationEntity updated = applicationService.validateApplication(applicationId); + if (isNdgValid(updated.getNdg())) { + log.info("NDG found for applicationId {}. Shutting down scheduler.", applicationId); + futureRef.get().cancel(false); + shutdownScheduler(applicationId); + } else if (updated.getNdgStatus() != null && updated.getNdgStatus().equals(GepafinConstant.NDG_FAILED)) { + log.info("NDG status is NDG_FAILED for applicationId {}. Shutting down scheduler.", applicationId); + futureRef.get().cancel(false); + shutdownScheduler(applicationId); + } + // Otherwise: no NDG yet, not timed out → next run happens in 15 minutes automatically. + } catch (Exception ex) { + log.error("Unexpected error during scheduled polling for applicationId {}: {}", applicationId, ex.getMessage(), ex); + try { + ApplicationEntity checkApp = applicationService.validateApplication(applicationId); + if (System.currentTimeMillis() - startTime > twoHoursMs) { + log.warn("After exception, 2-hour window passed for applicationId {}. Marking NDG_FAILED.", applicationId); + checkApp.setNdgStatus(GepafinConstant.NDG_FAILED); + applicationRepository.save(checkApp); + + futureRef.get().cancel(false); + shutdownScheduler(applicationId); + } + } catch (Exception ignore) { + futureRef.get().cancel(false); + shutdownScheduler(applicationId); + } + } + }; + + // Schedule pollingTask: run now (delay=0), then every fifteen minutes: + ScheduledFuture future = scheduler.scheduleWithFixedDelay(pollingTask, 0, // initial delay = 0 min → run immediately + fifteenMin, // subsequent runs every 15 minutes + TimeUnit.MINUTES); + futureRef.set(future); + } + + private void shutdownScheduler(Long applicationId) { + + ScheduledExecutorService shed = executorMap.remove(applicationId); + if (shed != null) { + shed.shutdownNow(); + } + log.info("Scheduler shut down for applicationId: {}", applicationId); + } + + private void processNdgGeneration(Long applicationId) { + // Validate application, company, and hub + log.info("Starting NDG generation process for applicationId: {}", applicationId); + ApplicationEntity application = applicationService.validateApplication(applicationId); + CompanyEntity company = companyService.validateCompany(application.getCompanyId()); + HubEntity hub = hubRepository.findByHubId(application.getHubId()); + + if (!hub.getUniqueUuid().equals(defaultHubUuid)) { + log.info("Ndg cannot be created for another Hub, it is default for Gepafin."); + throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.NO_NDG_FOR_ANOTHER_HUB)); + } + + try { + // Authenticate and fetch token if required + if (hub.getAppointmentAuthTokenId() == null || hub.getAreaCode() == null) { + authenticateAndSaveToken(hub, application); + } + + String authorizationToken = getBearerToken(hub); + + // Try retrieving NDG by VAT number + AppointmentLoginResponse ndgResponse = retrieveNdgByVatNumber(company.getVatNumber(), authorizationToken, hub, application); + if (isNdgValid(ndgResponse.getNdg())) { + saveNdgAndIdVisura(application, company, ndgResponse.getNdg()); + log.info("NDG successfully generated for applicationId: {}", applicationId); + } else { + log.info("Polling for NDG for applicationId: {}", applicationId); + handleNdgPolling(application, company, hub, authorizationToken); + } + } catch (Exception e) { + log.error("Exception occurred during NDG generation. ApplicationId: {}, CompanyId: {}, HubId: {}, Error: {}", applicationId, company.getId(), hub.getId(), + e.getMessage(), e); + } + } + + private void handleNdgPolling(ApplicationEntity application, CompanyEntity company, HubEntity hub, String authorizationToken) { + + log.info("Starting singleâ€shot NDG polling attempt for applicationId: {}, CompanyId: {}, HubId: {}", application.getId(), company.getId(), hub.getId()); + ApplicationEntity oldApplication = Utils.getClonedEntityForData(application); + CompanyEntity oldCompanyEntity=Utils.getClonedEntityForData(company); + long startTime = System.currentTimeMillis(); + long twoHoursMs = TimeUnit.HOURS.toMillis(2); + + try { + // 1) If NDG was already populated (perhaps by another thread), skip polling. + if (application.getNdg() != null) { + log.info("NDG already present for applicationId {}. Exiting singleâ€shot polling.", application.getId()); + return; + } + + // 2) Attempt to create Visura (this may immediately return a valid NDG) + AppointmentLoginResponse visuraResponse = createVisura(company, authorizationToken, hub); + + // 2a) If createVisura gave us a valid NDG, persist & exit + String fetchedNdg = visuraResponse.getNdg(); + if (isNdgValid(fetchedNdg)) { + log.info("Valid NDG retrieved from createVisura(): {} | applicationId: {}", fetchedNdg, application.getId()); + + company.setNdg(fetchedNdg); + application.setNdg(fetchedNdg); + application.setNdgStatus(GepafinConstant.NDG_GENERATED); + application.setStatus(ApplicationStatusTypeEnum.NDG.getValue()); + application.setIdVisura(visuraResponse.getIdVisura()); + + companyRepository.save(company); + applicationRepository.save(application); + loggingUtil.addVersionHistory( + VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldApplication).newData(application).build()); + loggingUtil.addVersionHistory( + VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldCompanyEntity).newData(company).build()); + + ApplicationEvaluationEntity eval = applicationEvaluationService.validateApplicationEvaluation(application.getApplicationEvaluationId()); + Map placeholders = new HashMap<>(); + placeholders.put("{{call_name}}", application.getCall().getName()); + placeholders.put("{{protocol_number}}", String.valueOf(application.getProtocol().getProtocolNumber())); + notificationDao.sendNotificationToInstructor(placeholders, eval, NotificationTypeEnum.NDG_GENERATION); + notificationDao.sendNotificationToSuperUser(application, placeholders, NotificationTypeEnum.NDG_GENERATION); + + log.info("NDG saved successfully for applicationId: {}", application.getId()); + return; + } + + // 2b) If no immediate NDG, fetch the Visura list JSON and parse out NDG + String visuraListJson = getVisuraList(visuraResponse.getIdVisura(), authorizationToken, application, hub); + log.debug("Parsing NDG from VisuraList JSON for applicationId: {}", application.getId()); + String parsedNdg = parseNdgFromVisuraListResponse(visuraListJson); + + if (isNdgValid(parsedNdg)) { + log.info("Valid NDG parsed from VisuraList: {} | applicationId: {}", parsedNdg, application.getId()); + + company.setNdg(parsedNdg); + application.setNdg(parsedNdg); + application.setNdgStatus(GepafinConstant.NDG_GENERATED); + application.setStatus(ApplicationStatusTypeEnum.NDG.getValue()); + application.setIdVisura(visuraResponse.getIdVisura()); + + companyRepository.save(company); + applicationRepository.save(application); + loggingUtil.addVersionHistory( + VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldApplication).newData(application).build()); + loggingUtil.addVersionHistory( + VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldCompanyEntity).newData(company).build()); + + ApplicationEvaluationEntity eval = applicationEvaluationService.validateApplicationEvaluation(application.getApplicationEvaluationId()); + Map placeholders = new HashMap<>(); + placeholders.put("{{call_name}}", application.getCall().getName()); + placeholders.put("{{protocol_number}}", String.valueOf(application.getProtocol().getProtocolNumber())); + notificationDao.sendNotificationToInstructor(placeholders, eval, NotificationTypeEnum.NDG_GENERATION); + notificationDao.sendNotificationToSuperUser(application, placeholders, NotificationTypeEnum.NDG_GENERATION); + + log.info("NDG saved successfully for applicationId: {}", application.getId()); + return; + } + + // 3) Neither direct API nor parsing yielded a valid NDG. Check timeout. + if (System.currentTimeMillis() - startTime > twoHoursMs) { + log.warn("NDG polling timed out after 2 hours for applicationId: {}", application.getId()); + application.setNdgStatus(GepafinConstant.NDG_FAILED); + applicationRepository.save(application); + return; + } + + // 4) No NDG yet—just return. The scheduler will retry in 15 minutes. + log.info("No valid NDG yet for applicationId {}. Returning to scheduler—next attempt in 15 minutes.", application.getId()); + } catch (Exception e) { + log.error("Exception during NDG polling for applicationId: {}", application.getId(), e); + // If timeout after exception, mark NDG_FAILED + if (System.currentTimeMillis() - startTime > twoHoursMs) { + log.warn("Example: exiting singleâ€shot polling due to timeout after exception for applicationId: {}", application.getId()); + application.setNdgStatus(GepafinConstant.NDG_FAILED); + applicationRepository.save(application); + } else { + log.info("Exception occurred but not timed out for applicationId {}. Returning to scheduler for next attempt in 15 minutes.", application.getId()); + } + } + + log.info("NDG polling completed for applicationId: {}", application.getId()); + } + + private static String getBearerToken(HubEntity hub) { + + return "Bearer " + hub.getAppointmentAuthTokenId(); + } + + private boolean isNdgValid(String ndg) { + + return ndg != null && !ndg.isEmpty(); + } + + private void saveNdgAndIdVisura(ApplicationEntity application, CompanyEntity company, String ndg) { + + ApplicationEntity oldApplication = Utils.getClonedEntityForData(application); + CompanyEntity oldCompanyEntity=Utils.getClonedEntityForData(company); + application.setNdg(ndg); + application.setNdgStatus(GepafinConstant.NDG_GENERATED); + application.setStatus(ApplicationStatusTypeEnum.NDG.getValue()); + company.setNdg(ndg); + companyRepository.save(company); + applicationRepository.save(application); + loggingUtil.addVersionHistory( + VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldApplication).newData(application).build()); + loggingUtil.addVersionHistory( + VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldCompanyEntity).newData(company).build()); + + ApplicationEvaluationEntity applicationEvaluationEntity = applicationEvaluationService.validateApplicationEvaluation(application.getApplicationEvaluationId()); +// Map placeHolders = notificationDao.sendNotificationToBeneficiary(application, NotificationTypeEnum.NDG_GENERATION); + Map placeHolders = new HashMap<>(); + placeHolders.put("{{call_name}}", application.getCall().getName()); + placeHolders.put("{{protocol_number}}", String.valueOf(application.getProtocol().getProtocolNumber())); + notificationDao.sendNotificationToInstructor(placeHolders, applicationEvaluationEntity, NotificationTypeEnum.NDG_GENERATION); + notificationDao.sendNotificationToSuperUser(application, placeHolders, NotificationTypeEnum.NDG_GENERATION); + log.info("NDG saved for applicationId: {}, {}", application.getId(), application.getNdg()); + } + + private String getVisuraList(String idVisura, String authorizationToken, ApplicationEntity application, HubEntity hub) { + + log.info("Initiating Visura list retrieval | ApplicationId: {}, HubId: {}, IdVisura: {}", application.getId(), hub.getId(), idVisura); + AppointmentVisuraListRequest visuraListRequest = new AppointmentVisuraListRequest(); + AppointmentVisuraListRequest.VisuraFilter filter = new AppointmentVisuraListRequest.VisuraFilter(); + filter.setIdVisura(idVisura); + visuraListRequest.setFilter(filter); + + try { + String requestJson = Utils.convertObjectToJson(visuraListRequest); + ResponseEntity response = appointmentApiService.getVisuraList(requestJson, authorizationToken); + return Utils.convertObjectToJson(response.getBody()); + } catch (FeignException.Forbidden forbiddenException) { + log.warn("403 Forbidden while fetching Visura list. Attempting token regeneration | ApplicationId: {}, HubId: {}", application.getId(), hub.getId()); + // Regenerate the token and retry + String newAuthorizationToken = regenerateTokenAndSave(hub, application); + return getVisuraList(idVisura, newAuthorizationToken, application, hub); + } catch (Exception e) { + log.error("Error while fetching Visura list | ApplicationId: {}, HubId: {}, Error: {}", application.getId(), hub.getId(), e.getMessage(), e); + throw new RuntimeException("Error fetching Ndg List", e); + } + } + + private AppointmentLoginResponse retrieveNdgByVatNumber(String vatNumber, String authorizationToken, HubEntity hub, ApplicationEntity application) { + + try { + log.info("Initiating NDG retrieval by VAT number | ApplicationId: {}, HubId: {}, VAT: {}", application.getId(), hub.getId(), vatNumber); + // Prepare the NDG request + AppointmentNdgRequest ndgRequest = getAppointmentNdgRequest(vatNumber); + // Call the API to retrieve NDG + ResponseEntity response = appointmentApiService.getNdgByVatNumber(ndgRequest, authorizationToken); + String responseJson = Utils.convertObjectToJson(response.getBody()); + // Parse and return the NDG response + return parseNdgResponse(responseJson); + } catch (FeignException.Forbidden forbiddenException) { + log.error("403 Forbidden during NDG retrieval | ApplicationId: {}, HubId: {}", application.getId(), hub.getId()); + logForbiddenError(); + // Regenerate the token and retry + String newAuthorizationToken = regenerateTokenAndSave(hub, application); + return retrieveNdgByVatNumber(vatNumber, newAuthorizationToken, hub, application); + } catch (Exception e) { + log.error("Error during NDG retrieval | ApplicationId: {}, HubId: {}, Message: {}", application.getId(), hub.getId(), e.getMessage(), e); + throw new RuntimeException("NDG retrieval failed.", e); + } + } + + private String regenerateTokenAndSave(HubEntity hub, ApplicationEntity application) { + + hub = authenticateAndSaveToken(hub, application); + return "Bearer " + hub.getAppointmentAuthTokenId(); + } + + private AppointmentLoginResponse createVisura(CompanyEntity company, String authorizationToken, HubEntity hub) { + + try { + String visuraRequest = getAppointmentVisuraRequest(company, hub.getAreaCode()); + ResponseEntity response = appointmentApiService.createVisura(visuraRequest, authorizationToken); + String responseJson = Utils.convertObjectToJson(response.getBody()); + return parseVisuraResponse(responseJson); + } catch (FeignException.Forbidden forbiddenException) { + logForbiddenError(); + // Regenerate the token and retry + String newAuthorizationToken = regenerateTokenAndSave(hub, null); + return createVisura(company, newAuthorizationToken, hub); + } catch (Exception e) { + log.error("Failed to create Visura for Ndg : {}", e.getMessage()); + throw new RuntimeException("Visura creation failed for Ndg.", e); + } + } + + private static void logForbiddenError() { + + log.error("403 Forbidden received while retrieving NDG. Regenerating token..."); + } + + private static AppointmentNdgRequest getAppointmentNdgRequest(String vatNumber) { + + log.info("Creating Appointment NDG Request | VAT Number: {}", vatNumber); + AppointmentNdgRequest request = new AppointmentNdgRequest(); + AppointmentNdgRequest.Filter filter = new AppointmentNdgRequest.Filter(); + filter.setPartitaIva(vatNumber); + + AppointmentNdgRequest.Pagination pagination = new AppointmentNdgRequest.Pagination(); + pagination.setTargetPage(AppointmentApiConstant.TARGET_PAGE_SIZE); + pagination.setRecordsPerPage(AppointmentApiConstant.RECORD_PER_PAGE_SIZE); + + request.setFilter(filter); + request.setPagination(pagination); + return request; + } + + private static String getAppointmentVisuraRequest(CompanyEntity company, String areaCode) { + + AppointmentVisuraRequest visuraRequest = new AppointmentVisuraRequest(); + AppointmentVisuraRequest.VisuraInput input = new AppointmentVisuraRequest.VisuraInput(); + input.setPartitaIva(company.getVatNumber()); + input.setCodiceFiscale(company.getCodiceFiscale()); + input.setCodArea(areaCode); + input.setVisuraMode(AppointmentApiConstant.VISURA_MODE); + input.setVisuraProvider(AppointmentApiConstant.VISURA_PROVIDER); + input.setCodAgente(AppointmentApiConstant.COD_AGENTE); + input.setAnagraficaLegame(AppointmentApiConstant.IS_ANAGRAFICA_LEGAME); + input.setCreaAnagrafica(AppointmentApiConstant.CREA_ANAGRAFICA); + input.setFromRating(AppointmentApiConstant.IS_FROM_RATING); + input.setSalvaDocumenti(AppointmentApiConstant.SALVA_DOCUMENTI); + input.setVisuraType(AppointmentApiConstant.VISURA_TYPE); + visuraRequest.setInput(input); + return Utils.convertObjectToJson(visuraRequest); + } + + private String parseNdgFromVisuraListResponse(String jsonResponse) { + + try { + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode rootNode = objectMapper.readTree(jsonResponse); + JsonNode dataNode = rootNode.get(GepafinConstant.DATA_STRING); + + if (dataNode != null && dataNode.isArray() && dataNode.size() > 0) { + JsonNode firstEntry = dataNode.get(0); + JsonNode ndgClienteNode = firstEntry.get("ndgCliente"); + if (ndgClienteNode != null && ndgClienteNode.get("code") != null) { + String code = ndgClienteNode.get("code").asText(); + return normalizeNullValue(code); + } + } + log.warn("NDG not found in Visura List API response."); + return null; + } catch (Exception e) { + log.error("Failed to parse NDG from Visura List API response: {}", e.getMessage(), e); + throw new RuntimeException("Error parsing NDG from Visura List API response", e); + } + } + + public AppointmentLoginResponse parseLoginResponse(String jsonResponse) { + + try { + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode rootNode = objectMapper.readTree(jsonResponse); + JsonNode dataNode = rootNode.get(GepafinConstant.DATA_STRING); + + if (dataNode != null) { + AppointmentLoginResponse response = new AppointmentLoginResponse(); + response.setTokenId(dataNode.get("tokenId").asText()); + JsonNode areasNode = dataNode.get("areas"); + if (areasNode != null && areasNode.isArray() && areasNode.size() > 0) { + response.setAreaCode(areasNode.get(0).get("code").asText()); + } + response.setCompanyId(dataNode.get("companyId").asLong()); + return response; + } else { + throw new RuntimeException("Invalid JSON structure: Missing 'data' node."); + } + } catch (Exception e) { + throw new RuntimeException("Failed to parse response from loginApi for odessa: " + e.getMessage(), e); + } + } + + public AppointmentLoginResponse parseVisuraResponse(String jsonResponse) { + + try { + // Log full raw JSON for debug purposes + log.info("Raw Visura JSON Response: {}", jsonResponse); + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode rootNode = objectMapper.readTree(jsonResponse); + JsonNode dataNode = rootNode.get(GepafinConstant.DATA_STRING); + + if (dataNode != null && dataNode.isObject()) { + AppointmentLoginResponse response = new AppointmentLoginResponse(); + JsonNode idVisuraNode = dataNode.get(GepafinConstant.ID_VISURA_STRING); + JsonNode ndgNode = dataNode.get(GepafinConstant.NDG_STRING); + if (idVisuraNode == null || ndgNode == null) { + log.error("Missing expected fields in 'data' node. JSON: {}", dataNode); + } + response.setIdVisura(normalizeNullValue(idVisuraNode != null ? idVisuraNode.asText() : null)); + response.setNdg(normalizeNullValue(ndgNode != null ? ndgNode.asText() : null)); + return response; + } else { + System.err.println("Invalid JSON: 'data' node is missing or not an object."); + throw new RuntimeException("Invalid JSON structure: Missing or malformed 'data' node."); + } + } catch (Exception e) { + System.err.println("Exception while parsing Visura response: " + e.getMessage()); + throw new RuntimeException("Failed to parse response: " + e.getMessage(), e); + } + } + + public AppointmentLoginResponse parseNdgResponse(String jsonResponse) { + + try { + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode rootNode = objectMapper.readTree(jsonResponse); + JsonNode dataArray = rootNode.get(GepafinConstant.DATA_STRING); + if (dataArray == null || !dataArray.isArray() || dataArray.isEmpty()) { + log.info("NDG data is empty or missing in the response."); + AppointmentLoginResponse emptyResponse = new AppointmentLoginResponse(); + emptyResponse.setNdg(null); + return emptyResponse; + } + JsonNode firstDataEntry = dataArray.get(0); + AppointmentLoginResponse response = new AppointmentLoginResponse(); + if (firstDataEntry.has(GepafinConstant.NDG_STRING)) { + response.setNdg(normalizeNullValue(firstDataEntry.get(GepafinConstant.NDG_STRING).asText())); + } + return response; + } catch (Exception e) { + log.error("Failed to parse response: {}", e.getMessage(), e); + throw new RuntimeException("Failed to parse NDG response.", e); + } + } + + private String normalizeNullValue(String value) { + + return (value == null || GepafinConstant.NULL_STRING.equalsIgnoreCase(value.trim())) ? null : value; + } + + public AppointmentCreationResponse createAppointment(Long applicationId, CreateAppointmentRequest createAppointmentRequest) { + // Validate the application + log.info("Starting appointment creation for applicationId: {}", applicationId); + ApplicationEntity application = applicationService.validateApplication(applicationId); + + AppointmentCreationResponse appointmentCreationResponse = new AppointmentCreationResponse(); + + ApplicationEntity oldApplicationData = Utils.getClonedEntityForData(application); + HubEntity hub = hubRepository.findByHubId(application.getHubId()); + + // Check hub UUID and enforce constraints + if (!hub.getUniqueUuid().equals(defaultHubUuid)) { + log.info("Appointment cannot be created for another Hub; default is required for Gepafin."); + throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.NO_APPOINTMENT_FOR_ANOTHER_HUB)); + } + + try { + // Pre-check conditions for appointment creation + if (application.getNdg() != null && !Objects.equals(application.getNdgStatus(), GepafinConstant.NDG_IN_PROGRESS) && application.getAppointmentId() != null) { + appointmentCreationResponse.setAppointmentId(application.getAppointmentId()); + throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.APPOINTMENT_ALREADY_CREATED)); + // return appointmentCreationResponse; + } + + if (application.getNdg() == null && Objects.equals(application.getNdgStatus(), GepafinConstant.NDG_IN_PROGRESS)) { + log.warn("NDG in progress but not available for applicationId: {}", applicationId); + throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.NDG_NOT_FOUND_FOR_APPLICATION)); + } + + // Generate authorization token and fetch template data + String authorizationToken = regenerateTokenAndSave(hub, application); + Long appointmentTemplateId = application.getCall().getAppointmentTemplateId(); + if (appointmentTemplateId == null) { + log.error("Missing appointment template ID for applicationId: {}", applicationId); + throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.APPOINTMENT_CANNOT_BE_CREATED)); + } + ResponseEntity response = appointmentApiService.getAppointmentTemplateForTemplateCreation(authorizationToken, appointmentTemplateId); + + if (response.getStatusCode() != HttpStatus.OK) { + log.error("Failed to retrieve appointment template for appointment creation. Status: {}", response.getStatusCode()); + throw new IllegalStateException("Failed to retrieve appointment template for appointment creation"); + } + + // Parse template data + String responseDataForTemplate = Utils.convertObjectToJson(response.getBody()); + AppointmentCreationRequest templateRichiestaData = parseTemplateResponseData(responseDataForTemplate); + + // Build the appointment request body + AppointmentCreationRequest appointmentCreationRequest = buildAppointmentCreationRequest(applicationId, createAppointmentRequest, appointmentTemplateId, + templateRichiestaData); + log.info("AppointmentCreationRequest : {}", appointmentCreationRequest); + String appointmentRequestBody = Utils.convertObjectToJson(appointmentCreationRequest); + + // Make API call to create the appointment + log.info("Context:{}, Authorization Token : {}, RequestBody : {}", context, authorizationToken, appointmentRequestBody); + ResponseEntity appointmentResponse = appointmentApiService.createAppointment(authorizationToken, context, appointmentRequestBody); + String appointmentId = extractAppointmentIdFromResponse(appointmentResponse); + + if (appointmentId == null) { + log.error("Failed to extract appointment ID from response for applicationId: {}", applicationId); + throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.APPOINTMENT_NOT_CREATED)); + } + // Update application with the appointment ID + application.setAppointmentId(appointmentId); + application.setStatus(ApplicationStatusTypeEnum.APPOINTMENT.getValue()); + applicationRepository.save(application); + + // Log version history + loggingUtil.addVersionHistory( + VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldApplicationData).newData(application).build()); + + appointmentCreationResponse.setAppointmentId(appointmentId); + return appointmentCreationResponse; + + } catch (FeignException.Forbidden forbiddenException) { + log.error("403 Forbidden received while retrieving template. Attempting to regenerate token and retry. Application ID: {}", applicationId); + regenerateTokenAndSave(hub, application); + return createAppointment(applicationId, createAppointmentRequest); + } + } + + private String extractAppointmentIdFromResponse(ResponseEntity appointmentResponse) { + + if (appointmentResponse.getBody() != null) { + log.info("Appointment API Response : {}", appointmentResponse.getBody()); + try { + Map responseBody = (Map) appointmentResponse.getBody(); + // 1. Try to get appointment ID from data.id + if (responseBody.containsKey(GepafinConstant.DATA_STRING)) { + Map data = (Map) responseBody.get(GepafinConstant.DATA_STRING); + if (data != null && data.containsKey(GepafinConstant.ID_STRING)) { + return data.get(GepafinConstant.ID_STRING).toString(); + } + } + // 2. If ID not present, check errors[0].cause.errorDescription + if (responseBody.containsKey(GepafinConstant.ERROR_STRING)) { + List> errors = (List>) responseBody.get(GepafinConstant.ERROR_STRING); + if (errors != null && !errors.isEmpty()) { + Map firstError = errors.get(0); + if (firstError.containsKey(GepafinConstant.CAUSE_STRING)) { + Map cause = (Map) firstError.get(GepafinConstant.CAUSE_STRING); + if (cause != null && cause.containsKey(GepafinConstant.ERROR_DESCRIPTION_STRING)) { + String errorDescription = cause.get(GepafinConstant.ERROR_DESCRIPTION_STRING).toString(); + log.warn("Appointment creation failed: {}", errorDescription); + } + } + } + } + } catch (Exception e) { + log.error("Error while extracting appointment ID or parsing error message", e); + } + } + return null; + } + + public AppointmentCreationRequest parseTemplateResponseData(String jsonResponse) { + + try { + + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode rootNode = objectMapper.readTree(jsonResponse); + JsonNode richiesteClienteArray = rootNode.path(GepafinConstant.DATA_STRING).path(GepafinConstant.RICHIESTE_CLIENTE_STRING); + + // Initialize the result object + AppointmentCreationRequest appointmentCreationRequest = new AppointmentCreationRequest(); + AppointmentCreationRequest.Input input = new AppointmentCreationRequest.Input(); + List richiestaClienteList = new ArrayList<>(); + if (!richiesteClienteArray.isArray()) { + log.warn("richiesteCliente array is missing or not an array."); + return new AppointmentCreationRequest(); + } + for (JsonNode richiestaNode : richiesteClienteArray) { + if (richiestaNode.isNull()) + continue; + + AppointmentCreationRequest.RichiestaCliente richiestaCliente = new AppointmentCreationRequest.RichiestaCliente(); + JsonNode prodottoNode = richiestaNode.path(AppointmentApiConstant.PRODOTTO); + String prodottoCode = prodottoNode.path(AppointmentApiConstant.PRODOTTO_CODE).asText(); + + richiestaCliente.setCodProdotto(prodottoCode); + richiestaCliente.setIdMotivazione(getIntValue(richiestaNode)); + richiestaCliente.setCodAbi(getTextValue(richiestaNode, AppointmentApiConstant.COD_ABI)); + richiestaCliente.setCodCab(getTextValue(richiestaNode, AppointmentApiConstant.COD_CAB)); + richiestaCliente.setIdNota(getTextValue(richiestaNode, AppointmentApiConstant.ID_NOTA)); + richiestaCliente.setImportoAgevolato(getTextValue(richiestaNode, AppointmentApiConstant.IMPORTO_AGEVOLATO)); + richiestaCliente.setImportoMedioLungoTermine(getTextValue(richiestaNode, AppointmentApiConstant.IMPORTO_MEDIOLUNGO_TERMINE)); + richiestaCliente.setCodTipoProdotto(getTextValue(richiestaNode, AppointmentApiConstant.COD_TIPO_PRODOTTO)); + richiestaCliente.setCodCategoriaProdotto(getTextValue(richiestaNode, AppointmentApiConstant.COD_CATEGORIA_PRODOTTO)); + richiestaCliente.setCodFormaTecnica(getTextValue(richiestaNode, AppointmentApiConstant.COD_FORMATECNICA)); + richiestaCliente.setCodOperazione(getTextValue(richiestaNode, AppointmentApiConstant.COD_OPERAZIONE)); + + richiestaClienteList.add(richiestaCliente); + } + + input.setRichiestaCliente(richiestaClienteList); + appointmentCreationRequest.setInput(input); + + return appointmentCreationRequest; + + } catch (JsonProcessingException e) { + log.error("JSON processing error: {}", e.getMessage(), e); + throw new IllegalStateException("Invalid JSON structure in template response", e); + } + } + + private String getTextValue(JsonNode node, String fieldName) { + + return node.path(fieldName).isTextual() ? node.path(fieldName).asText() : null; + } + + private int getIntValue(JsonNode node) { + + return node.path(AppointmentApiConstant.MOTIVAZIONE).path(AppointmentApiConstant.MOTIVAZIONE_ID).asInt(); + } + + public AppointmentCreationRequest buildAppointmentCreationRequest(Long applicationId, CreateAppointmentRequest createAppointmentRequest, Long areaCode, + AppointmentCreationRequest templateRichiestaData) { + + ApplicationEntity application = applicationService.validateApplication(applicationId); + CreateAppointmentRequest.Nota nota = createAppointmentRequest.getNota(); + + AppointmentCreationRequest appointmentCreationRequest = new AppointmentCreationRequest(); + AppointmentCreationRequest.Input input = new AppointmentCreationRequest.Input(); + + // Set Input Fields + input.setId(areaCode); + input.setNdg(application.getNdg()); + + // Populate richiestaCliente from template data + List richiestaClienteList = new ArrayList<>(); + for (AppointmentCreationRequest.RichiestaCliente templateRichiesta : templateRichiestaData.getInput().getRichiestaCliente()) { + AppointmentCreationRequest.RichiestaCliente richiestaCliente = new AppointmentCreationRequest.RichiestaCliente(); + BeanUtils.copyProperties(templateRichiesta, richiestaCliente); + + // Add specific `nota` + AppointmentCreationRequest.Nota requestNota = new AppointmentCreationRequest.Nota(); + requestNota.setTitolo(nota.getTitolo()); + requestNota.setTesto(nota.getTesto()); + richiestaCliente.setNota(requestNota); + richiestaCliente.setDurataMesiFinanziamento(createAppointmentRequest.getDurataMesiFinanziamento()); + richiestaCliente.setImportoBreveTermine(createAppointmentRequest.getImportoBreveTermine()); + richiestaClienteList.add(richiestaCliente); + } + + input.setRichiestaCliente(richiestaClienteList); + appointmentCreationRequest.setInput(input); + return appointmentCreationRequest; + } + + public DocumentUploadResponse uploadDocumentToExternalSystem(Long documentId, UploadDocToExternalSystemRequest docToExternalSystemRequest) { + log.info("Initiating upload to external system for documentId: {}", documentId); + // Check if the document is already being processed + DocumentEntity systemDoc = documentDao.validateDocument(documentId); + + ApplicationEntity application = null; + + if (systemDoc != null) { + DocumentSourceTypeEnum sourceType = DocumentSourceTypeEnum.valueOf(systemDoc.getSource()); + + switch (sourceType) { + case APPLICATION: + application = applicationDao.validateApplication(systemDoc.getSourceId()); + break; + case AMENDMENT: + ApplicationAmendmentRequestEntity applicationAmendmentEntity = applicationAmendmentRequestDao.validateApplicationAmendmentRequest(systemDoc.getSourceId()); + application = applicationDao.validateApplication(applicationAmendmentEntity.getApplicationId()); + break; + case EVALUATION: + ApplicationEvaluationEntity applicationEvaluationEntity = applicationEvaluationDao.validateApplicationEvaluation(systemDoc.getSourceId()); + application = applicationDao.validateApplication(applicationEvaluationEntity.getApplicationId()); + break; + + case CALL: + break; + + default: + log.warn("Unhandled document source type: {}", sourceType); + break; + } + } + + Claims claims = tokenProvider.getClaimsFromToken(tokenProvider.extractTokenFromRequest(request)); + Long hubId = Utils.extractHubIdFromPayload(claims.getSubject()); + + // Authenticate the hub before proceeding + HubEntity hub = hubRepository.findByHubId(hubId); + authenticateAndSaveToken(hub, application); + if (systemDoc != null && systemDoc.getDocumentAttachmentId() != null) { + // If the documentAttachmentId is already set, return the response + log.info("Document already uploaded with documentAttachmentId: {}", systemDoc.getDocumentAttachmentId()); + DocumentUploadResponse response = new DocumentUploadResponse(); + response.setDocumentAttachmentId(systemDoc.getDocumentAttachmentId()); + return response; + } + // Check if a thread is already running for this document upload + if (threadForDocumentMap.containsKey(documentId)) { + log.warn("Document upload already running for documentId: {}", documentId); + throw new CustomValidationException(Status.SUCCESS, Translator.toLocale(GepafinConstant.DOCUMENT_UPLOADING_IN_PROGRESS)); + } + // Start the upload process in the background + ExecutorService executor = Executors.newSingleThreadExecutor(runnable -> { + Thread thread = new Thread(runnable); + thread.setName(GepafinConstant.ASYNC_DOCUMENT_UPLOAD_NAME + documentId); + return thread; + }); + threadForDocumentMap.put(documentId, executor); + + ApplicationEntity finalApplication = application; + executor.submit(() -> { + threadLocalHubId.set(hubId); + try { + log.info("Starting async document upload for documentId: {}", documentId); + uploadDocumentToExternalSystemSync(documentId, docToExternalSystemRequest, finalApplication); + } catch (Exception e) { + log.error("Error in async document upload for documentId: {}", documentId, e); + } finally { + // Cleanup resources + ExecutorService executorToShutdown = threadForDocumentMap.remove(documentId); + if (executorToShutdown != null) { + executorToShutdown.shutdown(); + threadLocalHubId.remove(); + } + log.info("Async document upload completed for documentId: {}", documentId); + } + }); + return null; + } + + private void uploadDocumentToExternalSystemSync(Long documentId, UploadDocToExternalSystemRequest docToExternalSystemRequest, ApplicationEntity application) { + log.info("Starting sync document upload for documentId: {}", documentId); + // Synchronous upload logic + DocumentEntity systemDoc = documentDao.validateDocument(documentId); + + Long hubId = threadLocalHubId.get(); + HubEntity hub = hubRepository.findByHubId(hubId); + + if (!hub.getUniqueUuid().equals(defaultHubUuid)) { + log.info("Document cannot be uploaded for another Hub, it is default for Gepafin."); + throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.NO_DOCUMENT_UPLOAD_FOR_ANOTHER_HUB)); + } + + log.info("Got Document in system: {}", systemDoc); + String oldUrl = systemDoc.getFilePath(); + String authorizationToken = getBearerToken(hub); + + try { + File localFile = downloadFileFromS3(oldUrl); + MultipartFile multipartFile = convertFileToMultipartFile(localFile); + + UploadDocToExternalSystemRequest externalSystemRequest = new UploadDocToExternalSystemRequest(); + externalSystemRequest.setInput(getUploadDocumentInput(docToExternalSystemRequest)); + + String uploadDocRequest = Utils.convertObjectToJson(externalSystemRequest); + ResponseEntity uploadedDocumentData = appointmentApiService.uploadDocumentToExternalSystemForAppointment(authorizationToken, context, uploadDocRequest, + multipartFile); + + String responseData = Utils.convertObjectToJson(uploadedDocumentData.getBody()); + DocumentUploadResponse parsedResponse = parseDocumentUploadResponse(responseData); + + if (parsedResponse == null) { + log.error("Upload failed: parsed response is null for documentId: {}", documentId); + throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.ERROR_UPLOADING_DOCUMENT)); + } + + // Save the documentAttachmentId to the database + systemDoc.setDocumentAttachmentId(parsedResponse.getDocumentAttachmentId()); + documentRepository.save(systemDoc); + + log.info("Document uploaded successfully to external system: {}", parsedResponse); + } catch (FeignException.Forbidden forbiddenException) { + log.error("403 Forbidden from external system during upload for documentId: {}. Retrying with new token...", documentId); + regenerateTokenAndSave(hub, application); + uploadDocumentToExternalSystemSync(documentId, docToExternalSystemRequest, application); + } catch (Exception e) { + log.error("Exception during document upload: {}", e.getMessage(), e); + throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.EXTERNAL_DOCUMENT_UPLOAD_FAILURE_MSG)); + } + } + + private UploadDocToExternalSystemRequest.Input getUploadDocumentInput(UploadDocToExternalSystemRequest docToExternalSystemRequest) { + + UploadDocToExternalSystemRequest.Input input = new UploadDocToExternalSystemRequest.Input(); + input.setIdTipoProtocollo(docToExternalSystemRequest.getInput().getIdTipoProtocollo()); + input.setIdClassificazione(docToExternalSystemRequest.getInput().getIdClassificazione()); + input.setFlagDaFirmare(docToExternalSystemRequest.getInput().getFlagDaFirmare()); + input.setDescrizione(docToExternalSystemRequest.getInput().getDescrizione()); + + UploadDocToExternalSystemRequest.Input.Attributes attributes = new UploadDocToExternalSystemRequest.Input.Attributes(); + attributes.setNdg(docToExternalSystemRequest.getInput().getAttributes().getNdg()); + attributes.setEmail(docToExternalSystemRequest.getInput().getAttributes().getEmail()); + + input.setAttributes(attributes); + return input; + } + + public static MultipartFile convertFileToMultipartFile(File file) throws IOException { + + FileInputStream input = new FileInputStream(file); + return new MockMultipartFile(file.getName(), file.getName(), MediaType.APPLICATION_OCTET_STREAM_VALUE, input); + } + + private File downloadFileFromS3(String fileUrl) throws Exception { + + String key = amazonS3Service.extractS3KeyFromUrl(fileUrl); + String fileName = extractFileName(key); + String folderPath = key.substring(0, key.lastIndexOf("/")); + File localFile = new File(GepafinConstant.TEMP_FILE_PATH + fileName); + + try (InputStream s3Stream = amazonS3Service.getFile(folderPath, key); FileOutputStream outputStream = new FileOutputStream(localFile)) { + s3Stream.transferTo(outputStream); + } + + log.info("Downloaded file from old S3 bucket: {}", key); + return localFile; + } + + private String extractFileName(String filePath) { + + String[] parts = filePath.split("/"); + return parts[parts.length - 1]; + + } + + public DocumentUploadResponse parseDocumentUploadResponse(String jsonResponse) { + + try { + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode rootNode = objectMapper.readTree(jsonResponse); + + // Navigate to the "data" node + JsonNode dataNode = rootNode.get(GepafinConstant.DATA_STRING); + if (dataNode != null) { + DocumentUploadResponse response = new DocumentUploadResponse(); + + // Extract "documentAttachmentId" + JsonNode documentAttachmentIdNode = dataNode.get(GepafinConstant.DOCUMENT_ATTACHMENT_ID_STRING); + if (documentAttachmentIdNode != null) { + response.setDocumentAttachmentId(documentAttachmentIdNode.asText()); + } else { + throw new RuntimeException("Invalid JSON structure: Missing 'documentAttachmentId' node."); + } + + return response; + } else { + return null; + } + } catch (Exception e) { + throw new RuntimeException("Failed to parse response: " + e.getMessage(), e); + } + } } \ No newline at end of file diff --git a/src/main/java/net/gepafin/tendermanagement/util/Utils.java b/src/main/java/net/gepafin/tendermanagement/util/Utils.java index d4bad6ed..2fbe8dd9 100644 --- a/src/main/java/net/gepafin/tendermanagement/util/Utils.java +++ b/src/main/java/net/gepafin/tendermanagement/util/Utils.java @@ -1043,15 +1043,17 @@ public class Utils { return new ArrayList<>(responseMap.values()); } - public static void setHttpServletRequestForThread(String redirectURI, String methodType,HttpServletRequest request,String remoteUser) { + + public static void setHttpServletRequestForThread(HttpServletRequest request,String methodType, String remoteUser, Long userActionId) { MockHttpServletRequest mockRequest = new MockHttpServletRequest(); - mockRequest.setRequestURI(redirectURI); + mockRequest.setRequestURI(request.getRequestURI()); mockRequest.setMethod(methodType); - mockRequest.setRemoteUser(remoteUser); - Long userActionId = (Long) request.getAttribute(GepafinConstant.USER_ACTION_ID); - mockRequest.setAttribute(GepafinConstant.USER_ACTION_ID,userActionId ); + mockRequest.setRemoteUser(remoteUser); // or pass it in if needed + mockRequest.setAttribute(GepafinConstant.USER_ACTION_ID, userActionId); + ServletRequestAttributes attributes = new ServletRequestAttributes(mockRequest); RequestContextHolder.setRequestAttributes(attributes, true); } + } From 93acfe29807c023b5fdf29aead087b01ddd2ddf6 Mon Sep 17 00:00:00 2001 From: rajesh Date: Mon, 16 Jun 2025 15:35:29 +0530 Subject: [PATCH 20/35] Refactor NDG code --- .../constants/AppointmentApiConstant.java | 5 +- .../tendermanagement/dao/AppointmentDao.java | 318 +++++++++++------- .../tendermanagement/enums/NdgStatusEnum.java | 22 ++ 3 files changed, 229 insertions(+), 116 deletions(-) create mode 100644 src/main/java/net/gepafin/tendermanagement/enums/NdgStatusEnum.java diff --git a/src/main/java/net/gepafin/tendermanagement/constants/AppointmentApiConstant.java b/src/main/java/net/gepafin/tendermanagement/constants/AppointmentApiConstant.java index 6e5ed97c..799421fd 100644 --- a/src/main/java/net/gepafin/tendermanagement/constants/AppointmentApiConstant.java +++ b/src/main/java/net/gepafin/tendermanagement/constants/AppointmentApiConstant.java @@ -18,7 +18,7 @@ public class AppointmentApiConstant { public static final boolean CREA_ANAGRAFICA = Boolean.TRUE; public static final boolean SALVA_DOCUMENTI = Boolean.TRUE; public static final String VISURA_PROVIDER = "cerved"; - public static final String VISURA_TYPE = "StandardReport"; + public static final String VISURA_TYPE = "FullReport"; public static final String VISURA_MODE = "visure"; public static final String COD_AGENTE = "UtenzaAPIPortal"; public static final boolean IS_FROM_RATING = Boolean.FALSE; @@ -38,4 +38,7 @@ public class AppointmentApiConstant { public static final String COD_OPERAZIONE = "codOperazione"; public static final String MOTIVAZIONE_ID = "id"; public static final String PRODOTTO_CODE = "code"; + + public static final String WS_ANAGRAFICA_URL= "/WSAnagrafica.getList"; + } diff --git a/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java b/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java index f0fd43cb..66bf44d7 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java @@ -12,12 +12,7 @@ import net.gepafin.tendermanagement.config.Translator; import net.gepafin.tendermanagement.config.jwt.TokenProvider; import net.gepafin.tendermanagement.constants.AppointmentApiConstant; import net.gepafin.tendermanagement.constants.GepafinConstant; -import net.gepafin.tendermanagement.entities.ApplicationAmendmentRequestEntity; -import net.gepafin.tendermanagement.entities.ApplicationEntity; -import net.gepafin.tendermanagement.entities.ApplicationEvaluationEntity; -import net.gepafin.tendermanagement.entities.CompanyEntity; -import net.gepafin.tendermanagement.entities.DocumentEntity; -import net.gepafin.tendermanagement.entities.HubEntity; +import net.gepafin.tendermanagement.entities.*; import net.gepafin.tendermanagement.enums.*; import net.gepafin.tendermanagement.model.request.AppointmentCreationRequest; import net.gepafin.tendermanagement.model.request.AppointmentNdgRequest; @@ -30,11 +25,7 @@ import net.gepafin.tendermanagement.model.response.AppointmentCreationResponse; import net.gepafin.tendermanagement.model.response.AppointmentLoginResponse; import net.gepafin.tendermanagement.model.response.DocumentUploadResponse; import net.gepafin.tendermanagement.model.response.NdgResponse; -import net.gepafin.tendermanagement.repositories.ApplicationRepository; -import net.gepafin.tendermanagement.repositories.CompanyRepository; -import net.gepafin.tendermanagement.repositories.DocumentRepository; -import net.gepafin.tendermanagement.repositories.HubRepository; -import net.gepafin.tendermanagement.repositories.UserRepository; +import net.gepafin.tendermanagement.repositories.*; import net.gepafin.tendermanagement.service.AmazonS3Service; import net.gepafin.tendermanagement.service.ApplicationService; import net.gepafin.tendermanagement.service.CompanyService; @@ -62,13 +53,8 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; +import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicReference; -import java.util.concurrent.ScheduledExecutorService; @Slf4j @Component @@ -154,7 +140,7 @@ public class AppointmentDao { private final Map executorMap = new ConcurrentHashMap<>(); - + private final ConcurrentHashMap threadForDocumentMap = new ConcurrentHashMap<>(); private static final ThreadLocal threadLocalHubId = new ThreadLocal<>(); @@ -177,7 +163,7 @@ public class AppointmentDao { // Update application status log.info("Updating NDG status to IN_PROGRESS. applicationId: {}", applicationId); - application.setNdgStatus(GepafinConstant.NDG_IN_PROGRESS); + application.setNdgStatus(NdgStatusEnum.NDG_INITITATED.getValue()); applicationRepository.save(application); loggingUtil.addVersionHistory( @@ -401,6 +387,7 @@ public class AppointmentDao { } throw new RuntimeException("Max retries exceeded. Failed to login to Odessa."); } + private void CheckPasswordExpiredOrErrorInResponse(ApplicationEntity application, FeignException.Forbidden forbiddenException) { String responseBody = forbiddenException.contentUTF8(); @@ -436,89 +423,89 @@ public class AppointmentDao { } } } catch (IOException e) { - log.error("Unexpected exception during Odessa login.Error: {}",e.getMessage(), e); + log.error("Unexpected exception during Odessa login.Error: {}", e.getMessage(), e); } } - private void startAsyncNdgProcessing(Long applicationId) { - // If already polling for this applicationId, do nothing: - if (executorMap.containsKey(applicationId)) { - log.warn("Async processing already running for applicationId: {}", applicationId); - return; - } - ServletRequestAttributes requestAttributes = new ServletRequestAttributes(request); - - ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(runnable -> { - Thread t = new Thread(runnable); - t.setName("AsyncNdgProcessing-" + applicationId); - return t; - }); - executorMap.put(applicationId, scheduler); - - // Record the start time so we can stop after 2 hours: - long startTime = System.currentTimeMillis(); - long twoHoursMs = TimeUnit.HOURS.toMillis(2); - long fifteenMin = 15; // in MINUTES - - // We need a reference to cancel the scheduled task from inside itself when we're done: - AtomicReference> futureRef = new AtomicReference<>(); - - Runnable pollingTask = () -> { - RequestContextHolder.setRequestAttributes(requestAttributes, true); - Utils.setHttpServletRequestForThread(request,HttpMethodEnum.POST.getValue(),GepafinConstant.CREATE_NDG, (Long) request.getAttribute(GepafinConstant.USER_ACTION_ID)); - try { - // 1) If 2 hours have already passed, mark as FAILED and shut down: - if (System.currentTimeMillis() - startTime > twoHoursMs) { - ApplicationEntity app = applicationService.validateApplication(applicationId); - log.warn("2-hour timeout reached for applicationId {}. Marking NDG_FAILED.", applicationId); - app.setNdgStatus(GepafinConstant.NDG_FAILED); - applicationRepository.save(app); - - futureRef.get().cancel(false); - shutdownScheduler(applicationId); - return; - } - - // 2) Otherwise, call processNdgGeneration once: - processNdgGeneration(applicationId); - - // 3) After return, check if NDG is now set or timed out. If so, cancel & shut down: - ApplicationEntity updated = applicationService.validateApplication(applicationId); - if (isNdgValid(updated.getNdg())) { - log.info("NDG found for applicationId {}. Shutting down scheduler.", applicationId); - futureRef.get().cancel(false); - shutdownScheduler(applicationId); - } else if (updated.getNdgStatus() != null && updated.getNdgStatus().equals(GepafinConstant.NDG_FAILED)) { - log.info("NDG status is NDG_FAILED for applicationId {}. Shutting down scheduler.", applicationId); - futureRef.get().cancel(false); - shutdownScheduler(applicationId); - } - // Otherwise: no NDG yet, not timed out → next run happens in 15 minutes automatically. - } catch (Exception ex) { - log.error("Unexpected error during scheduled polling for applicationId {}: {}", applicationId, ex.getMessage(), ex); - try { - ApplicationEntity checkApp = applicationService.validateApplication(applicationId); - if (System.currentTimeMillis() - startTime > twoHoursMs) { - log.warn("After exception, 2-hour window passed for applicationId {}. Marking NDG_FAILED.", applicationId); - checkApp.setNdgStatus(GepafinConstant.NDG_FAILED); - applicationRepository.save(checkApp); - - futureRef.get().cancel(false); - shutdownScheduler(applicationId); - } - } catch (Exception ignore) { - futureRef.get().cancel(false); - shutdownScheduler(applicationId); - } - } - }; - - // Schedule pollingTask: run now (delay=0), then every fifteen minutes: - ScheduledFuture future = scheduler.scheduleWithFixedDelay(pollingTask, 0, // initial delay = 0 min → run immediately - fifteenMin, // subsequent runs every 15 minutes - TimeUnit.MINUTES); - futureRef.set(future); - } +// private void startAsyncNdgProcessing(Long applicationId) { +// // If already polling for this applicationId, do nothing: +// if (executorMap.containsKey(applicationId)) { +// log.warn("Async processing already running for applicationId: {}", applicationId); +// return; +// } +// ServletRequestAttributes requestAttributes = new ServletRequestAttributes(request); +// +// ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(runnable -> { +// Thread t = new Thread(runnable); +// t.setName("AsyncNdgProcessing-" + applicationId); +// return t; +// }); +// executorMap.put(applicationId, scheduler); +// +// // Record the start time so we can stop after 2 hours: +// long startTime = System.currentTimeMillis(); +// long twoHoursMs = TimeUnit.HOURS.toMillis(2); +// long fifteenMin = 15; // in MINUTES +// +// // We need a reference to cancel the scheduled task from inside itself when we're done: +// AtomicReference> futureRef = new AtomicReference<>(); +// +// Runnable pollingTask = () -> { +// RequestContextHolder.setRequestAttributes(requestAttributes, true); +// Utils.setHttpServletRequestForThread(request,HttpMethodEnum.POST.getValue(),GepafinConstant.CREATE_NDG, (Long) request.getAttribute(GepafinConstant.USER_ACTION_ID)); +// try { +// // 1) If 2 hours have already passed, mark as FAILED and shut down: +// if (System.currentTimeMillis() - startTime > twoHoursMs) { +// ApplicationEntity app = applicationService.validateApplication(applicationId); +// log.warn("2-hour timeout reached for applicationId {}. Marking NDG_FAILED.", applicationId); +// app.setNdgStatus(GepafinConstant.NDG_FAILED); +// applicationRepository.save(app); +// +// futureRef.get().cancel(false); +// shutdownScheduler(applicationId); +// return; +// } +// +// // 2) Otherwise, call processNdgGeneration once: +// processNdgGeneration(applicationId); +// +// // 3) After return, check if NDG is now set or timed out. If so, cancel & shut down: +// ApplicationEntity updated = applicationService.validateApplication(applicationId); +// if (isNdgValid(updated.getNdg())) { +// log.info("NDG found for applicationId {}. Shutting down scheduler.", applicationId); +// futureRef.get().cancel(false); +// shutdownScheduler(applicationId); +// } else if (updated.getNdgStatus() != null && updated.getNdgStatus().equals(GepafinConstant.NDG_FAILED)) { +// log.info("NDG status is NDG_FAILED for applicationId {}. Shutting down scheduler.", applicationId); +// futureRef.get().cancel(false); +// shutdownScheduler(applicationId); +// } +// // Otherwise: no NDG yet, not timed out → next run happens in 15 minutes automatically. +// } catch (Exception ex) { +// log.error("Unexpected error during scheduled polling for applicationId {}: {}", applicationId, ex.getMessage(), ex); +// try { +// ApplicationEntity checkApp = applicationService.validateApplication(applicationId); +// if (System.currentTimeMillis() - startTime > twoHoursMs) { +// log.warn("After exception, 2-hour window passed for applicationId {}. Marking NDG_FAILED.", applicationId); +// checkApp.setNdgStatus(GepafinConstant.NDG_FAILED); +// applicationRepository.save(checkApp); +// +// futureRef.get().cancel(false); +// shutdownScheduler(applicationId); +// } +// } catch (Exception ignore) { +// futureRef.get().cancel(false); +// shutdownScheduler(applicationId); +// } +// } +// }; +// +// // Schedule pollingTask: run now (delay=0), then every fifteen minutes: +// ScheduledFuture future = scheduler.scheduleWithFixedDelay(pollingTask, 0, // initial delay = 0 min → run immediately +// fifteenMin, // subsequent runs every 15 minutes +// TimeUnit.MINUTES); +// futureRef.set(future); +// } private void shutdownScheduler(Long applicationId) { @@ -529,12 +516,10 @@ public class AppointmentDao { log.info("Scheduler shut down for applicationId: {}", applicationId); } - private void processNdgGeneration(Long applicationId) { + private void processNdgGeneration(ApplicationEntity application, CompanyEntity company, HubEntity hub) { // Validate application, company, and hub + Long applicationId = application.getId(); log.info("Starting NDG generation process for applicationId: {}", applicationId); - ApplicationEntity application = applicationService.validateApplication(applicationId); - CompanyEntity company = companyService.validateCompany(application.getCompanyId()); - HubEntity hub = hubRepository.findByHubId(application.getHubId()); if (!hub.getUniqueUuid().equals(defaultHubUuid)) { log.info("Ndg cannot be created for another Hub, it is default for Gepafin."); @@ -552,7 +537,7 @@ public class AppointmentDao { // Try retrieving NDG by VAT number AppointmentLoginResponse ndgResponse = retrieveNdgByVatNumber(company.getVatNumber(), authorizationToken, hub, application); if (isNdgValid(ndgResponse.getNdg())) { - saveNdgAndIdVisura(application, company, ndgResponse.getNdg()); + saveNdg(application, company, ndgResponse.getNdg()); log.info("NDG successfully generated for applicationId: {}", applicationId); } else { log.info("Polling for NDG for applicationId: {}", applicationId); @@ -568,7 +553,7 @@ public class AppointmentDao { log.info("Starting singleâ€shot NDG polling attempt for applicationId: {}, CompanyId: {}, HubId: {}", application.getId(), company.getId(), hub.getId()); ApplicationEntity oldApplication = Utils.getClonedEntityForData(application); - CompanyEntity oldCompanyEntity=Utils.getClonedEntityForData(company); + CompanyEntity oldCompanyEntity = Utils.getClonedEntityForData(company); long startTime = System.currentTimeMillis(); long twoHoursMs = TimeUnit.HOURS.toMillis(2); @@ -589,7 +574,7 @@ public class AppointmentDao { company.setNdg(fetchedNdg); application.setNdg(fetchedNdg); - application.setNdgStatus(GepafinConstant.NDG_GENERATED); + application.setNdgStatus(NdgStatusEnum.NDG_GENERATED.getValue()); application.setStatus(ApplicationStatusTypeEnum.NDG.getValue()); application.setIdVisura(visuraResponse.getIdVisura()); @@ -678,10 +663,10 @@ public class AppointmentDao { return ndg != null && !ndg.isEmpty(); } - private void saveNdgAndIdVisura(ApplicationEntity application, CompanyEntity company, String ndg) { + private void saveNdg(ApplicationEntity application, CompanyEntity company, String ndg) { ApplicationEntity oldApplication = Utils.getClonedEntityForData(application); - CompanyEntity oldCompanyEntity=Utils.getClonedEntityForData(company); + CompanyEntity oldCompanyEntity = Utils.getClonedEntityForData(company); application.setNdg(ndg); application.setNdgStatus(GepafinConstant.NDG_GENERATED); application.setStatus(ApplicationStatusTypeEnum.NDG.getValue()); @@ -738,13 +723,13 @@ public class AppointmentDao { // Parse and return the NDG response return parseNdgResponse(responseJson); } catch (FeignException.Forbidden forbiddenException) { - log.error("403 Forbidden during NDG retrieval | ApplicationId: {}, HubId: {}", application.getId(), hub.getId()); + log.error("403 Forbidden during NDG retrieval | ApplicationId: {}, HubId: {}", application.getId(), hub.getId()); logForbiddenError(); // Regenerate the token and retry String newAuthorizationToken = regenerateTokenAndSave(hub, application); return retrieveNdgByVatNumber(vatNumber, newAuthorizationToken, hub, application); } catch (Exception e) { - log.error("Error during NDG retrieval | ApplicationId: {}, HubId: {}, Message: {}", application.getId(), hub.getId(), e.getMessage(), e); + log.error("Error during NDG retrieval | ApplicationId: {}, HubId: {}, Message: {}", application.getId(), hub.getId(), e.getMessage(), e); throw new RuntimeException("NDG retrieval failed.", e); } } @@ -993,7 +978,7 @@ public class AppointmentDao { return appointmentCreationResponse; } catch (FeignException.Forbidden forbiddenException) { - log.error("403 Forbidden received while retrieving template. Attempting to regenerate token and retry. Application ID: {}", applicationId); + log.error("403 Forbidden received while retrieving template. Attempting to regenerate token and retry. Application ID: {}", applicationId); regenerateTokenAndSave(hub, application); return createAppointment(applicationId, createAppointmentRequest); } @@ -1094,7 +1079,7 @@ public class AppointmentDao { } public AppointmentCreationRequest buildAppointmentCreationRequest(Long applicationId, CreateAppointmentRequest createAppointmentRequest, Long areaCode, - AppointmentCreationRequest templateRichiestaData) { + AppointmentCreationRequest templateRichiestaData) { ApplicationEntity application = applicationService.validateApplication(applicationId); CreateAppointmentRequest.Nota nota = createAppointmentRequest.getNota(); @@ -1207,7 +1192,7 @@ public class AppointmentDao { } private void uploadDocumentToExternalSystemSync(Long documentId, UploadDocToExternalSystemRequest docToExternalSystemRequest, ApplicationEntity application) { - log.info("Starting sync document upload for documentId: {}", documentId); + log.info("Starting sync document upload for documentId: {}", documentId); // Synchronous upload logic DocumentEntity systemDoc = documentDao.validateDocument(documentId); @@ -1248,7 +1233,7 @@ public class AppointmentDao { log.info("Document uploaded successfully to external system: {}", parsedResponse); } catch (FeignException.Forbidden forbiddenException) { - log.error("403 Forbidden from external system during upload for documentId: {}. Retrying with new token...", documentId); + log.error("403 Forbidden from external system during upload for documentId: {}. Retrying with new token...", documentId); regenerateTokenAndSave(hub, application); uploadDocumentToExternalSystemSync(documentId, docToExternalSystemRequest, application); } catch (Exception e) { @@ -1328,4 +1313,107 @@ public class AppointmentDao { throw new RuntimeException("Failed to parse response: " + e.getMessage(), e); } } -} \ No newline at end of file + + private void startAsyncNdgProcessing(Long applicationId) { + if (executorMap.containsKey(applicationId)) { + log.warn("Async processing already running for applicationId: {}", applicationId); + return; + } + ApplicationEntity application = applicationService.validateApplication(applicationId); + CompanyEntity company = companyService.validateCompany(application.getCompanyId()); + ApplicationEntity oldApplication = Utils.getClonedEntityForData(application); + CompanyEntity oldCompanyEntity = Utils.getClonedEntityForData(company); + ServletRequestAttributes requestAttributes = new ServletRequestAttributes(request); + ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(r -> { + Thread t = new Thread(r); + t.setName("AsyncNdgPolling-" + applicationId); + return t; + }); + + executorMap.put(applicationId, scheduler); + application.setNdgStatus(NdgStatusEnum.NDG_IN_PROGRESS.getValue()); + applicationRepository.save(application); + loggingUtil.addVersionHistory( + VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldApplication).newData(application).build()); + + long startTime = System.currentTimeMillis(); + long twoHoursMs = TimeUnit.HOURS.toMillis(2); + long fifteenMin = 15; + + HubEntity hub = hubRepository.findByHubId(application.getHubId()); + + // 1. Run full NDG generation logic once upfront + processNdgGeneration(application, company, hub); + + AtomicReference> futureRef = new AtomicReference<>(); + + // 2. Now define the polling logic ONLY + Runnable pollingTask = () -> { + RequestContextHolder.setRequestAttributes(requestAttributes, true); + Utils.setHttpServletRequestForThread(request, HttpMethodEnum.POST.getValue(), + GepafinConstant.CREATE_NDG, (Long) request.getAttribute(GepafinConstant.USER_ACTION_ID)); + try { + + // Stop if timeout + if (System.currentTimeMillis() - startTime > twoHoursMs) { + log.warn("Polling timed out for applicationId {}. Marking NDG_FAILED.", applicationId); + application.setNdgStatus(GepafinConstant.NDG_FAILED); + applicationRepository.save(application); + loggingUtil.addVersionHistory( + VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldApplication).newData(application).build()); + + futureRef.get().cancel(false); + shutdownScheduler(applicationId); + return; + } + + // If NDG already present or marked failed + if (isNdgValid(application.getNdg()) || GepafinConstant.NDG_FAILED.equals(application.getNdgStatus())) { + log.info("NDG already present or failed for applicationId {}. Stopping polling.", applicationId); + futureRef.get().cancel(false); + shutdownScheduler(applicationId); + return; + } + + // Only Visura polling here: + String visuraListJson = getVisuraList(application.getIdVisura(), hub.getAppointmentAuthTokenId(), application, hub); + String parsedNdg = parseNdgFromVisuraListResponse(visuraListJson); + + if (isNdgValid(parsedNdg)) { + log.info("Valid NDG parsed from VisuraList: {} | applicationId: {}", parsedNdg, application.getId()); + + company.setNdg(parsedNdg); + application.setNdg(parsedNdg); + application.setNdgStatus(GepafinConstant.NDG_GENERATED); + application.setStatus(ApplicationStatusTypeEnum.NDG.getValue()); +// application.setIdVisura(visuraResponse.getIdVisura()); + + companyRepository.save(company); + applicationRepository.save(application); + loggingUtil.addVersionHistory( + VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldApplication).newData(application).build()); + loggingUtil.addVersionHistory( + VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldCompanyEntity).newData(company).build()); + + ApplicationEvaluationEntity eval = applicationEvaluationService.validateApplicationEvaluation(application.getApplicationEvaluationId()); + Map placeholders = new HashMap<>(); + placeholders.put("{{call_name}}", application.getCall().getName()); + placeholders.put("{{protocol_number}}", String.valueOf(application.getProtocol().getProtocolNumber())); + notificationDao.sendNotificationToInstructor(placeholders, eval, NotificationTypeEnum.NDG_GENERATION); + notificationDao.sendNotificationToSuperUser(application, placeholders, NotificationTypeEnum.NDG_GENERATION); + + log.info("NDG saved successfully for applicationId: {}", application.getId()); + return; + } + + } catch (Exception ex) { + log.error("Error during NDG polling: {}", ex.getMessage(), ex); + } + }; + + ScheduledFuture future = scheduler.scheduleWithFixedDelay(pollingTask, fifteenMin, fifteenMin, TimeUnit.MINUTES); + futureRef.set(future); + } + +} + diff --git a/src/main/java/net/gepafin/tendermanagement/enums/NdgStatusEnum.java b/src/main/java/net/gepafin/tendermanagement/enums/NdgStatusEnum.java new file mode 100644 index 00000000..2d16a7bd --- /dev/null +++ b/src/main/java/net/gepafin/tendermanagement/enums/NdgStatusEnum.java @@ -0,0 +1,22 @@ +package net.gepafin.tendermanagement.enums; + + +import com.fasterxml.jackson.annotation.JsonValue; + +public enum NdgStatusEnum { + + NDG_INITITATED("NDG_INITITATED"), + NDG_GENERATED("NDG_GENERATED"), + NDG_IN_PROGRESS("NDG_IN_PROGRESS"); + + private String value; + + NdgStatusEnum(String value) { + this.value = value; + } + + @JsonValue + public String getValue() { + return value; + } +} From e01a00b13df15e9d045e7dfbadc4372ce815fd29 Mon Sep 17 00:00:00 2001 From: rajesh Date: Mon, 16 Jun 2025 15:35:29 +0530 Subject: [PATCH 21/35] Refactor NDG code --- .../constants/AppointmentApiConstant.java | 5 +- .../tendermanagement/dao/AppointmentDao.java | 318 +++++++++++------- .../tendermanagement/enums/NdgStatusEnum.java | 22 ++ 3 files changed, 229 insertions(+), 116 deletions(-) create mode 100644 src/main/java/net/gepafin/tendermanagement/enums/NdgStatusEnum.java diff --git a/src/main/java/net/gepafin/tendermanagement/constants/AppointmentApiConstant.java b/src/main/java/net/gepafin/tendermanagement/constants/AppointmentApiConstant.java index 6e5ed97c..799421fd 100644 --- a/src/main/java/net/gepafin/tendermanagement/constants/AppointmentApiConstant.java +++ b/src/main/java/net/gepafin/tendermanagement/constants/AppointmentApiConstant.java @@ -18,7 +18,7 @@ public class AppointmentApiConstant { public static final boolean CREA_ANAGRAFICA = Boolean.TRUE; public static final boolean SALVA_DOCUMENTI = Boolean.TRUE; public static final String VISURA_PROVIDER = "cerved"; - public static final String VISURA_TYPE = "StandardReport"; + public static final String VISURA_TYPE = "FullReport"; public static final String VISURA_MODE = "visure"; public static final String COD_AGENTE = "UtenzaAPIPortal"; public static final boolean IS_FROM_RATING = Boolean.FALSE; @@ -38,4 +38,7 @@ public class AppointmentApiConstant { public static final String COD_OPERAZIONE = "codOperazione"; public static final String MOTIVAZIONE_ID = "id"; public static final String PRODOTTO_CODE = "code"; + + public static final String WS_ANAGRAFICA_URL= "/WSAnagrafica.getList"; + } diff --git a/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java b/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java index f0fd43cb..66bf44d7 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java @@ -12,12 +12,7 @@ import net.gepafin.tendermanagement.config.Translator; import net.gepafin.tendermanagement.config.jwt.TokenProvider; import net.gepafin.tendermanagement.constants.AppointmentApiConstant; import net.gepafin.tendermanagement.constants.GepafinConstant; -import net.gepafin.tendermanagement.entities.ApplicationAmendmentRequestEntity; -import net.gepafin.tendermanagement.entities.ApplicationEntity; -import net.gepafin.tendermanagement.entities.ApplicationEvaluationEntity; -import net.gepafin.tendermanagement.entities.CompanyEntity; -import net.gepafin.tendermanagement.entities.DocumentEntity; -import net.gepafin.tendermanagement.entities.HubEntity; +import net.gepafin.tendermanagement.entities.*; import net.gepafin.tendermanagement.enums.*; import net.gepafin.tendermanagement.model.request.AppointmentCreationRequest; import net.gepafin.tendermanagement.model.request.AppointmentNdgRequest; @@ -30,11 +25,7 @@ import net.gepafin.tendermanagement.model.response.AppointmentCreationResponse; import net.gepafin.tendermanagement.model.response.AppointmentLoginResponse; import net.gepafin.tendermanagement.model.response.DocumentUploadResponse; import net.gepafin.tendermanagement.model.response.NdgResponse; -import net.gepafin.tendermanagement.repositories.ApplicationRepository; -import net.gepafin.tendermanagement.repositories.CompanyRepository; -import net.gepafin.tendermanagement.repositories.DocumentRepository; -import net.gepafin.tendermanagement.repositories.HubRepository; -import net.gepafin.tendermanagement.repositories.UserRepository; +import net.gepafin.tendermanagement.repositories.*; import net.gepafin.tendermanagement.service.AmazonS3Service; import net.gepafin.tendermanagement.service.ApplicationService; import net.gepafin.tendermanagement.service.CompanyService; @@ -62,13 +53,8 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; +import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicReference; -import java.util.concurrent.ScheduledExecutorService; @Slf4j @Component @@ -154,7 +140,7 @@ public class AppointmentDao { private final Map executorMap = new ConcurrentHashMap<>(); - + private final ConcurrentHashMap threadForDocumentMap = new ConcurrentHashMap<>(); private static final ThreadLocal threadLocalHubId = new ThreadLocal<>(); @@ -177,7 +163,7 @@ public class AppointmentDao { // Update application status log.info("Updating NDG status to IN_PROGRESS. applicationId: {}", applicationId); - application.setNdgStatus(GepafinConstant.NDG_IN_PROGRESS); + application.setNdgStatus(NdgStatusEnum.NDG_INITITATED.getValue()); applicationRepository.save(application); loggingUtil.addVersionHistory( @@ -401,6 +387,7 @@ public class AppointmentDao { } throw new RuntimeException("Max retries exceeded. Failed to login to Odessa."); } + private void CheckPasswordExpiredOrErrorInResponse(ApplicationEntity application, FeignException.Forbidden forbiddenException) { String responseBody = forbiddenException.contentUTF8(); @@ -436,89 +423,89 @@ public class AppointmentDao { } } } catch (IOException e) { - log.error("Unexpected exception during Odessa login.Error: {}",e.getMessage(), e); + log.error("Unexpected exception during Odessa login.Error: {}", e.getMessage(), e); } } - private void startAsyncNdgProcessing(Long applicationId) { - // If already polling for this applicationId, do nothing: - if (executorMap.containsKey(applicationId)) { - log.warn("Async processing already running for applicationId: {}", applicationId); - return; - } - ServletRequestAttributes requestAttributes = new ServletRequestAttributes(request); - - ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(runnable -> { - Thread t = new Thread(runnable); - t.setName("AsyncNdgProcessing-" + applicationId); - return t; - }); - executorMap.put(applicationId, scheduler); - - // Record the start time so we can stop after 2 hours: - long startTime = System.currentTimeMillis(); - long twoHoursMs = TimeUnit.HOURS.toMillis(2); - long fifteenMin = 15; // in MINUTES - - // We need a reference to cancel the scheduled task from inside itself when we're done: - AtomicReference> futureRef = new AtomicReference<>(); - - Runnable pollingTask = () -> { - RequestContextHolder.setRequestAttributes(requestAttributes, true); - Utils.setHttpServletRequestForThread(request,HttpMethodEnum.POST.getValue(),GepafinConstant.CREATE_NDG, (Long) request.getAttribute(GepafinConstant.USER_ACTION_ID)); - try { - // 1) If 2 hours have already passed, mark as FAILED and shut down: - if (System.currentTimeMillis() - startTime > twoHoursMs) { - ApplicationEntity app = applicationService.validateApplication(applicationId); - log.warn("2-hour timeout reached for applicationId {}. Marking NDG_FAILED.", applicationId); - app.setNdgStatus(GepafinConstant.NDG_FAILED); - applicationRepository.save(app); - - futureRef.get().cancel(false); - shutdownScheduler(applicationId); - return; - } - - // 2) Otherwise, call processNdgGeneration once: - processNdgGeneration(applicationId); - - // 3) After return, check if NDG is now set or timed out. If so, cancel & shut down: - ApplicationEntity updated = applicationService.validateApplication(applicationId); - if (isNdgValid(updated.getNdg())) { - log.info("NDG found for applicationId {}. Shutting down scheduler.", applicationId); - futureRef.get().cancel(false); - shutdownScheduler(applicationId); - } else if (updated.getNdgStatus() != null && updated.getNdgStatus().equals(GepafinConstant.NDG_FAILED)) { - log.info("NDG status is NDG_FAILED for applicationId {}. Shutting down scheduler.", applicationId); - futureRef.get().cancel(false); - shutdownScheduler(applicationId); - } - // Otherwise: no NDG yet, not timed out → next run happens in 15 minutes automatically. - } catch (Exception ex) { - log.error("Unexpected error during scheduled polling for applicationId {}: {}", applicationId, ex.getMessage(), ex); - try { - ApplicationEntity checkApp = applicationService.validateApplication(applicationId); - if (System.currentTimeMillis() - startTime > twoHoursMs) { - log.warn("After exception, 2-hour window passed for applicationId {}. Marking NDG_FAILED.", applicationId); - checkApp.setNdgStatus(GepafinConstant.NDG_FAILED); - applicationRepository.save(checkApp); - - futureRef.get().cancel(false); - shutdownScheduler(applicationId); - } - } catch (Exception ignore) { - futureRef.get().cancel(false); - shutdownScheduler(applicationId); - } - } - }; - - // Schedule pollingTask: run now (delay=0), then every fifteen minutes: - ScheduledFuture future = scheduler.scheduleWithFixedDelay(pollingTask, 0, // initial delay = 0 min → run immediately - fifteenMin, // subsequent runs every 15 minutes - TimeUnit.MINUTES); - futureRef.set(future); - } +// private void startAsyncNdgProcessing(Long applicationId) { +// // If already polling for this applicationId, do nothing: +// if (executorMap.containsKey(applicationId)) { +// log.warn("Async processing already running for applicationId: {}", applicationId); +// return; +// } +// ServletRequestAttributes requestAttributes = new ServletRequestAttributes(request); +// +// ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(runnable -> { +// Thread t = new Thread(runnable); +// t.setName("AsyncNdgProcessing-" + applicationId); +// return t; +// }); +// executorMap.put(applicationId, scheduler); +// +// // Record the start time so we can stop after 2 hours: +// long startTime = System.currentTimeMillis(); +// long twoHoursMs = TimeUnit.HOURS.toMillis(2); +// long fifteenMin = 15; // in MINUTES +// +// // We need a reference to cancel the scheduled task from inside itself when we're done: +// AtomicReference> futureRef = new AtomicReference<>(); +// +// Runnable pollingTask = () -> { +// RequestContextHolder.setRequestAttributes(requestAttributes, true); +// Utils.setHttpServletRequestForThread(request,HttpMethodEnum.POST.getValue(),GepafinConstant.CREATE_NDG, (Long) request.getAttribute(GepafinConstant.USER_ACTION_ID)); +// try { +// // 1) If 2 hours have already passed, mark as FAILED and shut down: +// if (System.currentTimeMillis() - startTime > twoHoursMs) { +// ApplicationEntity app = applicationService.validateApplication(applicationId); +// log.warn("2-hour timeout reached for applicationId {}. Marking NDG_FAILED.", applicationId); +// app.setNdgStatus(GepafinConstant.NDG_FAILED); +// applicationRepository.save(app); +// +// futureRef.get().cancel(false); +// shutdownScheduler(applicationId); +// return; +// } +// +// // 2) Otherwise, call processNdgGeneration once: +// processNdgGeneration(applicationId); +// +// // 3) After return, check if NDG is now set or timed out. If so, cancel & shut down: +// ApplicationEntity updated = applicationService.validateApplication(applicationId); +// if (isNdgValid(updated.getNdg())) { +// log.info("NDG found for applicationId {}. Shutting down scheduler.", applicationId); +// futureRef.get().cancel(false); +// shutdownScheduler(applicationId); +// } else if (updated.getNdgStatus() != null && updated.getNdgStatus().equals(GepafinConstant.NDG_FAILED)) { +// log.info("NDG status is NDG_FAILED for applicationId {}. Shutting down scheduler.", applicationId); +// futureRef.get().cancel(false); +// shutdownScheduler(applicationId); +// } +// // Otherwise: no NDG yet, not timed out → next run happens in 15 minutes automatically. +// } catch (Exception ex) { +// log.error("Unexpected error during scheduled polling for applicationId {}: {}", applicationId, ex.getMessage(), ex); +// try { +// ApplicationEntity checkApp = applicationService.validateApplication(applicationId); +// if (System.currentTimeMillis() - startTime > twoHoursMs) { +// log.warn("After exception, 2-hour window passed for applicationId {}. Marking NDG_FAILED.", applicationId); +// checkApp.setNdgStatus(GepafinConstant.NDG_FAILED); +// applicationRepository.save(checkApp); +// +// futureRef.get().cancel(false); +// shutdownScheduler(applicationId); +// } +// } catch (Exception ignore) { +// futureRef.get().cancel(false); +// shutdownScheduler(applicationId); +// } +// } +// }; +// +// // Schedule pollingTask: run now (delay=0), then every fifteen minutes: +// ScheduledFuture future = scheduler.scheduleWithFixedDelay(pollingTask, 0, // initial delay = 0 min → run immediately +// fifteenMin, // subsequent runs every 15 minutes +// TimeUnit.MINUTES); +// futureRef.set(future); +// } private void shutdownScheduler(Long applicationId) { @@ -529,12 +516,10 @@ public class AppointmentDao { log.info("Scheduler shut down for applicationId: {}", applicationId); } - private void processNdgGeneration(Long applicationId) { + private void processNdgGeneration(ApplicationEntity application, CompanyEntity company, HubEntity hub) { // Validate application, company, and hub + Long applicationId = application.getId(); log.info("Starting NDG generation process for applicationId: {}", applicationId); - ApplicationEntity application = applicationService.validateApplication(applicationId); - CompanyEntity company = companyService.validateCompany(application.getCompanyId()); - HubEntity hub = hubRepository.findByHubId(application.getHubId()); if (!hub.getUniqueUuid().equals(defaultHubUuid)) { log.info("Ndg cannot be created for another Hub, it is default for Gepafin."); @@ -552,7 +537,7 @@ public class AppointmentDao { // Try retrieving NDG by VAT number AppointmentLoginResponse ndgResponse = retrieveNdgByVatNumber(company.getVatNumber(), authorizationToken, hub, application); if (isNdgValid(ndgResponse.getNdg())) { - saveNdgAndIdVisura(application, company, ndgResponse.getNdg()); + saveNdg(application, company, ndgResponse.getNdg()); log.info("NDG successfully generated for applicationId: {}", applicationId); } else { log.info("Polling for NDG for applicationId: {}", applicationId); @@ -568,7 +553,7 @@ public class AppointmentDao { log.info("Starting singleâ€shot NDG polling attempt for applicationId: {}, CompanyId: {}, HubId: {}", application.getId(), company.getId(), hub.getId()); ApplicationEntity oldApplication = Utils.getClonedEntityForData(application); - CompanyEntity oldCompanyEntity=Utils.getClonedEntityForData(company); + CompanyEntity oldCompanyEntity = Utils.getClonedEntityForData(company); long startTime = System.currentTimeMillis(); long twoHoursMs = TimeUnit.HOURS.toMillis(2); @@ -589,7 +574,7 @@ public class AppointmentDao { company.setNdg(fetchedNdg); application.setNdg(fetchedNdg); - application.setNdgStatus(GepafinConstant.NDG_GENERATED); + application.setNdgStatus(NdgStatusEnum.NDG_GENERATED.getValue()); application.setStatus(ApplicationStatusTypeEnum.NDG.getValue()); application.setIdVisura(visuraResponse.getIdVisura()); @@ -678,10 +663,10 @@ public class AppointmentDao { return ndg != null && !ndg.isEmpty(); } - private void saveNdgAndIdVisura(ApplicationEntity application, CompanyEntity company, String ndg) { + private void saveNdg(ApplicationEntity application, CompanyEntity company, String ndg) { ApplicationEntity oldApplication = Utils.getClonedEntityForData(application); - CompanyEntity oldCompanyEntity=Utils.getClonedEntityForData(company); + CompanyEntity oldCompanyEntity = Utils.getClonedEntityForData(company); application.setNdg(ndg); application.setNdgStatus(GepafinConstant.NDG_GENERATED); application.setStatus(ApplicationStatusTypeEnum.NDG.getValue()); @@ -738,13 +723,13 @@ public class AppointmentDao { // Parse and return the NDG response return parseNdgResponse(responseJson); } catch (FeignException.Forbidden forbiddenException) { - log.error("403 Forbidden during NDG retrieval | ApplicationId: {}, HubId: {}", application.getId(), hub.getId()); + log.error("403 Forbidden during NDG retrieval | ApplicationId: {}, HubId: {}", application.getId(), hub.getId()); logForbiddenError(); // Regenerate the token and retry String newAuthorizationToken = regenerateTokenAndSave(hub, application); return retrieveNdgByVatNumber(vatNumber, newAuthorizationToken, hub, application); } catch (Exception e) { - log.error("Error during NDG retrieval | ApplicationId: {}, HubId: {}, Message: {}", application.getId(), hub.getId(), e.getMessage(), e); + log.error("Error during NDG retrieval | ApplicationId: {}, HubId: {}, Message: {}", application.getId(), hub.getId(), e.getMessage(), e); throw new RuntimeException("NDG retrieval failed.", e); } } @@ -993,7 +978,7 @@ public class AppointmentDao { return appointmentCreationResponse; } catch (FeignException.Forbidden forbiddenException) { - log.error("403 Forbidden received while retrieving template. Attempting to regenerate token and retry. Application ID: {}", applicationId); + log.error("403 Forbidden received while retrieving template. Attempting to regenerate token and retry. Application ID: {}", applicationId); regenerateTokenAndSave(hub, application); return createAppointment(applicationId, createAppointmentRequest); } @@ -1094,7 +1079,7 @@ public class AppointmentDao { } public AppointmentCreationRequest buildAppointmentCreationRequest(Long applicationId, CreateAppointmentRequest createAppointmentRequest, Long areaCode, - AppointmentCreationRequest templateRichiestaData) { + AppointmentCreationRequest templateRichiestaData) { ApplicationEntity application = applicationService.validateApplication(applicationId); CreateAppointmentRequest.Nota nota = createAppointmentRequest.getNota(); @@ -1207,7 +1192,7 @@ public class AppointmentDao { } private void uploadDocumentToExternalSystemSync(Long documentId, UploadDocToExternalSystemRequest docToExternalSystemRequest, ApplicationEntity application) { - log.info("Starting sync document upload for documentId: {}", documentId); + log.info("Starting sync document upload for documentId: {}", documentId); // Synchronous upload logic DocumentEntity systemDoc = documentDao.validateDocument(documentId); @@ -1248,7 +1233,7 @@ public class AppointmentDao { log.info("Document uploaded successfully to external system: {}", parsedResponse); } catch (FeignException.Forbidden forbiddenException) { - log.error("403 Forbidden from external system during upload for documentId: {}. Retrying with new token...", documentId); + log.error("403 Forbidden from external system during upload for documentId: {}. Retrying with new token...", documentId); regenerateTokenAndSave(hub, application); uploadDocumentToExternalSystemSync(documentId, docToExternalSystemRequest, application); } catch (Exception e) { @@ -1328,4 +1313,107 @@ public class AppointmentDao { throw new RuntimeException("Failed to parse response: " + e.getMessage(), e); } } -} \ No newline at end of file + + private void startAsyncNdgProcessing(Long applicationId) { + if (executorMap.containsKey(applicationId)) { + log.warn("Async processing already running for applicationId: {}", applicationId); + return; + } + ApplicationEntity application = applicationService.validateApplication(applicationId); + CompanyEntity company = companyService.validateCompany(application.getCompanyId()); + ApplicationEntity oldApplication = Utils.getClonedEntityForData(application); + CompanyEntity oldCompanyEntity = Utils.getClonedEntityForData(company); + ServletRequestAttributes requestAttributes = new ServletRequestAttributes(request); + ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(r -> { + Thread t = new Thread(r); + t.setName("AsyncNdgPolling-" + applicationId); + return t; + }); + + executorMap.put(applicationId, scheduler); + application.setNdgStatus(NdgStatusEnum.NDG_IN_PROGRESS.getValue()); + applicationRepository.save(application); + loggingUtil.addVersionHistory( + VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldApplication).newData(application).build()); + + long startTime = System.currentTimeMillis(); + long twoHoursMs = TimeUnit.HOURS.toMillis(2); + long fifteenMin = 15; + + HubEntity hub = hubRepository.findByHubId(application.getHubId()); + + // 1. Run full NDG generation logic once upfront + processNdgGeneration(application, company, hub); + + AtomicReference> futureRef = new AtomicReference<>(); + + // 2. Now define the polling logic ONLY + Runnable pollingTask = () -> { + RequestContextHolder.setRequestAttributes(requestAttributes, true); + Utils.setHttpServletRequestForThread(request, HttpMethodEnum.POST.getValue(), + GepafinConstant.CREATE_NDG, (Long) request.getAttribute(GepafinConstant.USER_ACTION_ID)); + try { + + // Stop if timeout + if (System.currentTimeMillis() - startTime > twoHoursMs) { + log.warn("Polling timed out for applicationId {}. Marking NDG_FAILED.", applicationId); + application.setNdgStatus(GepafinConstant.NDG_FAILED); + applicationRepository.save(application); + loggingUtil.addVersionHistory( + VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldApplication).newData(application).build()); + + futureRef.get().cancel(false); + shutdownScheduler(applicationId); + return; + } + + // If NDG already present or marked failed + if (isNdgValid(application.getNdg()) || GepafinConstant.NDG_FAILED.equals(application.getNdgStatus())) { + log.info("NDG already present or failed for applicationId {}. Stopping polling.", applicationId); + futureRef.get().cancel(false); + shutdownScheduler(applicationId); + return; + } + + // Only Visura polling here: + String visuraListJson = getVisuraList(application.getIdVisura(), hub.getAppointmentAuthTokenId(), application, hub); + String parsedNdg = parseNdgFromVisuraListResponse(visuraListJson); + + if (isNdgValid(parsedNdg)) { + log.info("Valid NDG parsed from VisuraList: {} | applicationId: {}", parsedNdg, application.getId()); + + company.setNdg(parsedNdg); + application.setNdg(parsedNdg); + application.setNdgStatus(GepafinConstant.NDG_GENERATED); + application.setStatus(ApplicationStatusTypeEnum.NDG.getValue()); +// application.setIdVisura(visuraResponse.getIdVisura()); + + companyRepository.save(company); + applicationRepository.save(application); + loggingUtil.addVersionHistory( + VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldApplication).newData(application).build()); + loggingUtil.addVersionHistory( + VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldCompanyEntity).newData(company).build()); + + ApplicationEvaluationEntity eval = applicationEvaluationService.validateApplicationEvaluation(application.getApplicationEvaluationId()); + Map placeholders = new HashMap<>(); + placeholders.put("{{call_name}}", application.getCall().getName()); + placeholders.put("{{protocol_number}}", String.valueOf(application.getProtocol().getProtocolNumber())); + notificationDao.sendNotificationToInstructor(placeholders, eval, NotificationTypeEnum.NDG_GENERATION); + notificationDao.sendNotificationToSuperUser(application, placeholders, NotificationTypeEnum.NDG_GENERATION); + + log.info("NDG saved successfully for applicationId: {}", application.getId()); + return; + } + + } catch (Exception ex) { + log.error("Error during NDG polling: {}", ex.getMessage(), ex); + } + }; + + ScheduledFuture future = scheduler.scheduleWithFixedDelay(pollingTask, fifteenMin, fifteenMin, TimeUnit.MINUTES); + futureRef.set(future); + } + +} + diff --git a/src/main/java/net/gepafin/tendermanagement/enums/NdgStatusEnum.java b/src/main/java/net/gepafin/tendermanagement/enums/NdgStatusEnum.java new file mode 100644 index 00000000..2d16a7bd --- /dev/null +++ b/src/main/java/net/gepafin/tendermanagement/enums/NdgStatusEnum.java @@ -0,0 +1,22 @@ +package net.gepafin.tendermanagement.enums; + + +import com.fasterxml.jackson.annotation.JsonValue; + +public enum NdgStatusEnum { + + NDG_INITITATED("NDG_INITITATED"), + NDG_GENERATED("NDG_GENERATED"), + NDG_IN_PROGRESS("NDG_IN_PROGRESS"); + + private String value; + + NdgStatusEnum(String value) { + this.value = value; + } + + @JsonValue + public String getValue() { + return value; + } +} From 6d66037adbb4a8728987b1733868ca98bb7d377f Mon Sep 17 00:00:00 2001 From: rajesh Date: Wed, 18 Jun 2025 20:23:12 +0530 Subject: [PATCH 22/35] Done ticket GEPAFINBE-223 & GEPAFINBE-224 --- .../constants/GepafinConstant.java | 2 + .../tendermanagement/dao/ApplicationDao.java | 82 +++++++++++++----- .../tendermanagement/dao/AppointmentDao.java | 85 ++++++++++++++++--- .../tendermanagement/dao/CompanyDao.java | 67 +++++++++++---- .../entities/ApplicationEntity.java | 3 + .../entities/NdganagEntity.java | 33 +++++++ .../enums/UserActionContextEnum.java | 2 + .../repositories/CompanyRepository.java | 2 + .../repositories/NdganagRepository.java | 14 +++ .../service/AppointmentService.java | 3 + .../service/CompanyService.java | 1 + .../service/impl/AppointmentServiceImpl.java | 12 +++ .../service/impl/CompanyServiceImpl.java | 8 +- .../web/rest/api/AppointmentApi.java | 11 +++ .../web/rest/api/CompanyApi.java | 13 +++ .../rest/api/impl/AppointmentController.java | 16 ++++ .../rest/api/impl/CompanyApiController.java | 13 +++ .../db/changelog/db.changelog-1.0.0.xml | 20 +++++ src/main/resources/message_en.properties | 2 + src/main/resources/message_it.properties | 2 + 20 files changed, 344 insertions(+), 47 deletions(-) create mode 100644 src/main/java/net/gepafin/tendermanagement/entities/NdganagEntity.java create mode 100644 src/main/java/net/gepafin/tendermanagement/repositories/NdganagRepository.java diff --git a/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java b/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java index 90988320..3874fc83 100644 --- a/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java +++ b/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java @@ -572,6 +572,8 @@ public class GepafinConstant { public static final String PROTOCOL_EXTERNAL_DATE="DATA_PG_DT"; public static final String PROTOCOL_DOC_SUFFIX="#noauth"; public static final String CREATE_NDG="CHECK_OR_CREATE_NDG_CODE"; + public static final String NDG_NOT_FOUND="ndg.not.found"; + public static final String EMAIL_PEC_REQUIRED="email.pec.cannot.null"; } diff --git a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java index 38067a66..02fb01d2 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java @@ -557,25 +557,69 @@ public class ApplicationDao { VersionActionTypeEnum actionType = VersionActionTypeEnum.INSERT; List contentResponseBeans = formDao.convertFormEntityToFormResponseBean(formEntity).getContent(); - contentResponseBeans.stream() - .filter(content -> "numberinput".equals(content.getName()) && content.getId().toString().equals(applicationFormFieldRequestBean.getFieldId())) - .map(ContentResponseBean::getSettings) - .flatMap(List::stream) - .filter(setting -> "isRequestedAmount".equals(setting.getName()) && Boolean.TRUE.equals(setting.getValue())) - .findFirst() - .ifPresent(setting -> { - Object fieldValue = applicationFormFieldRequestBean.getFieldValue(); - if(fieldValue!=null) { - try { - BigDecimal amountRequested = new BigDecimal(fieldValue.toString()); - applicationFormEntity.getApplication().setAmountRequested(amountRequested); - log.info("Set amountRequested to {} for Application ID: {}", amountRequested, applicationFormEntity.getApplication().getId()); - } catch (NumberFormatException e) { - log.error("Invalid number format for requested amount: {}", fieldValue, e); - throw new IllegalArgumentException("Field value is not a valid number: " + fieldValue, e); - } - } - }); +// contentResponseBeans.stream() +// .filter(content -> "numberinput".equals(content.getName()) && content.getId().toString().equals(applicationFormFieldRequestBean.getFieldId())) +// .map(ContentResponseBean::getSettings) +// .flatMap(List::stream) +// .filter(setting -> "isRequestedAmount".equals(setting.getName()) && Boolean.TRUE.equals(setting.getValue())) +// .findFirst() +// .ifPresent(setting -> { +// Object fieldValue = applicationFormFieldRequestBean.getFieldValue(); +// if(fieldValue!=null) { +// try { +// BigDecimal amountRequested = new BigDecimal(fieldValue.toString()); +// applicationFormEntity.getApplication().setAmountRequested(amountRequested); +// log.info("Set amountRequested to {} for Application ID: {}", amountRequested, applicationFormEntity.getApplication().getId()); +// } catch (NumberFormatException e) { +// log.error("Invalid number format for requested amount: {}", fieldValue, e); +// throw new IllegalArgumentException("Field value is not a valid number: " + fieldValue, e); +// } +// } +// }); + + contentResponseBeans.stream() + .filter(content -> content.getId().toString().equals(applicationFormFieldRequestBean.getFieldId())) + .findFirst() + .ifPresent(content -> { + Object fieldValue = applicationFormFieldRequestBean.getFieldValue(); + if (fieldValue == null) { + return; + } + + // Convert settings list to a map + Map settingMap = content.getSettings().stream() + .collect(Collectors.toMap(SettingResponseBean::getName, SettingResponseBean::getValue, (v1, v2) -> v1)); + + String fieldType = content.getName(); + + // Define handlers for different (fieldType + settingName) combinations + Map handlers = new HashMap<>(); + + // Add handler for isRequestedAmount + if ("numberinput".equals(fieldType) && Boolean.TRUE.equals(settingMap.get("isRequestedAmount"))) { + handlers.put("isRequestedAmount", () -> { + try { + BigDecimal amountRequested = new BigDecimal(fieldValue.toString()); + applicationFormEntity.getApplication().setAmountRequested(amountRequested); + log.info("Set amountRequested to {} for Application ID: {}", amountRequested, applicationFormEntity.getApplication().getId()); + } catch (NumberFormatException e) { + log.error("Invalid number format for requested amount: {}", fieldValue, e); + throw new IllegalArgumentException("Field value is not a valid number: " + fieldValue, e); + } + }); + } + + // Add handler for isPecEmail + if ("textinput".equals(fieldType) && Boolean.TRUE.equals(settingMap.get("isPecEmail"))) { + handlers.put("isPecEmail", () -> { + applicationFormEntity.getApplication().setPecEmail(fieldValue.toString()); + log.info("Set PEC to {} for Application ID: {}", fieldValue, applicationFormEntity.getApplication().getId()); + }); + } + + // Run all applicable handlers + handlers.values().forEach(Runnable::run); + }); ApplicationFormFieldEntity oldApplicationFormFieldData = null; diff --git a/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java b/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java index 66bf44d7..76a7fcff 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java @@ -138,6 +138,9 @@ public class AppointmentDao { @Autowired private ApplicationEvaluationDao applicationEvaluationDao; + @Autowired + private NdganagRepository ndganagRepository; + private final Map executorMap = new ConcurrentHashMap<>(); @@ -535,7 +538,13 @@ public class AppointmentDao { String authorizationToken = getBearerToken(hub); // Try retrieving NDG by VAT number - AppointmentLoginResponse ndgResponse = retrieveNdgByVatNumber(company.getVatNumber(), authorizationToken, hub, application); + NdganagEntity ndganagEntity = ndganagRepository.findByVatNumber(company.getVatNumber()); + AppointmentLoginResponse ndgResponse=new AppointmentLoginResponse(); + if (ndganagEntity != null || ndganagEntity.getNdg() != null) { + ndgResponse.setNdg(ndganagEntity.getNdg()); + }else { + ndgResponse = retrieveNdgByVatNumber(company.getVatNumber(), authorizationToken, hub, application); + } if (isNdgValid(ndgResponse.getNdg())) { saveNdg(application, company, ndgResponse.getNdg()); log.info("NDG successfully generated for applicationId: {}", applicationId); @@ -716,10 +725,7 @@ public class AppointmentDao { try { log.info("Initiating NDG retrieval by VAT number | ApplicationId: {}, HubId: {}, VAT: {}", application.getId(), hub.getId(), vatNumber); // Prepare the NDG request - AppointmentNdgRequest ndgRequest = getAppointmentNdgRequest(vatNumber); - // Call the API to retrieve NDG - ResponseEntity response = appointmentApiService.getNdgByVatNumber(ndgRequest, authorizationToken); - String responseJson = Utils.convertObjectToJson(response.getBody()); + String responseJson = getNdgFromExternalService(vatNumber, authorizationToken); // Parse and return the NDG response return parseNdgResponse(responseJson); } catch (FeignException.Forbidden forbiddenException) { @@ -875,30 +881,38 @@ public class AppointmentDao { } public AppointmentLoginResponse parseNdgResponse(String jsonResponse) { + AppointmentLoginResponse loginResponse = new AppointmentLoginResponse(); + String ndg=extractNdg(jsonResponse); + if (ndg==null){ return null;} + else { + loginResponse.setNdg(ndg); + } + return loginResponse; + } + private String extractNdg(String jsonResponse) { try { ObjectMapper objectMapper = new ObjectMapper(); JsonNode rootNode = objectMapper.readTree(jsonResponse); JsonNode dataArray = rootNode.get(GepafinConstant.DATA_STRING); if (dataArray == null || !dataArray.isArray() || dataArray.isEmpty()) { log.info("NDG data is empty or missing in the response."); - AppointmentLoginResponse emptyResponse = new AppointmentLoginResponse(); - emptyResponse.setNdg(null); - return emptyResponse; + return null; } JsonNode firstDataEntry = dataArray.get(0); AppointmentLoginResponse response = new AppointmentLoginResponse(); if (firstDataEntry.has(GepafinConstant.NDG_STRING)) { - response.setNdg(normalizeNullValue(firstDataEntry.get(GepafinConstant.NDG_STRING).asText())); + String ndg=normalizeNullValue(firstDataEntry.get(GepafinConstant.NDG_STRING).asText()); + return ndg; } - return response; } catch (Exception e) { log.error("Failed to parse response: {}", e.getMessage(), e); throw new RuntimeException("Failed to parse NDG response.", e); } + return null; } - private String normalizeNullValue(String value) { + public String normalizeNullValue(String value) { return (value == null || GepafinConstant.NULL_STRING.equalsIgnoreCase(value.trim())) ? null : value; } @@ -1415,5 +1429,54 @@ public class AppointmentDao { futureRef.set(future); } + public NdgResponse getNdgByVatNumber(String vatNumber, UserEntity userEntity) { + HubEntity hub=userEntity.getHub(); + NdganagEntity ndganagEntity = ndganagRepository.findByVatNumber(vatNumber); + NdgResponse ndgResponse=new NdgResponse(); + String jsonResponse=null; + String authorizationToken = hub.getAppointmentAuthTokenId(); + if (ndganagEntity == null || ndganagEntity.getNdg() == null) { + + try { + log.info("Initiating NDG retrieval by VAT number | HubId: {}, VAT: {}", hub.getId(), vatNumber); + // Prepare the NDG request + jsonResponse=getNdgFromExternalService(vatNumber, authorizationToken); + checkAndSaveNdg(jsonResponse, ndgResponse); + // Parse and return the NDG response + } catch (FeignException.Forbidden forbiddenException) { + log.error("403 Forbidden during NDG retrieval | HubId: {}", hub.getId()); + logForbiddenError(); + // Regenerate the token and retry + String newAuthorizationToken = regenerateTokenAndSave(hub, null); + jsonResponse= getNdgFromExternalService(vatNumber,newAuthorizationToken); + if (checkAndSaveNdg(jsonResponse, ndgResponse)) return null; + } catch (Exception e) { + log.error("Error during NDG retrieval |, HubId: {}, Message: {}", hub.getId(), e.getMessage(), e); + throw new RuntimeException("NDG retrieval failed.", e); + } + }else { + ndgResponse.setNdg(ndganagEntity.getNdg()); + } + return ndgResponse; + } + + private boolean checkAndSaveNdg(String jsonResponse, NdgResponse ndgResponse) { + String ndg=extractNdg(jsonResponse); + if (ndg==null){ + return true; + } + else { + ndgResponse.setNdg(ndg); + } + return false; + } + + private String getNdgFromExternalService(String vatNumber, String authorizationToken) { + AppointmentNdgRequest ndgRequest = getAppointmentNdgRequest(vatNumber); + // Call the API to retrieve NDG + ResponseEntity response = appointmentApiService.getNdgByVatNumber(ndgRequest, authorizationToken); + String responseJson = Utils.convertObjectToJson(response.getBody()); + return responseJson; + } } diff --git a/src/main/java/net/gepafin/tendermanagement/dao/CompanyDao.java b/src/main/java/net/gepafin/tendermanagement/dao/CompanyDao.java index 26ca3f29..8b59eebe 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/CompanyDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/CompanyDao.java @@ -2,6 +2,7 @@ package net.gepafin.tendermanagement.dao; import org.springframework.data.domain.Pageable; // Correct package +import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -413,7 +414,7 @@ public class CompanyDao { Translator.toLocale(GepafinConstant.INVALID_LIMIT)); } - int successfulUpdates = 0; + int successfulUpdates = 0; int failedUpdates = 0; int invalidVatNumbers = 0; @@ -531,7 +532,11 @@ public class CompanyDao { Map dataMap = Utils.extractMap(companyDataMap, "data"); Object dataObj = companyDataMap.get("data"); - if (dataMap == null) { + if (dataObj instanceof Map singleMap) { + dataMap = (Map) singleMap; + } else if (dataObj instanceof List list && !list.isEmpty() && list.get(0) instanceof Map firstMap) { + dataMap = (Map) firstMap; + } else { log.warn("Company ID {}: 'data' section is missing or invalid in the JSON.", company.getId()); return; } @@ -540,11 +545,11 @@ public class CompanyDao { updateCodiceAtecoField(company); } else { - Object pecEmail = Utils.extractMap(dataMap, "pec"); + Object pecEmail = dataMap.get("pec"); if (pecEmail == null) { log.warn("Company ID {}: 'pec' section is missing or invalid.", company.getId()); - company.setPec((String) pecEmail); } + company.setPec((String) pecEmail); // Extract 'atecoClassification' section Map atecoClassificationMap = Utils.extractMap(dataMap, "atecoClassification"); @@ -577,49 +582,79 @@ public class CompanyDao { } public void getCompanyEntity() { - List companyEntities=companyRepository.findAll(); + List companyEntities=companyRepository.findByJsonIsNotNullAndPecIsNull(); + List companyEntityList=new ArrayList<>(); for (CompanyEntity company:companyEntities){ - if(company.getJson()!=null){ + if(company.getJson()!=null && company.getPec()==null){ if (company == null || company.getJson() == null || company.getJson().isEmpty()) { log.warn("Company is null or JSON data is empty."); - return; + continue; } Map vatCheckResponse = Utils.convertJsonStringToMap(company.getJson()); if (vatCheckResponse == null) { log.warn("Company ID {}: Invalid JSON response.", company.getId()); - return; + continue; } Map companyDataMap = Utils.convertJsonStringToMap(company.getJson()); if (companyDataMap == null) { log.warn("Company ID {}: Failed to parse JSON data.", company.getId()); - return; + continue; } + Object dataObj = vatCheckResponse.get("data"); - if (!(dataObj instanceof Map dataMap)) { - log.warn("Company ID {}: 'data' is missing or not a valid object.", company.getId()); - return; + Map dataMap=null; +// if (!(dataObj instanceof Map dataMap)) { +// log.warn("Company ID {}: 'data' is missing or not a valid object.", company.getId()); +// continue; +// } + + if (dataObj instanceof Map singleMap) { + dataMap = (Map) singleMap; + } else if (dataObj instanceof List list && !list.isEmpty() && list.get(0) instanceof Map firstMap) { + dataMap = (Map) firstMap; + } else { + log.warn("Company ID {}: 'data' section is missing or invalid in the JSON.", company.getId()); + continue; } + + if (dataMap.containsKey("pec")) { + Object pecEmailObj = dataMap.get("pec"); + if (pecEmailObj instanceof String pec && !pec.isEmpty()) { + company.setPec(pec); // Only set if valid string + companyEntityList.add(company); + } else { + log.warn("Company ID {}: 'pec' is missing, empty, or not a string.", company.getId()); + continue; + } + } + + if (!dataMap.containsKey("dettaglio")) { log.warn("Company ID {}: 'dettaglio' not present inside 'data'. Skipping codiceAteco update.", company.getId()); - return; + continue; } + Object dettaglioObj = dataMap.get("dettaglio"); if (!(dettaglioObj instanceof Map dettaglio)) { log.warn("Company ID {}: 'dettaglio' is not a valid object.", company.getId()); - return; + continue; } - Object pecEmailObj = dettaglio.get("pec"); if (pecEmailObj instanceof String pec && !pec.isEmpty()) { - company.setPec(pec); // Only set if valid string + if(pec!=null) { + company.setPec(pec); // Only set if valid string + companyEntityList.add(company); + } } else { log.warn("Company ID {}: 'pec' is missing, empty, or not a string.", company.getId()); + continue; } } } + companyRepository.saveAll(companyEntityList); } } diff --git a/src/main/java/net/gepafin/tendermanagement/entities/ApplicationEntity.java b/src/main/java/net/gepafin/tendermanagement/entities/ApplicationEntity.java index e2208c77..8e783c70 100644 --- a/src/main/java/net/gepafin/tendermanagement/entities/ApplicationEntity.java +++ b/src/main/java/net/gepafin/tendermanagement/entities/ApplicationEntity.java @@ -73,4 +73,7 @@ public class ApplicationEntity extends BaseEntity { @Column(name = "APPLICATION_EVALUATION_ID") private Long applicationEvaluationId; + @Column(name = "PEC_EMAIL") + private String pecEmail; + } \ No newline at end of file diff --git a/src/main/java/net/gepafin/tendermanagement/entities/NdganagEntity.java b/src/main/java/net/gepafin/tendermanagement/entities/NdganagEntity.java new file mode 100644 index 00000000..463f31c9 --- /dev/null +++ b/src/main/java/net/gepafin/tendermanagement/entities/NdganagEntity.java @@ -0,0 +1,33 @@ +package net.gepafin.tendermanagement.entities; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; +import lombok.Data; +import org.hibernate.annotations.Where; + +@Entity +@Table(name = "NDGANAG") +@Data +@Where(clause = "is_deleted = false") +public class NdganagEntity extends BaseEntity{ + + @Column(name = "NDG") + private String ndg; + + @Column(name = "COMPANY_NAME") + private String companyName; + + @Column(name = "VAT_NUMBER") + private String vatNumber; + + @Column(name = "CODICE_FISCALE") + private String codiceFiscale; + + @Column(name = "JSON") + private String json; + + @Column(name = "IS_DELETED") + private Boolean isDeleted; + +} diff --git a/src/main/java/net/gepafin/tendermanagement/enums/UserActionContextEnum.java b/src/main/java/net/gepafin/tendermanagement/enums/UserActionContextEnum.java index fe2e4cd1..1de6b333 100644 --- a/src/main/java/net/gepafin/tendermanagement/enums/UserActionContextEnum.java +++ b/src/main/java/net/gepafin/tendermanagement/enums/UserActionContextEnum.java @@ -67,6 +67,7 @@ public enum UserActionContextEnum { GET_COMPANY_BY_USER("GET_COMPANY_BY_USER"), REMOVE_COMPANY_FROM_USER("REMOVE_COMPANY_FROM_USER"), UPDATE_COMPANY_JSON("UPDATE_COMPANY_JSON"), + EXTRACT_PEC_FROM_COMPANY("EXTRACT_PEC_FROM_COMPANY"), /** LookUpData action context **/ CREATE_LOOKUP_DATA("CREATE_LOOKUP_DATA"), @@ -180,6 +181,7 @@ public enum UserActionContextEnum { CHECK_OR_CREATE_NDG_CODE("CHECK_OR_CREATE_NDG_CODE"), CREATE_APPOINTMENT("CREATE_APPOINTMENT"), UPLOAD_DOCUMENT_TO_EXTERNAL_SYSTEM("UPLOAD_DOCUMENT_TO_EXTERNAL_SYSTEM"), + GET_NDG_BY_VAT_NUMBER("GET_NDG_BY_VAT_NUMBER"), GET_ALL_NOTIFICATION_BY_PAGINATION("GET_ALL_NOTIFICATION_BY_PAGINATION"), GET_ALL_CALL_BY_PAGINATION("GET_ALL_CALL_BY_PAGINATION"), diff --git a/src/main/java/net/gepafin/tendermanagement/repositories/CompanyRepository.java b/src/main/java/net/gepafin/tendermanagement/repositories/CompanyRepository.java index 350b9b49..39d261ea 100644 --- a/src/main/java/net/gepafin/tendermanagement/repositories/CompanyRepository.java +++ b/src/main/java/net/gepafin/tendermanagement/repositories/CompanyRepository.java @@ -31,5 +31,7 @@ public interface CompanyRepository extends JpaRepository { """) Page findCompaniesWithMissingVatCheck(Pageable pageable); + List findByJsonIsNotNullAndPecIsNull(); + } diff --git a/src/main/java/net/gepafin/tendermanagement/repositories/NdganagRepository.java b/src/main/java/net/gepafin/tendermanagement/repositories/NdganagRepository.java new file mode 100644 index 00000000..ad0fe99a --- /dev/null +++ b/src/main/java/net/gepafin/tendermanagement/repositories/NdganagRepository.java @@ -0,0 +1,14 @@ +package net.gepafin.tendermanagement.repositories; + +import net.gepafin.tendermanagement.entities.NdganagEntity; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + + +@Repository +public interface NdganagRepository extends JpaRepository { + + NdganagEntity findByVatNumber(String vatNumber); + + +} diff --git a/src/main/java/net/gepafin/tendermanagement/service/AppointmentService.java b/src/main/java/net/gepafin/tendermanagement/service/AppointmentService.java index 842901a4..e324804c 100644 --- a/src/main/java/net/gepafin/tendermanagement/service/AppointmentService.java +++ b/src/main/java/net/gepafin/tendermanagement/service/AppointmentService.java @@ -13,4 +13,7 @@ public interface AppointmentService { AppointmentCreationResponse createAppointmentForApplication(HttpServletRequest request, Long applicationId, CreateAppointmentRequest createAppointmentRequest); DocumentUploadResponse uploadDocToExternalSystem(HttpServletRequest request, Long documentId, UploadDocToExternalSystemRequest docToExternalSystemRequest); + + NdgResponse getNdgByVatNumber(HttpServletRequest request,String vatNumber); + } diff --git a/src/main/java/net/gepafin/tendermanagement/service/CompanyService.java b/src/main/java/net/gepafin/tendermanagement/service/CompanyService.java index 74b6be0e..4c868f1e 100644 --- a/src/main/java/net/gepafin/tendermanagement/service/CompanyService.java +++ b/src/main/java/net/gepafin/tendermanagement/service/CompanyService.java @@ -47,4 +47,5 @@ public interface CompanyService { void updateMissingVatCheckResponses(HttpServletRequest request, LimitRequest limitRequest); + void extractPecFromJson(HttpServletRequest request); } diff --git a/src/main/java/net/gepafin/tendermanagement/service/impl/AppointmentServiceImpl.java b/src/main/java/net/gepafin/tendermanagement/service/impl/AppointmentServiceImpl.java index 2e7960df..6c49b214 100644 --- a/src/main/java/net/gepafin/tendermanagement/service/impl/AppointmentServiceImpl.java +++ b/src/main/java/net/gepafin/tendermanagement/service/impl/AppointmentServiceImpl.java @@ -2,12 +2,14 @@ package net.gepafin.tendermanagement.service.impl; import jakarta.servlet.http.HttpServletRequest; import net.gepafin.tendermanagement.dao.AppointmentDao; +import net.gepafin.tendermanagement.entities.UserEntity; import net.gepafin.tendermanagement.model.request.CreateAppointmentRequest; import net.gepafin.tendermanagement.model.request.UploadDocToExternalSystemRequest; import net.gepafin.tendermanagement.model.response.AppointmentCreationResponse; import net.gepafin.tendermanagement.model.response.DocumentUploadResponse; import net.gepafin.tendermanagement.model.response.NdgResponse; import net.gepafin.tendermanagement.service.AppointmentService; +import net.gepafin.tendermanagement.util.Validator; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -17,6 +19,9 @@ public class AppointmentServiceImpl implements AppointmentService { @Autowired private AppointmentDao appointmentDao; + @Autowired + private Validator validator; + @Override public NdgResponse checkNdgForAppointment(HttpServletRequest request, Long applicationId) { @@ -34,4 +39,11 @@ public class AppointmentServiceImpl implements AppointmentService { return appointmentDao.uploadDocumentToExternalSystem(documentId, docToExternalSystemRequest); } + + @Override + public NdgResponse getNdgByVatNumber(HttpServletRequest request,String vatNumber) { + UserEntity userEntity = validator.validateUser(request); + NdgResponse ndgResponse= appointmentDao.getNdgByVatNumber(vatNumber,userEntity); + return ndgResponse; + } } 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 f1d8f890..f9a8b8c8 100644 --- a/src/main/java/net/gepafin/tendermanagement/service/impl/CompanyServiceImpl.java +++ b/src/main/java/net/gepafin/tendermanagement/service/impl/CompanyServiceImpl.java @@ -140,6 +140,12 @@ public class CompanyServiceImpl implements CompanyService { @Override public void updateMissingVatCheckResponses(HttpServletRequest request, LimitRequest limitRequest) { UserEntity userEntity =validator.validateUser(request); - companyDao.updateMissingVatCheckResponses(request, limitRequest); + companyDao.updateMissingVatCheckResponses(request,limitRequest); + } + + @Override + public void extractPecFromJson(HttpServletRequest request) { + UserEntity userEntity =validator.validateUser(request); + companyDao.getCompanyEntity(); } } diff --git a/src/main/java/net/gepafin/tendermanagement/web/rest/api/AppointmentApi.java b/src/main/java/net/gepafin/tendermanagement/web/rest/api/AppointmentApi.java index 5507492a..64f767b9 100644 --- a/src/main/java/net/gepafin/tendermanagement/web/rest/api/AppointmentApi.java +++ b/src/main/java/net/gepafin/tendermanagement/web/rest/api/AppointmentApi.java @@ -56,4 +56,15 @@ public interface AppointmentApi { ResponseEntity> uploadDocumentToExternalSystem(HttpServletRequest request, @Parameter(description = "The document id", required = true) @PathVariable(value = "documentId", required = true) Long documentId, @RequestBody UploadDocToExternalSystemRequest docToExternalSystemRequest); + + @Operation(summary = "API to get ndg by vatNumber", 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 = "/vatNumber/{vatNumber}", produces = MediaType.APPLICATION_JSON_VALUE) + ResponseEntity> getNdgByVatNumber(HttpServletRequest request,@PathVariable(value = "vatNumber", required = true) @Parameter(description = "vatNumber",required = true)String vatNumber); + } diff --git a/src/main/java/net/gepafin/tendermanagement/web/rest/api/CompanyApi.java b/src/main/java/net/gepafin/tendermanagement/web/rest/api/CompanyApi.java index fea29d0d..a30e18be 100644 --- a/src/main/java/net/gepafin/tendermanagement/web/rest/api/CompanyApi.java +++ b/src/main/java/net/gepafin/tendermanagement/web/rest/api/CompanyApi.java @@ -169,4 +169,17 @@ public interface CompanyApi { @PreAuthorize("hasRole('ROLE_SUPER_ADMIN') ") ResponseEntity> updateMissingVatCheckResponses(HttpServletRequest request, @Parameter(description = "Limit request object ", required = true) @RequestBody LimitRequest limitRequest); + + @Operation(summary = "Api to extract pec from the company json", 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 = "/pec", produces = { "application/json" }) + @PreAuthorize("hasRole('ROLE_SUPER_ADMIN') ") + ResponseEntity> extractPecFromJson(HttpServletRequest request); + + } diff --git a/src/main/java/net/gepafin/tendermanagement/web/rest/api/impl/AppointmentController.java b/src/main/java/net/gepafin/tendermanagement/web/rest/api/impl/AppointmentController.java index 1708b8cd..7212355a 100644 --- a/src/main/java/net/gepafin/tendermanagement/web/rest/api/impl/AppointmentController.java +++ b/src/main/java/net/gepafin/tendermanagement/web/rest/api/impl/AppointmentController.java @@ -17,6 +17,7 @@ import net.gepafin.tendermanagement.service.AppointmentService; import net.gepafin.tendermanagement.util.LoggingUtil; import net.gepafin.tendermanagement.web.rest.api.AppointmentApi; import net.gepafin.tendermanagement.web.rest.api.errors.Status; +import org.opensaml.xmlsec.signature.G; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -81,4 +82,19 @@ public class AppointmentController implements AppointmentApi { .body(new Response<>(documentUploadResponse, Status.SUCCESS, Translator.toLocale(message))); } + + @Override + public ResponseEntity> getNdgByVatNumber(HttpServletRequest request,String vatNumber) { + loggingUtil.logUserAction( + UserActionRequest.builder().request(request).actionType(UserActionLogsEnum.UPLOAD).actionContext(UserActionContextEnum.GET_NDG_BY_VAT_NUMBER).build()); + + NdgResponse ndgResponse= appointmentService.getNdgByVatNumber(request,vatNumber); + if(ndgResponse==null){ + return ResponseEntity.status(HttpStatus.CREATED) + .body(new Response<>(ndgResponse, Status.NOT_FOUND, Translator.toLocale(GepafinConstant.NDG_NOT_FOUND))); + } + + return ResponseEntity.status(HttpStatus.CREATED) + .body(new Response<>(ndgResponse, Status.SUCCESS, Translator.toLocale(GepafinConstant.NDG_FETCH_SUCCESSFULLY))); + } } diff --git a/src/main/java/net/gepafin/tendermanagement/web/rest/api/impl/CompanyApiController.java b/src/main/java/net/gepafin/tendermanagement/web/rest/api/impl/CompanyApiController.java index 64dee16b..08d34ccf 100644 --- a/src/main/java/net/gepafin/tendermanagement/web/rest/api/impl/CompanyApiController.java +++ b/src/main/java/net/gepafin/tendermanagement/web/rest/api/impl/CompanyApiController.java @@ -201,4 +201,17 @@ public class CompanyApiController implements CompanyApi{ return ResponseEntity.status(HttpStatus.OK) .body(new Response<>(null, Status.SUCCESS, Translator.toLocale(GepafinConstant.COMPANY_UPDATED_SUCCESS_MSG))); } + + @Override + public ResponseEntity> extractPecFromJson(HttpServletRequest request) { + + log.info("Api to set pec from json field"); + + companyService.extractPecFromJson(request); + + loggingUtil.logUserAction(UserActionRequest.builder().request(request).actionType(UserActionLogsEnum.UPDATE).actionContext(UserActionContextEnum.EXTRACT_PEC_FROM_COMPANY).build()); + + return ResponseEntity.status(HttpStatus.OK) + .body(new Response<>(null, Status.SUCCESS, Translator.toLocale(GepafinConstant.COMPANY_UPDATED_SUCCESS_MSG))); + } } 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 f69ef624..36f52dee 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 @@ -2955,4 +2955,24 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/message_en.properties b/src/main/resources/message_en.properties index e69af662..8d9aae03 100644 --- a/src/main/resources/message_en.properties +++ b/src/main/resources/message_en.properties @@ -408,3 +408,5 @@ resend.email.sent.failed.msg = Failed to resend the email. application.readmit.success=Application has been readmitted successfully. no.email.log.msg = No failed emails found for given userActionId. user.action.id.not.found = User Action id not found. +ndg.not.found=NDG not found. +email.pec.cannot.null=Email pec is required. diff --git a/src/main/resources/message_it.properties b/src/main/resources/message_it.properties index ef23cfac..5a94ede6 100644 --- a/src/main/resources/message_it.properties +++ b/src/main/resources/message_it.properties @@ -399,3 +399,5 @@ resend.email.sent.failed.msg = Impossibile inviare nuovamente l'e-mail. application.readmit.success=L'applicazione è stata riammessa con successo. no.email.log.msg = Nessuna email trovata per userActionId specificato. user.action.id.not.found = ID azione utente non trovato. +ndg.not.found=NDG non trovato. +email.pec.cannot.null=L'indirizzo email pec è obbligatorio. From 85df94c98222ec490b175352f5b974ab361cf78b Mon Sep 17 00:00:00 2001 From: rajesh Date: Thu, 19 Jun 2025 19:45:59 +0530 Subject: [PATCH 23/35] Done changes with respect to pec --- .../tendermanagement/dao/ApplicationDao.java | 33 +++++++++----- .../dao/EmailNotificationDao.java | 44 ++++++++++++++----- .../tendermanagement/dao/ProtocolDao.java | 8 ++++ .../application-production.properties | 2 + src/main/resources/application.properties | 1 + 5 files changed, 66 insertions(+), 22 deletions(-) diff --git a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java index 02fb01d2..198a4ede 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java @@ -1222,26 +1222,39 @@ public class ApplicationDao { String email = userEntity.getEmail(); if (userEntity.getBeneficiary() != null) { emailLogRequest.setRecipientType(RecipientTypeEnum.BENEFICIARY); - email = userEntity.getBeneficiary().getEmail(); - emailLogRequest.setRecipientId(userEntity.getBeneficiary().getId()); + + if(Boolean.TRUE.equals(hub.getUniqueUuid().equals(defaultHubUuid))){ + email=applicationEntity.getPecEmail(); + }else { + email = userEntity.getBeneficiary().getEmail(); + } + emailLogRequest.setRecipientId(userEntity.getBeneficiary().getId()); } emailNotificationDao.sendMail(hub.getId(), subject, body, List.of(email),emailLogRequest); List recipientEmails = new ArrayList<>(); // recipientEmails.add(email); String companyEmail = userWithCompany.getEmail(); String contactEmail = userWithCompany.getContactEmail(); + if(Boolean.TRUE.equals(hub.getUniqueUuid().equals(defaultHubUuid))){ + if (company.getPec()!=null) { + recipientEmails.add(company.getPec()); + }else { + recipientEmails.add(userWithCompany.getPec()); + } + } + else { + if (companyEmail != null && !companyEmail.isEmpty()) { + recipientEmails.add(companyEmail); + } - if (companyEmail != null && !companyEmail.isEmpty()) { - recipientEmails.add(companyEmail); - } - - if (contactEmail != null && !contactEmail.isEmpty() && !contactEmail.equals(companyEmail)) { - recipientEmails.add(contactEmail); - } + if (contactEmail != null && !contactEmail.isEmpty() && !contactEmail.equals(companyEmail)) { + recipientEmails.add(contactEmail); + } + } if(Boolean.FALSE.equals(recipientEmails.isEmpty())){ emailLogRequest.setRecipientId(applicationEntity.getCompanyId()); emailLogRequest.setRecipientType(RecipientTypeEnum.COMPANY); - emailLogRequest.setRecipientEmails(companyEmail); + emailLogRequest.setRecipientEmails(String.valueOf(recipientEmails)); } emailNotificationDao.sendMail(hub.getId(), subject, body, recipientEmails,emailLogRequest); } diff --git a/src/main/java/net/gepafin/tendermanagement/dao/EmailNotificationDao.java b/src/main/java/net/gepafin/tendermanagement/dao/EmailNotificationDao.java index e263a9f9..59d6193f 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/EmailNotificationDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/EmailNotificationDao.java @@ -67,6 +67,9 @@ public class EmailNotificationDao { @Autowired private ApplicationEvaluationRepository applicationEvaluationRepository; + @Value("${default.hub.uuid}") + private String defaultHubUuid; + @Value("${rinaldo_email}") private String rinaldoEmail; @@ -109,20 +112,37 @@ public class EmailNotificationDao { UserWithCompanyEntity userWithCompany=companyService.getUserWithCompany(userEntity.getId(),company.getId()); String companyEmail = userWithCompany.getEmail(); String contactEmail = userWithCompany.getContactEmail(); - - if (companyEmail != null && !companyEmail.isEmpty()) { - EmailLogRequest emailLogRequest = emailLogDao.createEmailLogRequest(systemEmailTemplateResponse.getEmailScenario(), RecipientTypeEnum.COMPANY,company.getId() , - companyEmail, userEntity.getId(), applicationEntity.getId(), amendmentId, applicationEntity.getCall().getId()); - sendMail(applicationEntity.getHubId(), subject, body, List.of(companyEmail), emailLogRequest); - } - if (contactEmail != null && !contactEmail.isEmpty() && !contactEmail.equals(companyEmail)) { - EmailLogRequest emailLogRequest = emailLogDao.createEmailLogRequest(systemEmailTemplateResponse.getEmailScenario(), RecipientTypeEnum.COMPANY,company.getId(), - contactEmail, userEntity.getId(), applicationEntity.getId(), amendmentId, applicationEntity.getCall().getId()); - sendMail(applicationEntity.getHubId(), subject, body, List.of(contactEmail), emailLogRequest); - } + if (Boolean.TRUE.equals(userEntity.getHub().getUniqueUuid().equals(defaultHubUuid))){ + List recipientEmails=new ArrayList<>(); + if (company.getPec()!=null) { + recipientEmails.add(company.getPec()); + }else { + recipientEmails.add(userWithCompany.getPec()); + } + EmailLogRequest emailLogRequest = emailLogDao.createEmailLogRequest(systemEmailTemplateResponse.getEmailScenario(), RecipientTypeEnum.COMPANY,company.getId() , + String.valueOf(recipientEmails), userEntity.getId(), applicationEntity.getId(), amendmentId, applicationEntity.getCall().getId()); + sendMail(applicationEntity.getHubId(), subject, body, recipientEmails, emailLogRequest); + } + else { + if (companyEmail != null && !companyEmail.isEmpty()) { + EmailLogRequest emailLogRequest = emailLogDao.createEmailLogRequest(systemEmailTemplateResponse.getEmailScenario(), RecipientTypeEnum.COMPANY, company.getId(), + companyEmail, userEntity.getId(), applicationEntity.getId(), amendmentId, applicationEntity.getCall().getId()); + sendMail(applicationEntity.getHubId(), subject, body, List.of(companyEmail), emailLogRequest); + } + if (contactEmail != null && !contactEmail.isEmpty() && !contactEmail.equals(companyEmail)) { + EmailLogRequest emailLogRequest = emailLogDao.createEmailLogRequest(systemEmailTemplateResponse.getEmailScenario(), RecipientTypeEnum.COMPANY, company.getId(), + contactEmail, userEntity.getId(), applicationEntity.getId(), amendmentId, applicationEntity.getCall().getId()); + sendMail(applicationEntity.getHubId(), subject, body, List.of(contactEmail), emailLogRequest); + } + } if (userEntity.getBeneficiary().getEmail() != null) { - String beneficiaryEmail = userEntity.getBeneficiary().getEmail(); + String beneficiaryEmail = null; + if (Boolean.TRUE.equals(userEntity.getHub().getUniqueUuid().equals(defaultHubUuid))){ + beneficiaryEmail=applicationEntity.getPecEmail(); + }else { + beneficiaryEmail=userEntity.getBeneficiary().getEmail(); + } EmailLogRequest emailLogRequest = emailLogDao.createEmailLogRequest(systemEmailTemplateResponse.getEmailScenario(), RecipientTypeEnum.BENEFICIARY,userEntity.getBeneficiary().getId() , beneficiaryEmail, userEntity.getId(), applicationEntity.getId(), amendmentId, applicationEntity.getCall().getId()); sendMail(applicationEntity.getHubId(), subject, body, List.of(beneficiaryEmail), emailLogRequest); diff --git a/src/main/java/net/gepafin/tendermanagement/dao/ProtocolDao.java b/src/main/java/net/gepafin/tendermanagement/dao/ProtocolDao.java index 61895195..d198c599 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/ProtocolDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/ProtocolDao.java @@ -89,6 +89,9 @@ public class ProtocolDao { @Value("${tipoCorrispondente}") private String tipoCorrispondente; + @Value("${isSviluppumbriaProtocolEnabled}") + private String isSviluppUmbriaProtocolEnabled; + @Autowired private ApplicationSignedDocumentRepository applicationSignedDocumentRepository; @@ -156,6 +159,11 @@ public class ProtocolDao { } public ProtocolEntity createExternalProtocol(ApplicationEntity application, CompanyEntity company, ProtocolEntity protocol) { + + if (Boolean.FALSE.equals(Boolean.parseBoolean(isSviluppUmbriaProtocolEnabled))) { + return protocol; + } + log.info("Starting createExternalProtocol for application ID: {}", application.getId()); log.debug("Successfully retrieved bearer token"); diff --git a/src/main/resources/application-production.properties b/src/main/resources/application-production.properties index c4ad1fe9..ef5f73f2 100644 --- a/src/main/resources/application-production.properties +++ b/src/main/resources/application-production.properties @@ -37,3 +37,5 @@ spring.rabbitmq.port=61613 spring.rabbitmq.username=guest spring.rabbitmq.password=guest spring.rabbitmq.virtual-host=/ + +isSviluppumbriaProtocolEnabled = false \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 2e40b904..4cb55816 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -91,3 +91,4 @@ codiceUo=101_10 competente=true tipoCorrispondente=Amministrazione sviluppumbriaUuid=t7jh5wfg9QXylNaTZkPoE +isSviluppumbriaProtocolEnabled = true From c7c21cf90d37d243bcf1bf8fed46fd42c7b0b3b2 Mon Sep 17 00:00:00 2001 From: rajesh Date: Fri, 27 Jun 2025 20:04:39 +0530 Subject: [PATCH 24/35] Done ticket GEPAFINBE-231 --- .../tendermanagement/dao/ApplicationDao.java | 83 ++++++++++++++++++- .../dao/ApplicationEvaluationDao.java | 13 ++- .../dao/EmailNotificationDao.java | 31 ++++++- .../dao/SystemEmailTemplatesDao.java | 4 +- .../entities/SystemEmailTemplatesEntity.java | 3 +- .../enums/ApplicationStatusForEvaluation.java | 3 +- .../enums/ApplicationStatusTypeEnum.java | 3 +- .../enums/EmailScenarioTypeEnum.java | 3 +- .../enums/UserActionContextEnum.java | 2 + .../repositories/ApplicationRepository.java | 3 + .../EvaluationCriteriaRepository.java | 2 + .../service/ApplicationService.java | 5 +- .../service/impl/ApplicationServiceImpl.java | 6 ++ .../web/rest/api/ApplicationApi.java | 15 ++++ .../api/impl/ApplicationApiController.java | 21 +++++ .../db/changelog/db.changelog-1.0.0.xml | 4 + ...template_technical_evaluation_rejected.sql | 31 +++++++ 17 files changed, 217 insertions(+), 15 deletions(-) create mode 100644 src/main/resources/db/dump/insert_system_email_template_technical_evaluation_rejected.sql diff --git a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java index 198a4ede..a90f9c4a 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java @@ -56,13 +56,11 @@ import org.springframework.web.multipart.MultipartFile; import jakarta.servlet.http.HttpServletRequest; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStreamWriter; +import java.io.*; import java.lang.reflect.Method; import java.math.BigDecimal; +import java.nio.charset.StandardCharsets; import java.sql.Timestamp; import java.text.MessageFormat; import java.text.SimpleDateFormat; @@ -217,6 +215,9 @@ public class ApplicationDao { @Autowired private ApplicationEvaluationDao applicationEvaluationDao; + @Autowired + private EvaluationCriteriaRepository evaluationCriteriaRepository; + public final Random random = new Random(); public ApplicationResponseBean createApplication(HttpServletRequest request, ApplicationRequestBean applicationRequestBean, Long formId, Long applicationId) { @@ -2419,4 +2420,78 @@ public class ApplicationDao { emailNotificationDao.sendMail(hub.getId(), subject, body, List.of(GepafinConstant.RINALDO_EMAIL),emailLogRequest); } + public byte[] downloadRankingCsv(Long callId) { + CallEntity callEntity = callService.validateCall(callId); + + BigDecimal scoreList = BigDecimal.ZERO; + List evaluationCriteriaEntities = + evaluationCriteriaRepository.findByCallIdAndIsDeletedFalse(callId); + List headers = Arrays.asList( + "ApplicationID", + "VatNumber", + "Company Name", + "Protocol", + "Requested Amount", + "Status", + "Total Score", + "Individual Scores" + ); + + + for (EvaluationCriteriaEntity evaluationCriteria : evaluationCriteriaEntities) { + scoreList = scoreList.add(evaluationCriteria.getScore()); + } + + List applications = + applicationRepository.findByCallIdAndIsDeletedFalseAndStatusIn(callId,List.of(ApplicationStatusForEvaluation.APPROVED.getValue(),ApplicationStatusForEvaluation.ADMISSIBLE.getValue(),ApplicationStatusForEvaluation.TECHNICAL_EVALUATION.getValue())); + + // Collect all rows with totalScore for sorting + List> rows = new ArrayList<>(); + + for (ApplicationEntity app : applications) { + CompanyEntity company = companyService.validateCompany(app.getCompanyId()); + String name = company.getCompanyName(); + String vat = company.getVatNumber(); + Long applicationId = app.getId(); + ProtocolEntity protocolEntity = app.getProtocol(); + Long protocol = (protocolEntity != null) ? protocolEntity.getProtocolNumber() : 0L; + BigDecimal requestedAmount = app.getAmountRequested(); + String status = app.getStatus(); + + ApplicationEvaluationEntity applicationEvaluationEntity = + applicationEvaluationRepository.findByApplicationId(app.getId()); + + BigDecimal totalScore = applicationEvaluationDao.calculateTotalScore( + applicationEvaluationEntity.getCriteria() + ); + + rows.add(Arrays.asList( + applicationId, + vat, + name, + protocol, + requestedAmount, + status, + scoreList, + totalScore + )); + } + + // 5. Write the CSV using Commons CSV, with headers: + ByteArrayOutputStream out = new ByteArrayOutputStream(); + try (OutputStreamWriter writer = new OutputStreamWriter(out, StandardCharsets.UTF_8); + CSVPrinter csvPrinter = new CSVPrinter(writer, + CSVFormat.DEFAULT.withHeader(headers.toArray(new String[0])))) { + + for (List row : rows) { + csvPrinter.printRecord(row); + } + csvPrinter.flush(); + } catch (IOException e) { + throw new RuntimeException("Error while generating CSV", e); + } + + return out.toByteArray(); + } + } diff --git a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationEvaluationDao.java b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationEvaluationDao.java index 3cb7237a..9fa68a28 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationEvaluationDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationEvaluationDao.java @@ -1910,7 +1910,8 @@ public class ApplicationEvaluationDao { Optional existingEntityOptional = applicationEvaluationRepository.findByAssignedApplicationsEntity_IdAndIsDeletedFalse( assignedApplicationsEntity.getId()); ApplicationEvaluationEntity entity; - + UserEntity user=userService.validateUser(application.getUserId()); + HubEntity hub=user.getHub(); EmailSendResponse emailSendResponse = new EmailSendResponse(); if (existingEntityOptional.isPresent()) { ApplicationEvaluationEntity existingEntity = existingEntityOptional.get(); @@ -1940,7 +1941,13 @@ public class ApplicationEvaluationDao { application.setStatus(newStatus.getValue()); log.info("Application status updated to {} for applicationId: {}", newStatus, application.getId()); } - application = applicationRepository.save(application); + + if(newStatus.equals(ApplicationStatusForEvaluation.TECHNICAL_EVALUATION_REJECTED) && Boolean.TRUE.equals(application.getStatus().equals(ApplicationStatusTypeEnum.ADMISSIBLE.getValue()))) { + application.setStatus(newStatus.getValue()); + log.info("Application status updated to {} for applicationId: {}", newStatus, application.getId()); + emailNotificationDao.sendMailForApplicationTechnicalEvaluationRejected(application,hub,existingEntity); + } + application = applicationRepository.save(application); /** This code is responsible for adding a version history log for the "Update Application" operation. **/ loggingUtil.addVersionHistory(VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldApplicationEntity).newData(application).build()); @@ -2573,7 +2580,7 @@ public class ApplicationEvaluationDao { } } - private BigDecimal calculateTotalScore(String criteriaJson){ + public BigDecimal calculateTotalScore(String criteriaJson){ try { ObjectMapper objectMapper = new ObjectMapper(); // Convert JSON string to List of Maps diff --git a/src/main/java/net/gepafin/tendermanagement/dao/EmailNotificationDao.java b/src/main/java/net/gepafin/tendermanagement/dao/EmailNotificationDao.java index 59d6193f..462958cb 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/EmailNotificationDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/EmailNotificationDao.java @@ -73,6 +73,9 @@ public class EmailNotificationDao { @Value("${rinaldo_email}") private String rinaldoEmail; + @Autowired + private SystemEmailTemplatesDao systemEmailTemplatesDao; + private void sendEmail(ApplicationEntity applicationEntity, SystemEmailTemplatesEntity.SystemEmailTemplatesEntityTypeEnum templateType, Map bodyPlaceholders, List additionalRecipients, Long amendmentId) { @@ -355,4 +358,30 @@ public class EmailNotificationDao { throw new IllegalArgumentException("Failed to parse email configuration JSON", e); } } -} \ No newline at end of file + public void sendMailForApplicationTechnicalEvaluationRejected(ApplicationEntity applicationEntity,HubEntity hub,ApplicationEvaluationEntity applicationEvaluationEntity) { + + Map bodyPlaceholders = prepareEmailPlaceholdersForTechnicalEvaluationRejected(applicationEntity,hub,applicationEvaluationEntity); + sendEmail(applicationEntity, SystemEmailTemplatesEntity.SystemEmailTemplatesEntityTypeEnum.INADMISSIBILITY_NOTIFICATION_DUE_TO_TECHNICAL_EVALUATION_FAILURE, bodyPlaceholders, null, + null); + } + public Map prepareEmailPlaceholdersForTechnicalEvaluationRejected(ApplicationEntity applicationEntity,HubEntity hub,ApplicationEvaluationEntity applicationEvaluationEntity) { + Map bodyPlaceholders = new HashMap<>(); + bodyPlaceholders.put("{{call_name}}", applicationEntity.getCall().getName()); + String protocolNumber = applicationEntity.getProtocol().getExternalProtocolNumber(); + if (protocolNumber == null) { + protocolNumber = String.valueOf(applicationEntity.getProtocol().getProtocolNumber()); + } + bodyPlaceholders.put("{{protocol_number}}", protocolNumber); + String protocolDate = DateTimeUtil.formatLocalDateTime(applicationEntity.getProtocol().getCreatedDate(), GepafinConstant.DD_MM_YYYY); + if (applicationEntity.getProtocol().getExternalProtocolDate() != null) { + protocolDate = DateTimeUtil.formatLocalDateTime(applicationEntity.getProtocol().getExternalProtocolDate(), GepafinConstant.DD_MM_YYYY); + } + bodyPlaceholders.put("{{protocol_date}}", protocolDate); + bodyPlaceholders.put("{{protocol_time}}", DateTimeUtil.parseLocalTimeToString(applicationEntity.getProtocol().getTime(), GepafinConstant.HH_MM_SS)); + bodyPlaceholders.put("{{email_signature}}", hub.getEmailSignature()); + bodyPlaceholders.put("{{platform_link}}", hub.getDomainName()); + bodyPlaceholders.put("{{form_text}}", applicationEvaluationEntity.getMotivation()); + + return bodyPlaceholders; + } + } \ No newline at end of file diff --git a/src/main/java/net/gepafin/tendermanagement/dao/SystemEmailTemplatesDao.java b/src/main/java/net/gepafin/tendermanagement/dao/SystemEmailTemplatesDao.java index 8808abfd..2362b77e 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/SystemEmailTemplatesDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/SystemEmailTemplatesDao.java @@ -116,7 +116,7 @@ public class SystemEmailTemplatesDao { return htmlContent; } - private String replaceEmailSignature(HubEntity hub, String htmlContent, Map languageMap) { + public String replaceEmailSignature(HubEntity hub, String htmlContent, Map languageMap) { String emailSignature = defaultEmailSignature; if(hub != null && Boolean.FALSE.equals(StringUtils.isEmpty(hub.getEmailSignature()))){ emailSignature = hub.getEmailSignature(); @@ -124,7 +124,7 @@ public class SystemEmailTemplatesDao { return htmlContent.replace("{{email_signature}}", emailSignature); } - private String replacePlatformLinkPlaceholder(HubEntity hub, String htmlContent, Map languageMap) { + public String replacePlatformLinkPlaceholder(HubEntity hub, String htmlContent, Map languageMap) { String platformLink = feBaseUrl; if(hub != null && Boolean.FALSE.equals(StringUtils.isEmpty(hub.getDomainName()))){ platformLink = hub.getDomainName(); diff --git a/src/main/java/net/gepafin/tendermanagement/entities/SystemEmailTemplatesEntity.java b/src/main/java/net/gepafin/tendermanagement/entities/SystemEmailTemplatesEntity.java index a4c40863..1ac63bae 100644 --- a/src/main/java/net/gepafin/tendermanagement/entities/SystemEmailTemplatesEntity.java +++ b/src/main/java/net/gepafin/tendermanagement/entities/SystemEmailTemplatesEntity.java @@ -55,7 +55,8 @@ public class SystemEmailTemplatesEntity extends BaseEntity { USER_ONBOARDING_BANDI("USER_ONBOARDING_BANDI"), PASSWORD_RESET("PASSWORD_RESET"), INADMISSIBILITY_TEMPLATE("INADMISSIBILITY_NOTIFICATION"), - APPLICATION_SUBMISSION_FAILURE_NOTIFICATION("APPLICATION_SUBMISSION_FAILURE_NOTIFICATION"); + APPLICATION_SUBMISSION_FAILURE_NOTIFICATION("APPLICATION_SUBMISSION_FAILURE_NOTIFICATION"), + INADMISSIBILITY_NOTIFICATION_DUE_TO_TECHNICAL_EVALUATION_FAILURE("INADMISSIBILITY_NOTIFICATION_DUE_TO_TECHNICAL_EVALUATION_FAILURE"); private String value; SystemEmailTemplatesEntityTypeEnum(String value) { diff --git a/src/main/java/net/gepafin/tendermanagement/enums/ApplicationStatusForEvaluation.java b/src/main/java/net/gepafin/tendermanagement/enums/ApplicationStatusForEvaluation.java index f0f6acb8..d9c3c45e 100644 --- a/src/main/java/net/gepafin/tendermanagement/enums/ApplicationStatusForEvaluation.java +++ b/src/main/java/net/gepafin/tendermanagement/enums/ApplicationStatusForEvaluation.java @@ -6,7 +6,8 @@ public enum ApplicationStatusForEvaluation { APPROVED("APPROVED"), REJECTED("REJECTED"), ADMISSIBLE("ADMISSIBLE"), - TECHNICAL_EVALUATION("TECHNICAL_EVALUATION"); + TECHNICAL_EVALUATION("TECHNICAL_EVALUATION"), + TECHNICAL_EVALUATION_REJECTED("TECHNICAL_EVALUATION_REJECTED"); private String value; diff --git a/src/main/java/net/gepafin/tendermanagement/enums/ApplicationStatusTypeEnum.java b/src/main/java/net/gepafin/tendermanagement/enums/ApplicationStatusTypeEnum.java index 041ec85a..3afbc3ad 100644 --- a/src/main/java/net/gepafin/tendermanagement/enums/ApplicationStatusTypeEnum.java +++ b/src/main/java/net/gepafin/tendermanagement/enums/ApplicationStatusTypeEnum.java @@ -16,7 +16,8 @@ public enum ApplicationStatusTypeEnum { APPOINTMENT("APPOINTMENT"), NDG("NDG"), ADMISSIBLE("ADMISSIBLE"), - TECHNICAL_EVALUATION("TECHNICAL_EVALUATION"); + TECHNICAL_EVALUATION("TECHNICAL_EVALUATION"), + TECHNICAL_EVALUATION_REJECTED("TECHNICAL_EVALUATION_REJECTED"); private String value; diff --git a/src/main/java/net/gepafin/tendermanagement/enums/EmailScenarioTypeEnum.java b/src/main/java/net/gepafin/tendermanagement/enums/EmailScenarioTypeEnum.java index 908e554d..ab25ad25 100644 --- a/src/main/java/net/gepafin/tendermanagement/enums/EmailScenarioTypeEnum.java +++ b/src/main/java/net/gepafin/tendermanagement/enums/EmailScenarioTypeEnum.java @@ -12,7 +12,8 @@ public enum EmailScenarioTypeEnum { USER_CREATION("USER_CREATION"), PASSWORD_RESET_REQUEST("PASSWORD_RESET_REQUEST"), APPLICATION_REJECTED("APPLICATION_REJECTED"), - APPLICATION_SUBMISSION_FAILURE("APPLICATION_SUBMISSION_FAILURE"); + APPLICATION_SUBMISSION_FAILURE("APPLICATION_SUBMISSION_FAILURE"), + APPLICATION_TECHNICAL_EVALUATION_REJECTED("APPLICATION_TECHNICAL_EVALUATION_REJECTED"); private final String value; diff --git a/src/main/java/net/gepafin/tendermanagement/enums/UserActionContextEnum.java b/src/main/java/net/gepafin/tendermanagement/enums/UserActionContextEnum.java index 1de6b333..4200e361 100644 --- a/src/main/java/net/gepafin/tendermanagement/enums/UserActionContextEnum.java +++ b/src/main/java/net/gepafin/tendermanagement/enums/UserActionContextEnum.java @@ -47,6 +47,8 @@ public enum UserActionContextEnum { GET_NEXT_PREVIOUS_FORM("GET_NEXT_PREVIOUS_FORM"), DOWNLOAD_APPLICATION_DOC_ZIP("DOWNLOAD_APPLICATION_DOC_ZIP"), READMIT_APPLICATION("READMIT_APPLICATION"), + DOWNLOAD_CSV_BY_CALL_ID("DOWNLOAD_CSV_BY_CALL_ID"), + DOWNLOAD_CSV_AS_PER_RANKING("DOWNLOAD_CSV_AS_PER_RANKING"), /** FAQ action context **/ CREATE_FAQ("CREATE_FAQ"), diff --git a/src/main/java/net/gepafin/tendermanagement/repositories/ApplicationRepository.java b/src/main/java/net/gepafin/tendermanagement/repositories/ApplicationRepository.java index c760689a..37d2f270 100644 --- a/src/main/java/net/gepafin/tendermanagement/repositories/ApplicationRepository.java +++ b/src/main/java/net/gepafin/tendermanagement/repositories/ApplicationRepository.java @@ -181,4 +181,7 @@ public interface ApplicationRepository extends JpaRepository findByCallIdAndIsDeletedFalseAndStatusIn(Long callId,List status); + + } diff --git a/src/main/java/net/gepafin/tendermanagement/repositories/EvaluationCriteriaRepository.java b/src/main/java/net/gepafin/tendermanagement/repositories/EvaluationCriteriaRepository.java index 1a7f7ccd..8c939956 100644 --- a/src/main/java/net/gepafin/tendermanagement/repositories/EvaluationCriteriaRepository.java +++ b/src/main/java/net/gepafin/tendermanagement/repositories/EvaluationCriteriaRepository.java @@ -18,4 +18,6 @@ public interface EvaluationCriteriaRepository extends JpaRepository findByCallIdAndLookupDataTypeAndIsDeletedFalse(Long callId, String type); // List findByCallId(Long callId); + List findByCallIdAndIsDeletedFalse(Long callId); + } diff --git a/src/main/java/net/gepafin/tendermanagement/service/ApplicationService.java b/src/main/java/net/gepafin/tendermanagement/service/ApplicationService.java index c7af4f88..9cc2ea22 100644 --- a/src/main/java/net/gepafin/tendermanagement/service/ApplicationService.java +++ b/src/main/java/net/gepafin/tendermanagement/service/ApplicationService.java @@ -52,4 +52,7 @@ public interface ApplicationService { public ApplicationResponse readmitApplication(HttpServletRequest request, Long applicationId); - } + public byte[] downloadRankingCsv(HttpServletRequest request, Long callId); + + +} 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 357661d1..aca38d23 100644 --- a/src/main/java/net/gepafin/tendermanagement/service/impl/ApplicationServiceImpl.java +++ b/src/main/java/net/gepafin/tendermanagement/service/impl/ApplicationServiceImpl.java @@ -176,4 +176,10 @@ public class ApplicationServiceImpl implements ApplicationService { UserEntity userEntity = validator.validateUser(request); return applicationDao.readmitApplication(request, applicationId); } + + @Override + public byte[] downloadRankingCsv(HttpServletRequest request, Long callId) { + UserEntity userEntity = validator.validateUser(request); + return applicationDao.downloadRankingCsv(callId); + } } 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 a18e6852..b64b3951 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 @@ -252,6 +252,21 @@ public interface ApplicationApi { ResponseEntity> readmitApplication(HttpServletRequest request, @Parameter(description = "The application id", required = true) @PathVariable("applicationId") Long applicationId); + @Operation(summary = "Api to download application data as a CSV file as per ranking", + 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 = "/call/{callId}/ranking-csv") + @PreAuthorize("hasRole('ROLE_SUPER_ADMIN') || hasRole('ROLE_INSTRUCTOR_MANAGER')") + public ResponseEntity downloadRankingCsv( + HttpServletRequest request, @Parameter(description = "The call id", required = true) @PathVariable(value = "callId", required = true) Long callId); + } 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 840711ae..76946420 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 @@ -27,6 +27,8 @@ import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import org.slf4j.Logger; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; import java.util.List; @@ -236,6 +238,10 @@ public class ApplicationApiController implements ApplicationApi { } @Override public ResponseEntity exportCsv(HttpServletRequest request, Long callId) { + + loggingUtil.logUserAction( + UserActionRequest.builder().request(request).actionType(UserActionLogsEnum.DOWNLOAD).actionContext(UserActionContextEnum.DOWNLOAD_CSV_BY_CALL_ID).build()); + byte[] csvBytes =applicationService.exportCsv(request,callId); return ResponseEntity.ok() .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=applications.csv") @@ -254,4 +260,19 @@ public class ApplicationApiController implements ApplicationApi { return ResponseEntity.status(HttpStatus.OK) .body(new Response<>(applicationResponse, Status.SUCCESS, Translator.toLocale(GepafinConstant.READMIT_APPLICATION_SUCCESS))); } + + @Override + public ResponseEntity downloadRankingCsv(HttpServletRequest request, Long callId) { + loggingUtil.logUserAction( + UserActionRequest.builder().request(request).actionType(UserActionLogsEnum.DOWNLOAD).actionContext(UserActionContextEnum.DOWNLOAD_CSV_AS_PER_RANKING).build()); + + byte[] csvBytes =applicationService.downloadRankingCsv(request,callId); + String dateString = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd")); + String fileName = "call_" + callId + "_" + dateString + ".csv"; + + return ResponseEntity.ok() + .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + fileName) + .contentType(MediaType.APPLICATION_OCTET_STREAM) + .body(csvBytes); + } } 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 147dd7f7..c16d810c 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 @@ -2980,4 +2980,8 @@ + + + diff --git a/src/main/resources/db/dump/insert_system_email_template_technical_evaluation_rejected.sql b/src/main/resources/db/dump/insert_system_email_template_technical_evaluation_rejected.sql new file mode 100644 index 00000000..7b76a13d --- /dev/null +++ b/src/main/resources/db/dump/insert_system_email_template_technical_evaluation_rejected.sql @@ -0,0 +1,31 @@ +INSERT INTO gepafin_schema.system_email_template +(template_name, "type", html_content, subject, "json", "system", is_deleted, created_date, updated_date, email_scenario) +VALUES +( + 'Application Technical Evaluation Rejected Template', + 'INADMISSIBILITY_NOTIFICATION_DUE_TO_TECHNICAL_EVALUATION_FAILURE', + ' + +
+

Buongiorno,

+

Si comunica che, in riferimento alla domanda a valere sul bando “{{call_name}}†di cui al + Protocollo n. {{protocol_number}} del {{protocol_date}} alle {{protocol_time}}, + a stessa è stata sottoposta a valutazione tecnica ed economico finanziaria + con esito negativo

+

Le motivazioni sono le seguenti: {{form_text}}

+

Vi ricordiamo che i Beneficiari, in caso di mancato accoglimento della Domanda di Finanziamento agevolato, entro 10 giorni dalla data di ricevimento della presente potranno formulare ricorso al Gestore tramite + modello disponibile nello sportello online + {{platform_link}}, e sul sito internet home - ND Credit Repair , nella sezione dedicata ai Bandi e Avvisi pubblici.

+

Distinti Saluti,

+

{{email_signature}}

+
+ + ', + 'BANDO – "{{call_name}}" – Esito negativo della valutazione tecnica – {{company_name}}', + null, + true, + false, + CURRENT_TIMESTAMP, + CURRENT_TIMESTAMP, + 'APPLICATION_TECHNICAL_EVALUATION_REJECTED' +); From cbed4500cdbb841d36ca28dfb0427c21fee3a519 Mon Sep 17 00:00:00 2001 From: rajesh Date: Mon, 30 Jun 2025 16:46:56 +0530 Subject: [PATCH 25/35] Updated logic for application status --- .../tendermanagement/dao/ApplicationEvaluationDao.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationEvaluationDao.java b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationEvaluationDao.java index 9fa68a28..215a5e24 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationEvaluationDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationEvaluationDao.java @@ -1932,17 +1932,17 @@ public class ApplicationEvaluationDao { } } - if(newStatus.equals(ApplicationStatusForEvaluation.TECHNICAL_EVALUATION) && Boolean.TRUE.equals(application.getStatus().equals(ApplicationStatusTypeEnum.ADMISSIBLE.getValue()))){ + if(newStatus.equals(ApplicationStatusForEvaluation.TECHNICAL_EVALUATION)){ log.info("Processing technical evaluation for applicationId: {}", application.getId()); processTechnicalEvaluation(application.getId(), application, newStatus); } - if((newStatus.equals(ApplicationStatusForEvaluation.APPROVED) || newStatus.equals(ApplicationStatusForEvaluation.REJECTED)) && application.getStatus().equals(ApplicationStatusTypeEnum.EVALUATION.getValue())) { + if((newStatus.equals(ApplicationStatusForEvaluation.APPROVED) || newStatus.equals(ApplicationStatusForEvaluation.REJECTED))) { application.setStatus(newStatus.getValue()); log.info("Application status updated to {} for applicationId: {}", newStatus, application.getId()); } - if(newStatus.equals(ApplicationStatusForEvaluation.TECHNICAL_EVALUATION_REJECTED) && Boolean.TRUE.equals(application.getStatus().equals(ApplicationStatusTypeEnum.ADMISSIBLE.getValue()))) { + if(newStatus.equals(ApplicationStatusForEvaluation.TECHNICAL_EVALUATION_REJECTED)) { application.setStatus(newStatus.getValue()); log.info("Application status updated to {} for applicationId: {}", newStatus, application.getId()); emailNotificationDao.sendMailForApplicationTechnicalEvaluationRejected(application,hub,existingEntity); From b7456a87eeed9455831a32ec761e23fc194ecad3 Mon Sep 17 00:00:00 2001 From: rajesh Date: Tue, 1 Jul 2025 11:41:21 +0530 Subject: [PATCH 26/35] Updated message in evaluation API --- .../constants/GepafinConstant.java | 2 +- .../dao/ApplicationEvaluationDao.java | 16 +++++++++++----- .../impl/ApplicationEvaluationApiController.java | 2 +- src/main/resources/message_en.properties | 1 + src/main/resources/message_it.properties | 1 + 5 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java b/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java index 3874fc83..42857d23 100644 --- a/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java +++ b/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java @@ -574,7 +574,7 @@ public class GepafinConstant { public static final String CREATE_NDG="CHECK_OR_CREATE_NDG_CODE"; public static final String NDG_NOT_FOUND="ndg.not.found"; public static final String EMAIL_PEC_REQUIRED="email.pec.cannot.null"; - + public static final String USER_REQUEST_COMPLETED="user.request.completed"; } diff --git a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationEvaluationDao.java b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationEvaluationDao.java index 215a5e24..ccc4e315 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationEvaluationDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationEvaluationDao.java @@ -1946,12 +1946,15 @@ public class ApplicationEvaluationDao { application.setStatus(newStatus.getValue()); log.info("Application status updated to {} for applicationId: {}", newStatus, application.getId()); emailNotificationDao.sendMailForApplicationTechnicalEvaluationRejected(application,hub,existingEntity); + application.setDateRejected(DateTimeUtil.DateServerToUTC(LocalDateTime.now())); + emailSendResponse = emailDao.buildEmailSendResponseFromRequest(request); + responses = List.of(emailSendResponse); + if (!Boolean.TRUE.equals(emailSendResponse.getIsEmailSend())) { + saveEmailSendResponseToEvaluation(emailSendResponse, existingEntity); + } } application = applicationRepository.save(application); - /** This code is responsible for adding a version history log for the "Update Application" operation. **/ - loggingUtil.addVersionHistory(VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldApplicationEntity).newData(application).build()); - ApplicationEvaluationEntity oldApplicationEvaluation = Utils.getClonedEntityForData(existingEntity); AssignedApplicationsEntity oldAssignedApplication = Utils.getClonedEntityForData(assignedApplicationsEntity); @@ -1961,7 +1964,7 @@ public class ApplicationEvaluationDao { throw new CustomValidationException(Status.BAD_REQUEST,Translator.toLocale(GepafinConstant.APPLICATION_CANNOT_APPROVED_OR_REJECTED)); } String statusType = application.getStatus(); - if (application.getStatus().equals(ApplicationStatusTypeEnum.APPROVED.getValue()) || application.getStatus().equals(ApplicationStatusTypeEnum.REJECTED.getValue())) { + if (application.getStatus().equals(ApplicationStatusTypeEnum.APPROVED.getValue()) || application.getStatus().equals(ApplicationStatusTypeEnum.REJECTED.getValue()) || application.getStatus().equals(ApplicationStatusTypeEnum.TECHNICAL_EVALUATION_REJECTED.getValue())) { existingEntity.setStatus(ApplicationEvaluationStatusTypeEnum.CLOSE.getValue()); existingEntity.setClosingDate(DateTimeUtil.DateServerToUTC(LocalDateTime.now())); assignedApplicationsEntity.setStatus(AssignedApplicationEnum.CLOSE.getValue()); @@ -1976,7 +1979,7 @@ public class ApplicationEvaluationDao { entity = applicationEvaluationRepository.save(existingEntity); assignedApplicationsRepository.save(assignedApplicationsEntity); - if (application.getStatus().equals(ApplicationStatusTypeEnum.APPROVED.getValue()) || application.getStatus().equals(ApplicationStatusTypeEnum.REJECTED.getValue())) { + if (application.getStatus().equals(ApplicationStatusTypeEnum.APPROVED.getValue()) || application.getStatus().equals(ApplicationStatusTypeEnum.REJECTED.getValue()) || application.getStatus().equals(ApplicationStatusTypeEnum.TECHNICAL_EVALUATION_REJECTED.getValue())) { /** This code is responsible for adding a version history log for the "Update Application Evaluation" operation. **/ loggingUtil.addVersionHistory(VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldApplicationEvaluation).newData(entity).build()); @@ -2006,6 +2009,9 @@ public class ApplicationEvaluationDao { notificationDao.sendNotificationToBeneficiary(application, NotificationTypeEnum.EVALUATION_RESULT); } + /** This code is responsible for adding a version history log for the "Update Application" operation. **/ + loggingUtil.addVersionHistory(VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldApplicationEntity).newData(application).build()); + Map placeHolders = new HashMap<>(); placeHolders.put("{{call_name}}", application.getCall().getName()); String protocolNumber=application.getProtocol().getExternalProtocolNumber(); diff --git a/src/main/java/net/gepafin/tendermanagement/web/rest/api/impl/ApplicationEvaluationApiController.java b/src/main/java/net/gepafin/tendermanagement/web/rest/api/impl/ApplicationEvaluationApiController.java index 50eadb36..2f6bbcb3 100644 --- a/src/main/java/net/gepafin/tendermanagement/web/rest/api/impl/ApplicationEvaluationApiController.java +++ b/src/main/java/net/gepafin/tendermanagement/web/rest/api/impl/ApplicationEvaluationApiController.java @@ -46,7 +46,7 @@ public class ApplicationEvaluationApiController implements ApplicationEvaluation request, evaluationRequest, assignedApplicationsId); return ResponseEntity.status(HttpStatus.CREATED) - .body(new Response<>(response, Status.SUCCESS, Translator.toLocale(GepafinConstant.EVALUATION_CREATED_SUCCESSFULLY))); + .body(new Response<>(response, Status.SUCCESS, Translator.toLocale(GepafinConstant.USER_REQUEST_COMPLETED))); } diff --git a/src/main/resources/message_en.properties b/src/main/resources/message_en.properties index 8d9aae03..a706e568 100644 --- a/src/main/resources/message_en.properties +++ b/src/main/resources/message_en.properties @@ -410,3 +410,4 @@ no.email.log.msg = No failed emails found for given userActionId. user.action.id.not.found = User Action id not found. ndg.not.found=NDG not found. email.pec.cannot.null=Email pec is required. +user.request.completed=User request completed successfully. diff --git a/src/main/resources/message_it.properties b/src/main/resources/message_it.properties index 5a94ede6..f4246425 100644 --- a/src/main/resources/message_it.properties +++ b/src/main/resources/message_it.properties @@ -401,3 +401,4 @@ no.email.log.msg = Nessuna email trovata per userActionId specificato. user.action.id.not.found = ID azione utente non trovato. ndg.not.found=NDG non trovato. email.pec.cannot.null=L'indirizzo email pec è obbligatorio. +user.request.completed=Richiesta utente completata con successo. From b12b0a46659bdc2d601d65b69317f50448a65c18 Mon Sep 17 00:00:00 2001 From: rajesh Date: Tue, 1 Jul 2025 18:15:28 +0530 Subject: [PATCH 27/35] Updated code --- .../tendermanagement/dao/AppointmentDao.java | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java b/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java index 76a7fcff..ef5779fa 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java @@ -154,6 +154,13 @@ public class AppointmentDao { ApplicationEntity oldApplication = Utils.getClonedEntityForData(application); NdgResponse ndgResponse = new NdgResponse(); + CompanyEntity company = companyService.validateCompany(application.getCompanyId()); + NdganagEntity ndganagEntity = ndganagRepository.findByVatNumber(company.getVatNumber()); + if (ndganagEntity != null && ndganagEntity.getNdg() != null) { + ndgResponse.setNdg(ndganagEntity.getNdg()); + return ndgResponse; + } + if (application.getNdgStatus() != null && application.getNdgStatus().equalsIgnoreCase(GepafinConstant.NDG_IN_PROGRESS)) { log.warn("NDG generation already in progress. applicationId: {}", applicationId); throw new CustomValidationException(Status.SUCCESS, Translator.toLocale(GepafinConstant.NDG_GENERATION_IS_IN_PROGRESS)); @@ -165,7 +172,7 @@ public class AppointmentDao { } // Update application status - log.info("Updating NDG status to IN_PROGRESS. applicationId: {}", applicationId); + log.info("Updating NDG status of applicationId: {}", applicationId); application.setNdgStatus(NdgStatusEnum.NDG_INITITATED.getValue()); applicationRepository.save(application); @@ -538,13 +545,9 @@ public class AppointmentDao { String authorizationToken = getBearerToken(hub); // Try retrieving NDG by VAT number - NdganagEntity ndganagEntity = ndganagRepository.findByVatNumber(company.getVatNumber()); AppointmentLoginResponse ndgResponse=new AppointmentLoginResponse(); - if (ndganagEntity != null || ndganagEntity.getNdg() != null) { - ndgResponse.setNdg(ndganagEntity.getNdg()); - }else { ndgResponse = retrieveNdgByVatNumber(company.getVatNumber(), authorizationToken, hub, application); - } + if (isNdgValid(ndgResponse.getNdg())) { saveNdg(application, company, ndgResponse.getNdg()); log.info("NDG successfully generated for applicationId: {}", applicationId); From b4831ce3486bc8f6b0eea7fa113a54028543464b Mon Sep 17 00:00:00 2001 From: rajesh Date: Wed, 2 Jul 2025 14:51:37 +0530 Subject: [PATCH 28/35] Update logic for csv extraction --- .../tendermanagement/dao/ApplicationDao.java | 109 +++++++++++------- .../dao/ApplicationEvaluationDao.java | 4 +- 2 files changed, 71 insertions(+), 42 deletions(-) diff --git a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java index a90f9c4a..276c5c23 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java @@ -2423,61 +2423,90 @@ public class ApplicationDao { public byte[] downloadRankingCsv(Long callId) { CallEntity callEntity = callService.validateCall(callId); - BigDecimal scoreList = BigDecimal.ZERO; - List evaluationCriteriaEntities = - evaluationCriteriaRepository.findByCallIdAndIsDeletedFalse(callId); - List headers = Arrays.asList( - "ApplicationID", - "VatNumber", - "Company Name", - "Protocol", - "Requested Amount", - "Status", - "Total Score", - "Individual Scores" - ); + List applications = + applicationRepository.findByCallIdAndIsDeletedFalseAndStatusIn( + callId, + List.of( + ApplicationStatusForEvaluation.APPROVED.getValue(), + ApplicationStatusForEvaluation.ADMISSIBLE.getValue(), + ApplicationStatusForEvaluation.TECHNICAL_EVALUATION.getValue() + )); + List dynamicLabels = new ArrayList<>(); // Maintain insertion order, allow duplicates only once + Map> appLabelScoresMap = new HashMap<>(); - for (EvaluationCriteriaEntity evaluationCriteria : evaluationCriteriaEntities) { - scoreList = scoreList.add(evaluationCriteria.getScore()); + Map applicationMap = new HashMap<>(); + Map appTotalScoreMap = new HashMap<>(); + + for (ApplicationEntity app : applications) { + Long appId = app.getId(); + applicationMap.put(appId, app); + + ApplicationEvaluationEntity evaluation = + applicationEvaluationRepository.findByApplicationId(appId); + + BigDecimal totalScore = applicationEvaluationDao.calculateTotalScore(evaluation.getCriteria()); + appTotalScoreMap.put(appId, totalScore); + + List criteriaList = + evaluation.getCriteria() != null ? + Utils.convertJsonToList(evaluation.getCriteria(), new TypeReference>() {}) : + List.of(); + + List dbCriteriaList = applicationEvaluationDao.getCriteriaResponse(appId); + + Map scoreByLabel = new HashMap<>(); + + for (CriteriaResponse criteria : criteriaList) { + Optional matchedDb = dbCriteriaList.stream() + .filter(db -> Objects.equals(db.getId(), criteria.getId())) + .findFirst(); + + String label = matchedDb.map(CriteriaResponse::getLabel).orElse(""); + + if (!dynamicLabels.contains(label)) { + dynamicLabels.add(label); + } + + scoreByLabel.put(label, criteria.getScore() != null ? criteria.getScore() : BigDecimal.ZERO); + } + + appLabelScoresMap.put(appId, scoreByLabel); } - List applications = - applicationRepository.findByCallIdAndIsDeletedFalseAndStatusIn(callId,List.of(ApplicationStatusForEvaluation.APPROVED.getValue(),ApplicationStatusForEvaluation.ADMISSIBLE.getValue(),ApplicationStatusForEvaluation.TECHNICAL_EVALUATION.getValue())); + // Build headers dynamically + List headers = new ArrayList<>(List.of( + "ApplicationID", "VatNumber", "Company Name", "Protocol", "Requested Amount", "Status", "Total Score" + )); + headers.addAll(dynamicLabels); - // Collect all rows with totalScore for sorting + // Prepare data rows List> rows = new ArrayList<>(); for (ApplicationEntity app : applications) { + Long appId = app.getId(); CompanyEntity company = companyService.validateCompany(app.getCompanyId()); - String name = company.getCompanyName(); - String vat = company.getVatNumber(); - Long applicationId = app.getId(); ProtocolEntity protocolEntity = app.getProtocol(); - Long protocol = (protocolEntity != null) ? protocolEntity.getProtocolNumber() : 0L; - BigDecimal requestedAmount = app.getAmountRequested(); - String status = app.getStatus(); - ApplicationEvaluationEntity applicationEvaluationEntity = - applicationEvaluationRepository.findByApplicationId(app.getId()); + List row = new ArrayList<>(); + row.add(appId); + row.add(company.getVatNumber()); + row.add(company.getCompanyName()); + row.add(protocolEntity != null ? protocolEntity.getProtocolNumber() : 0L); + row.add(app.getAmountRequested()); + row.add(app.getStatus()); + row.add(appTotalScoreMap.get(appId)); - BigDecimal totalScore = applicationEvaluationDao.calculateTotalScore( - applicationEvaluationEntity.getCriteria() - ); + Map scores = appLabelScoresMap.getOrDefault(appId, Collections.emptyMap()); - rows.add(Arrays.asList( - applicationId, - vat, - name, - protocol, - requestedAmount, - status, - scoreList, - totalScore - )); + for (String label : dynamicLabels) { + row.add(scores.getOrDefault(label, BigDecimal.ZERO)); + } + + rows.add(row); } - // 5. Write the CSV using Commons CSV, with headers: + // Generate CSV ByteArrayOutputStream out = new ByteArrayOutputStream(); try (OutputStreamWriter writer = new OutputStreamWriter(out, StandardCharsets.UTF_8); CSVPrinter csvPrinter = new CSVPrinter(writer, diff --git a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationEvaluationDao.java b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationEvaluationDao.java index ccc4e315..6d574394 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationEvaluationDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationEvaluationDao.java @@ -1549,13 +1549,13 @@ public class ApplicationEvaluationDao { return callRepository.findCallEntityByApplicationId(applicationId); } - private List getEvaluationCriterias(CallEntity call) { + public List getEvaluationCriterias(CallEntity call) { log.info("Fetching evaluation criterias for callId: {}", call.getId()); return evaluationCriteriaRepository .findByCallIdAndLookupDataTypeAndIsDeletedFalse(call.getId(), LookUpDataEntity.LookUpDataTypeEnum.EVALUATION_CRITERIA.getValue()); } - private CriteriaResponse buildCriteriaResponse(Long applicationId, EvaluationCriteriaEntity criteria) { + public CriteriaResponse buildCriteriaResponse(Long applicationId, EvaluationCriteriaEntity criteria) { CriteriaResponse response = new CriteriaResponse(); response.setId(criteria.getId()); response.setLabel(criteria.getLookupData().getValue()); From e4cd66247eec37d676f147314e19836e172dd824 Mon Sep 17 00:00:00 2001 From: rajesh Date: Wed, 2 Jul 2025 19:11:07 +0530 Subject: [PATCH 29/35] Updated code for csv --- .../tendermanagement/dao/ApplicationDao.java | 50 +++++++++++++------ .../entities/ApplicationEntity.java | 3 ++ .../gepafin/tendermanagement/util/Utils.java | 35 +++++++++++-- .../db/changelog/db.changelog-1.0.0.xml | 6 +++ 4 files changed, 75 insertions(+), 19 deletions(-) diff --git a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java index 276c5c23..3f829a92 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java @@ -63,6 +63,7 @@ import java.math.BigDecimal; import java.nio.charset.StandardCharsets; import java.sql.Timestamp; import java.text.MessageFormat; +import java.text.NumberFormat; import java.text.SimpleDateFormat; import java.time.LocalDate; import java.time.LocalDateTime; @@ -594,11 +595,9 @@ public class ApplicationDao { String fieldType = content.getName(); // Define handlers for different (fieldType + settingName) combinations - Map handlers = new HashMap<>(); // Add handler for isRequestedAmount if ("numberinput".equals(fieldType) && Boolean.TRUE.equals(settingMap.get("isRequestedAmount"))) { - handlers.put("isRequestedAmount", () -> { try { BigDecimal amountRequested = new BigDecimal(fieldValue.toString()); applicationFormEntity.getApplication().setAmountRequested(amountRequested); @@ -607,19 +606,20 @@ public class ApplicationDao { log.error("Invalid number format for requested amount: {}", fieldValue, e); throw new IllegalArgumentException("Field value is not a valid number: " + fieldValue, e); } - }); } // Add handler for isPecEmail if ("textinput".equals(fieldType) && Boolean.TRUE.equals(settingMap.get("isPecEmail"))) { - handlers.put("isPecEmail", () -> { applicationFormEntity.getApplication().setPecEmail(fieldValue.toString()); log.info("Set PEC to {} for Application ID: {}", fieldValue, applicationFormEntity.getApplication().getId()); - }); + } + + if ("textinput".equals(fieldType) && Boolean.TRUE.equals(settingMap.get("isPIVA"))) { + applicationFormEntity.getApplication().setVatNumber(fieldValue.toString()); + log.info("Set PEC to {} for Application ID: {}", fieldValue, applicationFormEntity.getApplication().getId()); } // Run all applicable handlers - handlers.values().forEach(Runnable::run); }); @@ -2433,10 +2433,11 @@ public class ApplicationDao { )); List dynamicLabels = new ArrayList<>(); // Maintain insertion order, allow duplicates only once - Map> appLabelScoresMap = new HashMap<>(); + Map> appLabelScoresMap = new HashMap<>(); Map applicationMap = new HashMap<>(); - Map appTotalScoreMap = new HashMap<>(); + Map appTotalScoreMap = new HashMap<>(); + Map appInstructorMap = new HashMap<>(); // New map to store instructor name per app for (ApplicationEntity app : applications) { Long appId = app.getId(); @@ -2445,8 +2446,19 @@ public class ApplicationDao { ApplicationEvaluationEntity evaluation = applicationEvaluationRepository.findByApplicationId(appId); + if (evaluation != null && evaluation.getAssignedApplicationsEntity() != null) { + Long userId = evaluation.getAssignedApplicationsEntity().getUserId(); + if (userId != null) { + userRepository.findById(userId).ifPresent(user -> { + String firstName = user.getFirstName() != null ? user.getFirstName() : ""; + String lastName = user.getLastName() != null ? user.getLastName() : ""; + appInstructorMap.put(appId, firstName + " " + lastName); + }); + } + } + BigDecimal totalScore = applicationEvaluationDao.calculateTotalScore(evaluation.getCriteria()); - appTotalScoreMap.put(appId, totalScore); + appTotalScoreMap.put(appId, Utils.convertToItalianFormatWithOnlyDecimalValue(String.valueOf(totalScore))); List criteriaList = evaluation.getCriteria() != null ? @@ -2455,7 +2467,7 @@ public class ApplicationDao { List dbCriteriaList = applicationEvaluationDao.getCriteriaResponse(appId); - Map scoreByLabel = new HashMap<>(); + Map scoreByLabel = new HashMap<>(); for (CriteriaResponse criteria : criteriaList) { Optional matchedDb = dbCriteriaList.stream() @@ -2467,8 +2479,9 @@ public class ApplicationDao { if (!dynamicLabels.contains(label)) { dynamicLabels.add(label); } + String criteriaScore= String.valueOf(criteria.getScore() != null ? criteria.getScore() : BigDecimal.ZERO); - scoreByLabel.put(label, criteria.getScore() != null ? criteria.getScore() : BigDecimal.ZERO); + scoreByLabel.put(label, Utils.convertToItalianFormatWithOnlyDecimalValue(criteriaScore)); } appLabelScoresMap.put(appId, scoreByLabel); @@ -2476,7 +2489,7 @@ public class ApplicationDao { // Build headers dynamically List headers = new ArrayList<>(List.of( - "ApplicationID", "VatNumber", "Company Name", "Protocol", "Requested Amount", "Status", "Total Score" + "ApplicationID","Application VatNumber", "VatNumber", "Company Name", "Protocol", "Requested Amount", "Status","Instructor Name", "Total Score" )); headers.addAll(dynamicLabels); @@ -2490,17 +2503,20 @@ public class ApplicationDao { List row = new ArrayList<>(); row.add(appId); + row.add(app.getVatNumber()); row.add(company.getVatNumber()); row.add(company.getCompanyName()); row.add(protocolEntity != null ? protocolEntity.getProtocolNumber() : 0L); - row.add(app.getAmountRequested()); + String formattedAmount=Utils.convertToItalianFormatWithOnlyDecimalValue(String.valueOf(app.getAmountRequested())); + row.add(formattedAmount); row.add(app.getStatus()); + row.add(appInstructorMap.getOrDefault(appId, "")); row.add(appTotalScoreMap.get(appId)); - Map scores = appLabelScoresMap.getOrDefault(appId, Collections.emptyMap()); + Map scores = appLabelScoresMap.getOrDefault(appId, Collections.emptyMap()); for (String label : dynamicLabels) { - row.add(scores.getOrDefault(label, BigDecimal.ZERO)); + row.add(scores.getOrDefault(label, "")); } rows.add(row); @@ -2510,7 +2526,9 @@ public class ApplicationDao { ByteArrayOutputStream out = new ByteArrayOutputStream(); try (OutputStreamWriter writer = new OutputStreamWriter(out, StandardCharsets.UTF_8); CSVPrinter csvPrinter = new CSVPrinter(writer, - CSVFormat.DEFAULT.withHeader(headers.toArray(new String[0])))) { + CSVFormat.DEFAULT + .withDelimiter(';') + .withHeader(headers.toArray(new String[0])))) { for (List row : rows) { csvPrinter.printRecord(row); diff --git a/src/main/java/net/gepafin/tendermanagement/entities/ApplicationEntity.java b/src/main/java/net/gepafin/tendermanagement/entities/ApplicationEntity.java index 8e783c70..dacc3541 100644 --- a/src/main/java/net/gepafin/tendermanagement/entities/ApplicationEntity.java +++ b/src/main/java/net/gepafin/tendermanagement/entities/ApplicationEntity.java @@ -76,4 +76,7 @@ public class ApplicationEntity extends BaseEntity { @Column(name = "PEC_EMAIL") private String pecEmail; + @Column(name="VAT_NUMBER") + private String vatNumber; + } \ No newline at end of file diff --git a/src/main/java/net/gepafin/tendermanagement/util/Utils.java b/src/main/java/net/gepafin/tendermanagement/util/Utils.java index 19feb0e2..13cf6e87 100644 --- a/src/main/java/net/gepafin/tendermanagement/util/Utils.java +++ b/src/main/java/net/gepafin/tendermanagement/util/Utils.java @@ -6,9 +6,7 @@ import java.lang.reflect.Type; import java.nio.charset.StandardCharsets; import java.security.SecureRandom; import java.sql.Timestamp; -import java.text.NumberFormat; -import java.text.ParseException; -import java.text.SimpleDateFormat; +import java.text.*; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.OffsetDateTime; @@ -1063,6 +1061,37 @@ public class Utils { ServletRequestAttributes attributes = new ServletRequestAttributes(mockRequest); RequestContextHolder.setRequestAttributes(attributes, true); } + public static String convertToItalianFormatWithOnlyDecimalValue(String amount) { + try { + // Step 1: Sanitize and standardize the input + String sanitizedAmount = amount.trim(); + + // Remove thousand separators (either . or , depending on input style) + sanitizedAmount = sanitizedAmount.replace(",", "").replace(" ", ""); + + // Step 2: Ensure it uses '.' as decimal separator for parsing + if (sanitizedAmount.contains(".")) { + // It already uses dot as decimal separator + } else if (sanitizedAmount.contains(",")) { + // If input uses comma as decimal separator (like "1234,56") + sanitizedAmount = sanitizedAmount.replace(",", "."); + } + + // Step 3: Parse to double + double parsedAmount = Double.parseDouble(sanitizedAmount); + + // Step 4: Format without thousand separator and with comma as decimal + DecimalFormatSymbols symbols = new DecimalFormatSymbols(); + symbols.setDecimalSeparator(','); + + DecimalFormat italianDecimalFormat = new DecimalFormat("0.00", symbols); + italianDecimalFormat.setGroupingUsed(false); // no thousand separator + + return italianDecimalFormat.format(parsedAmount); + } catch (NumberFormatException e) { + return "Invalid amount format"; + } + } } 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 c16d810c..28f5c17a 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 @@ -2984,4 +2984,10 @@ + + + + + + From cbd06770fd40e95107735cb25acfee7ffd107980 Mon Sep 17 00:00:00 2001 From: rajesh Date: Thu, 3 Jul 2025 15:01:31 +0530 Subject: [PATCH 30/35] Added field pec in csv --- .../gepafin/tendermanagement/dao/ApplicationDao.java | 12 +++++++++--- .../service/impl/ApplicationServiceImpl.java | 2 +- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java index 3f829a92..0564ad66 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java @@ -219,6 +219,9 @@ public class ApplicationDao { @Autowired private EvaluationCriteriaRepository evaluationCriteriaRepository; + @Autowired + private CallRepository callRepository; + public final Random random = new Random(); public ApplicationResponseBean createApplication(HttpServletRequest request, ApplicationRequestBean applicationRequestBean, Long formId, Long applicationId) { @@ -2420,8 +2423,9 @@ public class ApplicationDao { emailNotificationDao.sendMail(hub.getId(), subject, body, List.of(GepafinConstant.RINALDO_EMAIL),emailLogRequest); } - public byte[] downloadRankingCsv(Long callId) { - CallEntity callEntity = callService.validateCall(callId); + public byte[] downloadRankingCsv(Long callId,UserEntity userEntity) { + + CallEntity callEntity = validator.validateUserWithCall(userEntity,callId); List applications = applicationRepository.findByCallIdAndIsDeletedFalseAndStatusIn( @@ -2489,7 +2493,7 @@ public class ApplicationDao { // Build headers dynamically List headers = new ArrayList<>(List.of( - "ApplicationID","Application VatNumber", "VatNumber", "Company Name", "Protocol", "Requested Amount", "Status","Instructor Name", "Total Score" + "ApplicationID","Application VatNumber", "VatNumber", "Company Name", "Protocol", "Requested Amount", "Status","Instructor Name","Application PEC","Company PEC","Total Score" )); headers.addAll(dynamicLabels); @@ -2511,6 +2515,8 @@ public class ApplicationDao { row.add(formattedAmount); row.add(app.getStatus()); row.add(appInstructorMap.getOrDefault(appId, "")); + row.add(app.getPecEmail()); + row.add(company.getPec()); row.add(appTotalScoreMap.get(appId)); Map scores = appLabelScoresMap.getOrDefault(appId, Collections.emptyMap()); 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 aca38d23..e311340e 100644 --- a/src/main/java/net/gepafin/tendermanagement/service/impl/ApplicationServiceImpl.java +++ b/src/main/java/net/gepafin/tendermanagement/service/impl/ApplicationServiceImpl.java @@ -180,6 +180,6 @@ public class ApplicationServiceImpl implements ApplicationService { @Override public byte[] downloadRankingCsv(HttpServletRequest request, Long callId) { UserEntity userEntity = validator.validateUser(request); - return applicationDao.downloadRankingCsv(callId); + return applicationDao.downloadRankingCsv(callId,userEntity); } } From f1aadafa1a0faa24498bc5b136d674765bb28feb Mon Sep 17 00:00:00 2001 From: rajesh Date: Thu, 3 Jul 2025 17:28:53 +0530 Subject: [PATCH 31/35] Done ticket GEPAFINBE-233 --- .../net/gepafin/tendermanagement/dao/ApplicationDao.java | 8 +++++--- .../java/net/gepafin/tendermanagement/dao/CallDao.java | 7 +++++++ .../net/gepafin/tendermanagement/entities/CallEntity.java | 3 +++ .../model/request/CreateCallRequestStep1.java | 2 ++ .../model/request/UpdateCallRequestStep1.java | 2 ++ .../model/response/CallDetailsResponseBean.java | 2 ++ .../tendermanagement/model/response/CallResponse.java | 2 ++ src/main/resources/db/changelog/db.changelog-1.0.0.xml | 6 ++++++ 8 files changed, 29 insertions(+), 3 deletions(-) diff --git a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java index 276c5c23..e46e6979 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java @@ -989,10 +989,12 @@ public class ApplicationDao { // call = callService.validatePublishedCall(call.getId()); // checkIfApplicationExists(call, userWithCompanyEntity, userEntity); HubEntity hubEntity = hubService.valdateHub(call.getHub().getId()); - if(hubEntity.getUniqueUuid().equals(defaultHubUuid)){ - checkIfApplicationExists(call, userWithCompanyEntity, userEntity); + if(call.getAllowMultipleApplications() == null || Boolean.FALSE.equals(call.getAllowMultipleApplications())){ + if(hubEntity.getUniqueUuid().equals(defaultHubUuid)){ + checkIfApplicationExists(call, userWithCompanyEntity, userEntity); + } } - ApplicationEntity applicationEntity = createApplicationEntity(userEntity, call, userWithCompanyEntity); + ApplicationEntity applicationEntity = createApplicationEntity(userEntity, call, userWithCompanyEntity); applicationEntity.setComments(applicationRequest.getComments()); applicationEntity = saveApplicationEntity(applicationEntity); return getApplicationResponse(applicationEntity); diff --git a/src/main/java/net/gepafin/tendermanagement/dao/CallDao.java b/src/main/java/net/gepafin/tendermanagement/dao/CallDao.java index 94c6f912..509e9e51 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/CallDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/CallDao.java @@ -224,6 +224,10 @@ public class CallDao { callEntity.setHub(userEntity.getHub()); callEntity.setNumberOfCheck(createCallRequest.getNumberOfCheck()); callEntity.setAppointmentTemplateId(createCallRequest.getAppointmentTemplateId()); + callEntity.setAllowMultipleApplications(false); + if (createCallRequest.getAllowMultipleApplications() != null) { + callEntity.setAllowMultipleApplications(createCallRequest.getAllowMultipleApplications()); + } callEntity = callRepository.save(callEntity); log.info("CallEntity saved with ID: {} for call name: '{}'", callEntity.getId(), callEntity.getName()); @@ -406,6 +410,7 @@ public class CallDao { createCallResponseBean.setDocumentationRequested(callEntity.getDocumentationRequested()); createCallResponseBean.setPriorityArea(callEntity.getPriorityArea()); createCallResponseBean.setConfidi(callEntity.getConfidi()); + createCallResponseBean.setAllowMultipleApplications(callEntity.getAllowMultipleApplications()); createCallResponseBean.setAmountMin(callEntity.getAmountMin()); createCallResponseBean.setPhoneNumber(callEntity.getPhoneNumber()); createCallResponseBean.setEndTime(callEntity.getEndTime()); @@ -683,6 +688,7 @@ public class CallDao { setIfUpdated(callEntity::getEvaluationVersion, callEntity::setEvaluationVersion, updateCallRequest.getEvaluationVersion().getValue()); setIfUpdated(callEntity::getNumberOfCheck, callEntity::setNumberOfCheck, updateCallRequest.getNumberOfCheck()); setIfUpdated(callEntity::getAppointmentTemplateId, callEntity::setAppointmentTemplateId, updateCallRequest.getAppointmentTemplateId()); + setIfUpdated(callEntity::getAllowMultipleApplications, callEntity::setAllowMultipleApplications, updateCallRequest.getAllowMultipleApplications()); callEntity = callRepository.save(callEntity); /** This code is responsible for adding a version history log for the "update call step 1" operation **/ @@ -792,6 +798,7 @@ public class CallDao { callDetailsResponseBean.setUpdatedDate(callEntity.getUpdatedDate()); callDetailsResponseBean.setNumberOfCheck(callEntity.getNumberOfCheck()); callDetailsResponseBean.setAppointmentTemplateId(callEntity.getAppointmentTemplateId()); + callDetailsResponseBean.setAllowMultipleApplications(callEntity.getAllowMultipleApplications()); return callDetailsResponseBean; } diff --git a/src/main/java/net/gepafin/tendermanagement/entities/CallEntity.java b/src/main/java/net/gepafin/tendermanagement/entities/CallEntity.java index dc8c05d8..dd2655ed 100644 --- a/src/main/java/net/gepafin/tendermanagement/entities/CallEntity.java +++ b/src/main/java/net/gepafin/tendermanagement/entities/CallEntity.java @@ -99,5 +99,8 @@ public class CallEntity extends BaseEntity { @Column(name = "APPOINTMENT_TEMPLATE_ID") private Long appointmentTemplateId; + + @Column(name = "allow_multiple_applications") + private Boolean allowMultipleApplications; } diff --git a/src/main/java/net/gepafin/tendermanagement/model/request/CreateCallRequestStep1.java b/src/main/java/net/gepafin/tendermanagement/model/request/CreateCallRequestStep1.java index 7d4bf8cf..c364db5a 100644 --- a/src/main/java/net/gepafin/tendermanagement/model/request/CreateCallRequestStep1.java +++ b/src/main/java/net/gepafin/tendermanagement/model/request/CreateCallRequestStep1.java @@ -44,6 +44,8 @@ public class CreateCallRequestStep1 { private Boolean confidi; + private Boolean allowMultipleApplications; + private List faq; private EvaluationVersionEnum evaluationVersion; diff --git a/src/main/java/net/gepafin/tendermanagement/model/request/UpdateCallRequestStep1.java b/src/main/java/net/gepafin/tendermanagement/model/request/UpdateCallRequestStep1.java index 01a0ea39..30530381 100644 --- a/src/main/java/net/gepafin/tendermanagement/model/request/UpdateCallRequestStep1.java +++ b/src/main/java/net/gepafin/tendermanagement/model/request/UpdateCallRequestStep1.java @@ -38,6 +38,8 @@ public class UpdateCallRequestStep1 { private Boolean confidi; + private Boolean allowMultipleApplications; + private List faq; private Long numberOfCheck; diff --git a/src/main/java/net/gepafin/tendermanagement/model/response/CallDetailsResponseBean.java b/src/main/java/net/gepafin/tendermanagement/model/response/CallDetailsResponseBean.java index 7ee0b620..51dbdd4d 100644 --- a/src/main/java/net/gepafin/tendermanagement/model/response/CallDetailsResponseBean.java +++ b/src/main/java/net/gepafin/tendermanagement/model/response/CallDetailsResponseBean.java @@ -23,6 +23,8 @@ public class CallDetailsResponseBean { private Boolean confidi; + private Boolean allowMultipleApplications; + private CallStatusEnum status; private Long regionId; diff --git a/src/main/java/net/gepafin/tendermanagement/model/response/CallResponse.java b/src/main/java/net/gepafin/tendermanagement/model/response/CallResponse.java index 76289b06..13a90715 100644 --- a/src/main/java/net/gepafin/tendermanagement/model/response/CallResponse.java +++ b/src/main/java/net/gepafin/tendermanagement/model/response/CallResponse.java @@ -44,6 +44,8 @@ public class CallResponse { private Boolean confidi; + private Boolean allowMultipleApplications; + private BigDecimal amountMin; private String email; 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 c16d810c..39f73ce4 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 @@ -2984,4 +2984,10 @@ + + + + + + From 2a75dadba920fc61bbb6c1f4ccb80d941a8b59f8 Mon Sep 17 00:00:00 2001 From: rajesh Date: Tue, 8 Jul 2025 19:31:24 +0530 Subject: [PATCH 32/35] Done ticket GEPAFINBE-234 --- .../dao/AssignedApplicationsDao.java | 1 + .../entities/ApplicationFormView.java | 3 + .../entities/AssignedApplicationsView.java | 3 + .../AssignedApplicationViewResponse.java | 1 + .../db/changelog/db.changelog-1.0.0.xml | 8 ++ ...pdate_application_form_view_08_07_2025.sql | 105 ++++++++++++++++++ ...e_assigned_application_view_08_07_2025.sql | 62 +++++++++++ 7 files changed, 183 insertions(+) create mode 100644 src/main/resources/db/dump/update_application_form_view_08_07_2025.sql create mode 100644 src/main/resources/db/dump/update_assigned_application_view_08_07_2025.sql diff --git a/src/main/java/net/gepafin/tendermanagement/dao/AssignedApplicationsDao.java b/src/main/java/net/gepafin/tendermanagement/dao/AssignedApplicationsDao.java index c05b04c2..0df566f5 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/AssignedApplicationsDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/AssignedApplicationsDao.java @@ -491,6 +491,7 @@ public class AssignedApplicationsDao { response.setCreatedDate(view.getCreatedDate()); response.setUpdatedDate(view.getUpdatedDate()); response.setEmailSendResponse(view.getEmailSendResponse()); + response.setAssignedUserName(view.getAssignedUserName()); return response; } diff --git a/src/main/java/net/gepafin/tendermanagement/entities/ApplicationFormView.java b/src/main/java/net/gepafin/tendermanagement/entities/ApplicationFormView.java index b3733afc..0af565bf 100644 --- a/src/main/java/net/gepafin/tendermanagement/entities/ApplicationFormView.java +++ b/src/main/java/net/gepafin/tendermanagement/entities/ApplicationFormView.java @@ -115,4 +115,7 @@ public class ApplicationFormView { @Column(name = "call_start_time") private LocalTime callStartTime; + @Column(name = "INSTRUCTOR_NAME") + private String instructorName; + } diff --git a/src/main/java/net/gepafin/tendermanagement/entities/AssignedApplicationsView.java b/src/main/java/net/gepafin/tendermanagement/entities/AssignedApplicationsView.java index 46bf004b..e4224631 100644 --- a/src/main/java/net/gepafin/tendermanagement/entities/AssignedApplicationsView.java +++ b/src/main/java/net/gepafin/tendermanagement/entities/AssignedApplicationsView.java @@ -67,4 +67,7 @@ public class AssignedApplicationsView{ @Column(name = "HUB_ID") private Long hubId; + + @Column(name="ASSIGNED_USER_NAME") + private String assignedUserName; } diff --git a/src/main/java/net/gepafin/tendermanagement/model/response/AssignedApplicationViewResponse.java b/src/main/java/net/gepafin/tendermanagement/model/response/AssignedApplicationViewResponse.java index e9a1f9f5..22f00b0d 100644 --- a/src/main/java/net/gepafin/tendermanagement/model/response/AssignedApplicationViewResponse.java +++ b/src/main/java/net/gepafin/tendermanagement/model/response/AssignedApplicationViewResponse.java @@ -21,6 +21,7 @@ public class AssignedApplicationViewResponse extends BaseBean { private Long protocolNumber; private String callName; private String companyName; + private String assignedUserName; private List emailSendResponse; 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 f459961b..ef0f3447 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 @@ -2996,4 +2996,12 @@ + + + + + + diff --git a/src/main/resources/db/dump/update_application_form_view_08_07_2025.sql b/src/main/resources/db/dump/update_application_form_view_08_07_2025.sql new file mode 100644 index 00000000..e68f0b60 --- /dev/null +++ b/src/main/resources/db/dump/update_application_form_view_08_07_2025.sql @@ -0,0 +1,105 @@ + +DROP VIEW IF EXISTS gepafin_schema.application_form_view ; + +CREATE OR REPLACE VIEW gepafin_schema.application_form_view AS +SELECT app_data.id, + app_data.call_id, + app_data.application_form_id, + app_data.form_id, + app_data.application_id, + field_data.value ->> 'id'::text AS field_id, + COALESCE(( SELECT s.value ->> 'value'::text + FROM jsonb_array_elements(field_data.value -> 'settings'::text) s(value) + WHERE (s.value ->> 'name'::text) = 'label'::text + LIMIT 1), field_data.value ->> 'label'::text) AS field_label, + ( SELECT (s.value ->> 'value'::text)::boolean AS bool + FROM jsonb_array_elements(field_data.value -> 'settings'::text) s(value) + WHERE (s.value ->> 'name'::text) = 'reportEnable'::text + LIMIT 1) AS report_enable, + COALESCE(( SELECT s.value ->> 'value'::text + FROM jsonb_array_elements(field_data.value -> 'settings'::text) s(value) + WHERE (s.value ->> 'name'::text) = 'reportHeader'::text + LIMIT 1), field_data.value ->> 'reportHeader'::text) AS report_header, + field_data.value ->> 'name'::text AS field_type, + CASE + WHEN (field_data.value ->> 'name'::text) = 'fileupload'::text THEN to_jsonb(( SELECT string_agg(d.file_name::text, ', '::text) AS string_agg + FROM unnest(string_to_array(app_data.field_value, ','::text)) file_ids(file_id) + JOIN gepafin_schema.document d ON d.id::text = file_ids.file_id + WHERE d.is_deleted = false)) + WHEN (field_data.value ->> 'name'::text) = ANY (ARRAY['checkboxes'::text, 'select'::text, 'radio'::text]) THEN + CASE + WHEN app_data.field_value ~~ '[%'::text THEN to_jsonb(( SELECT string_agg(opt.value ->> 'label'::text, ', '::text) AS string_agg + FROM jsonb_array_elements_text(app_data.field_value::jsonb) selected_id(value) + CROSS JOIN LATERAL ( SELECT s.value + FROM jsonb_array_elements(field_data.value -> 'settings'::text) s(value) + WHERE (s.value ->> 'name'::text) = 'options'::text) options_setting, + LATERAL jsonb_array_elements(options_setting.value -> 'value'::text) opt(value) + WHERE (opt.value ->> 'name'::text) = selected_id.value)) + ELSE to_jsonb(( SELECT opt.value ->> 'label'::text + FROM ( SELECT s.value + FROM jsonb_array_elements(field_data.value -> 'settings'::text) s(value) + WHERE (s.value ->> 'name'::text) = 'options'::text) options_setting, + LATERAL jsonb_array_elements(options_setting.value -> 'value'::text) opt(value) + WHERE (opt.value ->> 'name'::text) = app_data.field_value + LIMIT 1)) + END + ELSE to_jsonb(app_data.field_value) + END AS field_value, + app_data.status, + app_data.amount_requested, + app_data.amount_accepted, + app_data.is_deleted, + app_data.hub_id, + app_data.user_id, + app_data.evaluation_version, + app_data.company_id, + c.company_name, + c.vat_number AS company_vat_number, + c.codice_ateco, + c.codice_fiscale AS company_codice_fiscale, + p.protocol_number, + b.codice_fiscale AS user_codice_fiscale, + COALESCE(NULLIF(TRIM(BOTH FROM concat(COALESCE(u.first_name, ''::character varying), ' ', COALESCE(u.last_name, ''::character varying))), ''::text), ''::text) AS user_name, + COALESCE( NULLIF(TRIM(BOTH FROM CONCAT(COALESCE(aauser.first_name, ''), ' ', COALESCE(aauser.last_name, ''))),''),'NA') AS instructor_name, + uwc.is_legal_representant AS legal_representative, + cl.name AS call_title, + cl.end_date AS call_end_date, + cl.end_time AS call_end_time, + cl.start_date AS call_start_date, + cl.start_time AS call_start_time + FROM ( SELECT a.id AS application_id, + a.call_id, + a.protocol_number, + af.id AS application_form_id, + af.form_id, + aff.id, + aff.field_value, + a.status, + a.amount_requested, + a.amount_accepted, + a.is_deleted, + a.hub_id, + a.user_id, + a.evaluation_version, + a.created_date, + a.company_id, + aff.field_id, + f.content + FROM gepafin_schema.application a + JOIN gepafin_schema.application_form af ON af.application_id = a.id + JOIN gepafin_schema.application_form_field aff ON aff.application_form_id = af.id + JOIN gepafin_schema.form f ON f.id = af.form_id + WHERE a.is_deleted = false) app_data + CROSS JOIN LATERAL ( SELECT jsonb_array_elements.value + FROM jsonb_array_elements(app_data.content::jsonb) jsonb_array_elements(value) + WHERE (jsonb_array_elements.value ->> 'id'::text) = app_data.field_id::text) field_data(value) + LEFT JOIN gepafin_schema.call cl ON app_data.call_id = cl.id + LEFT JOIN gepafin_schema.company c ON app_data.company_id = c.id + LEFT JOIN gepafin_schema.protocol p ON app_data.protocol_number = p.id + LEFT JOIN gepafin_schema.gepafin_user u ON app_data.user_id = u.id + LEFT JOIN gepafin_schema.user_with_company uwc ON app_data.user_id = uwc.user_id AND app_data.company_id = uwc.company_id AND uwc.is_deleted = false + LEFT JOIN gepafin_schema.beneficiary b ON u.beneficiary_id = b.id + LEFT JOIN gepafin_schema.assigned_applications aa ON app_data.application_id = aa.application_id + LEFT JOIN gepafin_schema.gepafin_user aauser ON aa.user_id = aauser.id + WHERE app_data.id IS NOT NULL AND (app_data.status::text <> ALL (ARRAY['DRAFT'::character varying::text, 'AWAITING'::character varying::text, 'READY'::character varying::text])) + ORDER BY app_data.id, (field_data.value ->> 'id'::text); \ No newline at end of file diff --git a/src/main/resources/db/dump/update_assigned_application_view_08_07_2025.sql b/src/main/resources/db/dump/update_assigned_application_view_08_07_2025.sql new file mode 100644 index 00000000..354ab8c5 --- /dev/null +++ b/src/main/resources/db/dump/update_assigned_application_view_08_07_2025.sql @@ -0,0 +1,62 @@ + +DROP VIEW IF EXISTS gepafin_schema.assigned_applications_view ; + +CREATE OR REPLACE VIEW gepafin_schema.assigned_applications_view AS +SELECT + -- From assigned_applications + aa.id AS id, + aa.user_id AS user_id, + aa.status AS status, + aa.created_date AS created_date, + aa.updated_date AS updated_date, + aa.is_deleted AS is_deleted, + + -- From application + a.id AS application_id, + a.hub_id as hub_id, + a.status AS application_status, + a.submission_date AS submission_date, + ae.end_date AS evaluation_end_date, + a.ndg AS ndg, + a.appointment_id AS appointment_id, + + -- From protocol (OneToOne) + p.protocol_number AS protocol_number, + + -- From call (ManyToOne) + cl.name AS call_name, + + -- From company (ManyToOne) + c.company_name AS company_name, + ae.email_send_response AS email_send_response, + COALESCE(NULLIF(TRIM(BOTH FROM concat(COALESCE(u.first_name, ''::character varying), ' ', COALESCE(u.last_name, ''::character varying))), ''::text), ''::text) AS assigned_user_name + + +FROM gepafin_schema.assigned_applications aa + +-- Join application (ManyToOne from assigned_applications) +LEFT JOIN gepafin_schema.application a + ON aa.application_id = a.id + AND (a.is_deleted IS FALSE OR a.is_deleted IS NULL) + +-- Join application_evaluation (application_id matches + not deleted) +LEFT JOIN gepafin_schema.application_evaluation ae + ON ae.application_id = a.id + AND (ae.is_deleted IS FALSE OR ae.is_deleted IS NULL) + +-- Join protocol (OneToOne from application) +LEFT JOIN gepafin_schema.protocol p + ON a.protocol_number = p.id + +-- Join call (ManyToOne from application) +LEFT JOIN gepafin_schema.call cl + ON a.call_id = cl.id + +-- Join company (ManyToOne from application) +LEFT JOIN gepafin_schema.company c + ON a.company_id = c.id + +LEFT JOIN gepafin_schema.gepafin_user u ON aa.user_id = u.id + + +WHERE aa.is_deleted IS FALSE OR aa.is_deleted IS NULL; From ad26599d48a95415654dd617d07a358e240b055a Mon Sep 17 00:00:00 2001 From: rajesh Date: Thu, 10 Jul 2025 20:31:41 +0530 Subject: [PATCH 33/35] Fixed call end date issue --- .../constants/GepafinConstant.java | 2 + .../gepafin/tendermanagement/dao/CallDao.java | 49 ++++++++++++++----- src/main/resources/message_en.properties | 2 + src/main/resources/message_it.properties | 2 + 4 files changed, 42 insertions(+), 13 deletions(-) diff --git a/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java b/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java index 42857d23..71c63a9f 100644 --- a/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java +++ b/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java @@ -575,6 +575,8 @@ public class GepafinConstant { public static final String NDG_NOT_FOUND="ndg.not.found"; public static final String EMAIL_PEC_REQUIRED="email.pec.cannot.null"; public static final String USER_REQUEST_COMPLETED="user.request.completed"; + public static final String END_DATE_GREATER_THAN_NOW="end.date.greater.than.now"; + } diff --git a/src/main/java/net/gepafin/tendermanagement/dao/CallDao.java b/src/main/java/net/gepafin/tendermanagement/dao/CallDao.java index 509e9e51..d0bef93e 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/CallDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/CallDao.java @@ -628,19 +628,19 @@ public class CallDao { if (dates.size() > 0) { setIfUpdated(callEntity::getStartDate, callEntity::setStartDate, dates.get(0)); } - if (dates.size() > 1) { - LocalDate requestEndDate = dates.get(1).toLocalDate(); // Extract only the date - LocalDate storedEndDate = callEntity.getEndDate().toLocalDate(); // Extract only the date - - if (!requestEndDate.equals(storedEndDate)) { // Check if dates are different - - setIfUpdated(callEntity::getEndDate, callEntity::setEndDate, dates.get(1)); -// callEntity.setStatus(CallStatusEnum.PUBLISH.getValue()); -// callRepository.save(callEntity); - isEndDateUpdated = true; - } - } - } +// if (dates.size() > 1) { +// LocalDate requestEndDate = dates.get(1).toLocalDate(); // Extract only the date +// LocalDate storedEndDate = callEntity.getEndDate().toLocalDate(); // Extract only the date +// +// if (!requestEndDate.equals(storedEndDate)) { // Check if dates are different +// +// setIfUpdated(callEntity::getEndDate, callEntity::setEndDate, dates.get(1)); +//// callEntity.setStatus(CallStatusEnum.PUBLISH.getValue()); +//// callRepository.save(callEntity); +// isEndDateUpdated = true; +// } +// } +// } if (updateCallRequest.getEndTime() != null) { LocalTime requestEndTime = DateTimeUtil.parseTime(updateCallRequest.getEndTime()); @@ -653,6 +653,29 @@ public class CallDao { isEndTimeUpdated = true; } } + if (dates.size() > 1) { + LocalDate requestEndDate = dates.get(1).toLocalDate(); // Extract only the date + LocalDate storedEndDate = callEntity.getEndDate().toLocalDate(); // Extract only the date + + if (!requestEndDate.equals(storedEndDate)) { + // Check if dates are different + + setIfUpdated(callEntity::getEndDate, callEntity::setEndDate, dates.get(1)); + if(callEntity.getStatus().equals(CallStatusEnum.EXPIRED.getValue())) { + LocalDateTime newEndDate = LocalDateTime.of(requestEndDate, callEntity.getEndTime()); + if(newEndDate.isBefore(LocalDateTime.now())){ + throw new CustomValidationException(Status.VALIDATION_ERROR,Translator.toLocale(GepafinConstant.END_DATE_GREATER_THAN_NOW)); + } + + if (requestEndDate.isAfter(LocalDate.now()) || requestEndDate.isEqual(LocalDate.now())) { + callEntity.setStatus(CallStatusEnum.PUBLISH.getValue()); + callRepository.save(callEntity); + } + } + isEndDateUpdated = true; + } + } + } if (isEndDateUpdated || isEndTimeUpdated) { callRepository.save(callEntity); loggingUtil.logUserAction(UserActionRequest.builder() diff --git a/src/main/resources/message_en.properties b/src/main/resources/message_en.properties index a706e568..ad25a88b 100644 --- a/src/main/resources/message_en.properties +++ b/src/main/resources/message_en.properties @@ -411,3 +411,5 @@ user.action.id.not.found = User Action id not found. ndg.not.found=NDG not found. email.pec.cannot.null=Email pec is required. user.request.completed=User request completed successfully. +end.date.greater.than.now=End date must be greater than the current date and time. + diff --git a/src/main/resources/message_it.properties b/src/main/resources/message_it.properties index f4246425..f98e55b8 100644 --- a/src/main/resources/message_it.properties +++ b/src/main/resources/message_it.properties @@ -402,3 +402,5 @@ user.action.id.not.found = ID azione utente non trovato. ndg.not.found=NDG non trovato. email.pec.cannot.null=L'indirizzo email pec è obbligatorio. user.request.completed=Richiesta utente completata con successo. +end.date.greater.than.now=La data di fine deve essere successiva alla data e all'ora correnti. + From 107d1da6e1e358e08c385a9f8ee5cd9139d7d8f4 Mon Sep 17 00:00:00 2001 From: rajesh Date: Mon, 14 Jul 2025 12:09:26 +0530 Subject: [PATCH 34/35] Done ticket GEPAFINBE-232 --- .../dao/ApplicationAmendmentRequestDao.java | 2 +- .../dao/ApplicationEvaluationDao.java | 2 +- .../tendermanagement/dao/DocumentDao.java | 98 +++++++++++++++++-- 3 files changed, 91 insertions(+), 11 deletions(-) diff --git a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationAmendmentRequestDao.java b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationAmendmentRequestDao.java index 2882dc7f..68d67983 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationAmendmentRequestDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationAmendmentRequestDao.java @@ -803,7 +803,7 @@ public class ApplicationAmendmentRequestDao { } - private List getApplicationFormFieldList( + public List getApplicationFormFieldList( ApplicationAmendmentRequestEntity applicationAmendment, List fieldIds) { List applicationFormList = applicationFormRepository diff --git a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationEvaluationDao.java b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationEvaluationDao.java index 9fa68a28..91e7b8b3 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationEvaluationDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationEvaluationDao.java @@ -1155,7 +1155,7 @@ public class ApplicationEvaluationDao { applicationEvaluationResponse.setEmailSendResponse(entity.getEmailSendResponse()); return applicationEvaluationResponse; } - private List prepareEvaluationDocumentBeanList(ApplicationEvaluationEntity entity) { + public List prepareEvaluationDocumentBeanList(ApplicationEvaluationEntity entity) { List docRequest = new ArrayList<>(); if (entity != null && entity.getEvaluationDocument() != null) { diff --git a/src/main/java/net/gepafin/tendermanagement/dao/DocumentDao.java b/src/main/java/net/gepafin/tendermanagement/dao/DocumentDao.java index 3b5b41f6..013bf9e5 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/DocumentDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/DocumentDao.java @@ -1,13 +1,19 @@ package net.gepafin.tendermanagement.dao; import lombok.extern.slf4j.Slf4j; +import java.util.*; +import java.util.function.Function; import java.util.stream.Collectors; import jakarta.servlet.http.HttpServletRequest; +import net.gepafin.tendermanagement.entities.*; import net.gepafin.tendermanagement.enums.*; +import net.gepafin.tendermanagement.model.request.AmendmentFormField; +import net.gepafin.tendermanagement.model.request.EvaluationDocumentRequest; import net.gepafin.tendermanagement.model.request.VersionHistoryRequest; -import net.gepafin.tendermanagement.repositories.ApplicationEvaluationRepository; -import net.gepafin.tendermanagement.repositories.ApplicationRepository; +import net.gepafin.tendermanagement.model.response.ContentResponseBean; +import net.gepafin.tendermanagement.model.response.SettingResponseBean; +import net.gepafin.tendermanagement.repositories.*; import net.gepafin.tendermanagement.util.LoggingUtil; import net.gepafin.tendermanagement.util.Utils; import net.gepafin.tendermanagement.web.rest.api.errors.CustomValidationException; @@ -17,13 +23,8 @@ import org.springframework.web.multipart.MultipartFile; import net.gepafin.tendermanagement.config.Translator; import net.gepafin.tendermanagement.constants.GepafinConstant; -import net.gepafin.tendermanagement.entities.ApplicationEntity; -import net.gepafin.tendermanagement.entities.CallEntity; -import net.gepafin.tendermanagement.entities.DocumentEntity; import net.gepafin.tendermanagement.model.response.DocumentResponseBean; import net.gepafin.tendermanagement.model.response.UploadFileOnAmazonS3Response; -import net.gepafin.tendermanagement.repositories.ApplicationAmendmentRequestRepository; -import net.gepafin.tendermanagement.repositories.DocumentRepository; import net.gepafin.tendermanagement.service.AmazonS3Service; import net.gepafin.tendermanagement.service.ApplicationAmendmentRequestService; import net.gepafin.tendermanagement.service.ApplicationService; @@ -31,8 +32,6 @@ import net.gepafin.tendermanagement.service.CallService; import net.gepafin.tendermanagement.web.rest.api.errors.ResourceNotFoundException; import net.gepafin.tendermanagement.web.rest.api.errors.Status; import org.springframework.beans.factory.annotation.Value; -import java.util.ArrayList; -import java.util.List; @Slf4j @@ -78,6 +77,18 @@ public class DocumentDao { @Autowired private HttpServletRequest request; + @Autowired + private ApplicationFormRepository applicationFormRepository; + + @Autowired + private ApplicationFormFieldRepository applicationFormFieldRepository; + + @Autowired + private ApplicationAmendmentRequestDao applicationAmendmentRequestDao; + + @Autowired + private ApplicationEvaluationDao applicationEvaluationDao; + // @Value("${aws.s3.url.folder}") // private String s3Folder; @@ -224,18 +235,72 @@ public class DocumentDao { } else if (DocumentSourceTypeEnum.APPLICATION.getValue().equalsIgnoreCase(documentEntity.getSource())) { applicationId = documentEntity.getSourceId(); ApplicationEntity applicationEntity = applicationService.validateApplication(applicationId); + + List applicationFormEntity=applicationFormRepository.findByApplicationId(applicationId); + for (ApplicationFormEntity applicationForm:applicationFormEntity){ + FormEntity formEntity=applicationForm.getForm(); + List contentList = Utils.convertJsonStringToList(formEntity.getContent(), ContentResponseBean.class); + List applicationFormFieldEntityList=applicationFormFieldRepository.findByApplicationFormId(applicationForm.getId()); + for (ApplicationFormFieldEntity applicationFormFieldEntity:applicationFormFieldEntityList) { + contentList.forEach(contentResponseBean -> { + if (("fileupload".equals(contentResponseBean.getName()) || GepafinConstant.FILE_SELECT.equals(contentResponseBean.getName())) + && contentResponseBean.getId().equals(applicationFormFieldEntity.getFieldId())) { + String updatedValue = removeDocumentIdFromFieldValue(applicationFormFieldEntity.getFieldValue(), documentId); + applicationFormFieldEntity.setFieldValue(updatedValue); + applicationFormFieldRepository.save(applicationFormFieldEntity); + } + }); + } + } + callId = applicationEntity.getCall().getId(); log.info("Processing document of type APPLICATION. Resolved applicationId={}, callId={}", applicationId, callId); } else if(DocumentSourceTypeEnum.AMENDMENT.getValue().equalsIgnoreCase(documentEntity.getSource())){ amendmentId = documentEntity.getSourceId(); ApplicationEntity applicationEntity = applicationAmendmentRequestRepository.findApplicationByAmendmentId(amendmentId); + Optional applicationAmendmentRequestEntity=applicationAmendmentRequestRepository.findByIdAndIsDeletedFalse(amendmentId); + Map amendmentFormFieldMap = Utils + .convertJsonStringToList(applicationAmendmentRequestEntity.get().getFormFields(), AmendmentFormField.class) + .stream().collect(Collectors.toMap(AmendmentFormField::getFieldId, Function.identity())); + for (Map.Entry entry : amendmentFormFieldMap.entrySet()) { + AmendmentFormField amendmentFormField=entry.getValue(); + String updatedValue = removeDocumentIdFromFieldValue(amendmentFormField.getFieldValue(), documentId); + amendmentFormField.setFieldValue(updatedValue); + } + String amendmentDocs=applicationAmendmentRequestEntity.get().getAmendmentDocument(); + Map amendmentDocument=Utils.convertIntoJson(amendmentDocs); + String amendmentDocuments= (String) amendmentDocument.get("amendmentDocuments"); + if(amendmentDocuments!=null){ + String updatedValue = removeDocumentIdFromFieldValue(amendmentDocuments, documentId); + amendmentDocument.put("amendmentDocuments", updatedValue); + + // Step 4: Convert map back to JSON string + String updatedAmendmentDocs = Utils.convertMapIntoJsonString(amendmentDocument); // implement this if not available + + // Step 5: Set it back to entity + applicationAmendmentRequestEntity.get().setAmendmentDocument(updatedAmendmentDocs); + } + applicationAmendmentRequestEntity.get().setFormFields(Utils.convertListToJsonString(amendmentFormFieldMap.values().stream().toList())); + applicationAmendmentRequestRepository.save(applicationAmendmentRequestEntity.get()); + applicationId = applicationEntity.getId(); callId = applicationEntity.getCall().getId(); log.info("Processing document of type AMENDMENT. Resolved amendmentId={}, applicationId={}, callId={}", amendmentId, applicationId, callId); } else if(DocumentSourceTypeEnum.EVALUATION.getValue().equalsIgnoreCase(documentEntity.getSource())){ evaluationId = documentEntity.getSourceId(); ApplicationEntity applicationEntity = applicationEvaluationRepository.findApplicationByEvaluationId(evaluationId); + ApplicationEvaluationEntity entity=applicationEvaluationRepository.findByApplicationId(applicationEntity.getId()); + List allDocs = applicationEvaluationDao.prepareEvaluationDocumentBeanList(entity); + List updatedDocs=allDocs; + allDocs = allDocs.stream() + .filter(doc -> doc.getFileValue().equals(removeDocumentIdFromFieldValue(doc.getFileValue(), documentId))) + .collect(Collectors.toList()); + + + String updatedEvaluationDocJson = Utils.convertObjectToJson(allDocs); + entity.setEvaluationDocument(updatedEvaluationDocJson); + applicationEvaluationRepository.save(entity); applicationId = applicationEntity.getId(); callId = applicationEntity.getCall().getId(); log.info("Processing document of type EVALUATION. Resolved evaluationId={}, applicationId={}, callId={}", evaluationId, applicationId, callId); @@ -343,4 +408,19 @@ public class DocumentDao { throw new CustomValidationException(Status.VALIDATION_ERROR, Translator.toLocale(GepafinConstant.ERROR_MOVING_FILE_TO_DELETED_FOLDER)); } } + + public String removeDocumentIdFromFieldValue(String fieldValue, Long documentId) { + if (fieldValue == null || fieldValue.isBlank()) { + return fieldValue; + } + + List documentIdList = new ArrayList<>(Arrays.asList(fieldValue.split(","))); + documentIdList.replaceAll(String::trim); // Trim spaces for safety + + boolean removed = documentIdList.removeIf(id -> id.equals(String.valueOf(documentId))); + + // Return updated value only if modified, else return original + return removed ? String.join(",", documentIdList) : fieldValue; + } + } From 54b45733801cef6ab7ccfd1914b09a3b7727b30e Mon Sep 17 00:00:00 2001 From: rajesh Date: Wed, 23 Jul 2025 15:48:40 +0530 Subject: [PATCH 35/35] Done ticket GEPAFINBE-235 --- .../entities/ApplicationFormView.java | 5 + .../db/changelog/db.changelog-1.0.0.xml | 4 + ...pdate_application_form_view_23_07_2025.sql | 108 ++++++++++++++++++ 3 files changed, 117 insertions(+) create mode 100644 src/main/resources/db/dump/update_application_form_view_23_07_2025.sql diff --git a/src/main/java/net/gepafin/tendermanagement/entities/ApplicationFormView.java b/src/main/java/net/gepafin/tendermanagement/entities/ApplicationFormView.java index 0af565bf..e99d43ca 100644 --- a/src/main/java/net/gepafin/tendermanagement/entities/ApplicationFormView.java +++ b/src/main/java/net/gepafin/tendermanagement/entities/ApplicationFormView.java @@ -118,4 +118,9 @@ public class ApplicationFormView { @Column(name = "INSTRUCTOR_NAME") private String instructorName; + @Column(name = "SUBMISSION_DATE") + private LocalDate submissionDate; + + @Column(name = "SUBMISSION_TIME") + private LocalTime submissionTime; } 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 ef0f3447..c7f4e41e 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 @@ -3004,4 +3004,8 @@ + + + + diff --git a/src/main/resources/db/dump/update_application_form_view_23_07_2025.sql b/src/main/resources/db/dump/update_application_form_view_23_07_2025.sql new file mode 100644 index 00000000..eec055ec --- /dev/null +++ b/src/main/resources/db/dump/update_application_form_view_23_07_2025.sql @@ -0,0 +1,108 @@ + +DROP VIEW IF EXISTS gepafin_schema.application_form_view ; + +CREATE OR REPLACE VIEW gepafin_schema.application_form_view AS +SELECT app_data.id, + app_data.call_id, + app_data.application_form_id, + app_data.form_id, + app_data.application_id, + field_data.value ->> 'id'::text AS field_id, + COALESCE(( SELECT s.value ->> 'value'::text + FROM jsonb_array_elements(field_data.value -> 'settings'::text) s(value) + WHERE (s.value ->> 'name'::text) = 'label'::text + LIMIT 1), field_data.value ->> 'label'::text) AS field_label, + ( SELECT (s.value ->> 'value'::text)::boolean AS bool + FROM jsonb_array_elements(field_data.value -> 'settings'::text) s(value) + WHERE (s.value ->> 'name'::text) = 'reportEnable'::text + LIMIT 1) AS report_enable, + COALESCE(( SELECT s.value ->> 'value'::text + FROM jsonb_array_elements(field_data.value -> 'settings'::text) s(value) + WHERE (s.value ->> 'name'::text) = 'reportHeader'::text + LIMIT 1), field_data.value ->> 'reportHeader'::text) AS report_header, + field_data.value ->> 'name'::text AS field_type, + CASE + WHEN (field_data.value ->> 'name'::text) = 'fileupload'::text THEN to_jsonb(( SELECT string_agg(d.file_name::text, ', '::text) AS string_agg + FROM unnest(string_to_array(app_data.field_value, ','::text)) file_ids(file_id) + JOIN gepafin_schema.document d ON d.id::text = file_ids.file_id + WHERE d.is_deleted = false)) + WHEN (field_data.value ->> 'name'::text) = ANY (ARRAY['checkboxes'::text, 'select'::text, 'radio'::text]) THEN + CASE + WHEN app_data.field_value ~~ '[%'::text THEN to_jsonb(( SELECT string_agg(opt.value ->> 'label'::text, ', '::text) AS string_agg + FROM jsonb_array_elements_text(app_data.field_value::jsonb) selected_id(value) + CROSS JOIN LATERAL ( SELECT s.value + FROM jsonb_array_elements(field_data.value -> 'settings'::text) s(value) + WHERE (s.value ->> 'name'::text) = 'options'::text) options_setting, + LATERAL jsonb_array_elements(options_setting.value -> 'value'::text) opt(value) + WHERE (opt.value ->> 'name'::text) = selected_id.value)) + ELSE to_jsonb(( SELECT opt.value ->> 'label'::text + FROM ( SELECT s.value + FROM jsonb_array_elements(field_data.value -> 'settings'::text) s(value) + WHERE (s.value ->> 'name'::text) = 'options'::text) options_setting, + LATERAL jsonb_array_elements(options_setting.value -> 'value'::text) opt(value) + WHERE (opt.value ->> 'name'::text) = app_data.field_value + LIMIT 1)) + END + ELSE to_jsonb(app_data.field_value) + END AS field_value, + app_data.status, + app_data.amount_requested, + app_data.amount_accepted, + app_data.is_deleted, + app_data.hub_id, + app_data.user_id, + app_data.evaluation_version, + app_data.company_id, + c.company_name, + c.vat_number AS company_vat_number, + c.codice_ateco, + c.codice_fiscale AS company_codice_fiscale, + p.protocol_number, + DATE(app_data.submission_date) AS submission_date, + TO_CHAR(app_data.submission_date, 'HH24:MI:SS') AS submission_time, + b.codice_fiscale AS user_codice_fiscale, + COALESCE(NULLIF(TRIM(BOTH FROM concat(COALESCE(u.first_name, ''::character varying), ' ', COALESCE(u.last_name, ''::character varying))), ''::text), ''::text) AS user_name, + COALESCE( NULLIF(TRIM(BOTH FROM CONCAT(COALESCE(aauser.first_name, ''), ' ', COALESCE(aauser.last_name, ''))),''),'NA') AS instructor_name, + uwc.is_legal_representant AS legal_representative, + cl.name AS call_title, + cl.end_date AS call_end_date, + cl.end_time AS call_end_time, + cl.start_date AS call_start_date, + cl.start_time AS call_start_time + FROM ( SELECT a.id AS application_id, + a.call_id, + a.protocol_number, + af.id AS application_form_id, + af.form_id, + aff.id, + aff.field_value, + a.status, + a.amount_requested, + a.amount_accepted, + a.is_deleted, + a.hub_id, + a.user_id, + a.evaluation_version, + a.created_date, + a.company_id, + a.submission_date, + aff.field_id, + f.content + FROM gepafin_schema.application a + JOIN gepafin_schema.application_form af ON af.application_id = a.id + JOIN gepafin_schema.application_form_field aff ON aff.application_form_id = af.id + JOIN gepafin_schema.form f ON f.id = af.form_id + WHERE a.is_deleted = false) app_data + CROSS JOIN LATERAL ( SELECT jsonb_array_elements.value + FROM jsonb_array_elements(app_data.content::jsonb) jsonb_array_elements(value) + WHERE (jsonb_array_elements.value ->> 'id'::text) = app_data.field_id::text) field_data(value) + LEFT JOIN gepafin_schema.call cl ON app_data.call_id = cl.id + LEFT JOIN gepafin_schema.company c ON app_data.company_id = c.id + LEFT JOIN gepafin_schema.protocol p ON app_data.protocol_number = p.id + LEFT JOIN gepafin_schema.gepafin_user u ON app_data.user_id = u.id + LEFT JOIN gepafin_schema.user_with_company uwc ON app_data.user_id = uwc.user_id AND app_data.company_id = uwc.company_id AND uwc.is_deleted = false + LEFT JOIN gepafin_schema.beneficiary b ON u.beneficiary_id = b.id + LEFT JOIN gepafin_schema.assigned_applications aa ON app_data.application_id = aa.application_id + LEFT JOIN gepafin_schema.gepafin_user aauser ON aa.user_id = aauser.id + WHERE app_data.id IS NOT NULL AND (app_data.status::text <> ALL (ARRAY['DRAFT'::character varying::text, 'AWAITING'::character varying::text, 'READY'::character varying::text])) + ORDER BY app_data.id, (field_data.value ->> 'id'::text); \ No newline at end of file