From 3f6e11e7c4c58f13cbcecc8cdc6ece2dc57e869c Mon Sep 17 00:00:00 2001 From: rajesh Date: Fri, 23 May 2025 15:17:07 +0530 Subject: [PATCH 01/10] 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/10] 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/10] 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/10] 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 578fd8e6ee2f1a5f2f6429d1023b618b0f783f65 Mon Sep 17 00:00:00 2001 From: rajesh Date: Wed, 4 Jun 2025 13:03:04 +0530 Subject: [PATCH 05/10] 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 cdc98d2918d2a26626a0a77be72664a96f10462a Mon Sep 17 00:00:00 2001 From: rajesh Date: Thu, 5 Jun 2025 15:20:03 +0530 Subject: [PATCH 06/10] 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 f380aee9199b04f9d58be116f2eb1869f23ffe39 Mon Sep 17 00:00:00 2001 From: rajesh Date: Thu, 5 Jun 2025 16:33:54 +0530 Subject: [PATCH 07/10] 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 43e8df6cca87f27913229ac0f6752ed1ec7e7d7a Mon Sep 17 00:00:00 2001 From: rajesh Date: Thu, 5 Jun 2025 19:40:31 +0530 Subject: [PATCH 08/10] 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 a733411b5dd1fa8a371d02612998ad457d10e7ae Mon Sep 17 00:00:00 2001 From: rajesh Date: Fri, 6 Jun 2025 14:28:21 +0530 Subject: [PATCH 09/10] 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 e01a00b13df15e9d045e7dfbadc4372ce815fd29 Mon Sep 17 00:00:00 2001 From: rajesh Date: Mon, 16 Jun 2025 15:35:29 +0530 Subject: [PATCH 10/10] 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; + } +}