From e3b4b04b80825f7901f03924ecde57065971965d Mon Sep 17 00:00:00 2001 From: rajesh Date: Fri, 13 Mar 2026 20:26:04 +0530 Subject: [PATCH] Handled rejected email rollback cases --- .../dao/ApplicationAmendmentRequestDao.java | 31 ++++- .../tendermanagement/dao/PecMailDao.java | 120 ++++++++++++++++++ .../ApplicationAmendmentRequestEnum.java | 4 +- .../VersionHistoryRepository.java | 2 + 4 files changed, 152 insertions(+), 5 deletions(-) diff --git a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationAmendmentRequestDao.java b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationAmendmentRequestDao.java index 45b867b2..0c12eb8e 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationAmendmentRequestDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationAmendmentRequestDao.java @@ -786,6 +786,10 @@ public class ApplicationAmendmentRequestDao { 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.TRUE.equals(isBeneficiary) && existingApplicationAmendment.getStatus().equals(ApplicationAmendmentRequestEnum.DRAFT.getValue())){ + log.warn("Permission denied: Beneficiary tried to update amendment ID {} with status DRAFT (only instructor can update)", 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)); @@ -1278,16 +1282,35 @@ public class ApplicationAmendmentRequestDao { log.info("Updating application amendment with status: {}", id); 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))) { + + if (ApplicationAmendmentRequestEnum.AWAITING.equals(statusTypeEnum)) { + // Only instructor can set status to AWAITING (e.g. after finishing edits post email rejection); beneficiary must not be allowed + if (Boolean.TRUE.equals(validator.checkIsBeneficiary()) || Boolean.TRUE.equals(validator.checkIsConfidi())) { + log.warn("Permission denied: Beneficiary/Confidi tried to set amendment ID {} status to AWAITING", id); + throw new CustomValidationException(Status.VALIDATION_ERROR, Translator.toLocale(GepafinConstant.PERMISSION_DENIED)); + } + validator.validatePreInstructor(request, existingApplicationAmendment.getApplicationEvaluationEntity().getUserId()); + // Allow transition to AWAITING only from DRAFT (e.g. after instructor finished edits post email rejection) + if (!ApplicationAmendmentRequestEnum.DRAFT.getValue().equals(existingApplicationAmendment.getStatus())) { + log.warn("Invalid status transition: amendment ID {} is not in DRAFT (current: {})", id, existingApplicationAmendment.getStatus()); + throw new CustomValidationException(Status.VALIDATION_ERROR, Translator.toLocale(GepafinConstant.APPLICATION_AMENDMENT_APPROPIATE_STATUS)); + } + log.info("Updating amendment ID {} status from DRAFT to AWAITING", id); + existingApplicationAmendment.setStatus(ApplicationAmendmentRequestEnum.AWAITING.getValue()); + existingApplicationAmendment.setUpdatedDate(DateTimeUtil.DateServerToUTC(LocalDateTime.now())); + applicationAmendmentRequestRepository.save(existingApplicationAmendment); + loggingUtil.addVersionHistory(VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldApplicationAmendmentEntity).newData(existingApplicationAmendment).build()); + // Send the same mail as when (normal) amendment was created; only normal amendments can be in DRAFT (special amendment emails are sent at creation, not held) + emailNotificationDao.sendMailToNotifyBeneficiaryRegardingNewAmendment(existingApplicationAmendment); + } else 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); - - /** This code is responsible for adding a version history log for the "Update Application Amendment" operation. **/ loggingUtil.addVersionHistory(VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldApplicationAmendmentEntity).newData(existingApplicationAmendment).build()); } - ApplicationAmendmentRequestResponse response = convertEntityToResponse(existingApplicationAmendment,false); + + ApplicationAmendmentRequestResponse response = convertEntityToResponse(existingApplicationAmendment, false); log.info("Amendment status updated successfully: {}", response); return response; } diff --git a/src/main/java/net/gepafin/tendermanagement/dao/PecMailDao.java b/src/main/java/net/gepafin/tendermanagement/dao/PecMailDao.java index fcaecbcc..d25e1507 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/PecMailDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/PecMailDao.java @@ -6,13 +6,22 @@ import lombok.extern.log4j.Log4j2; import net.gepafin.tendermanagement.config.Translator; import net.gepafin.tendermanagement.constants.GepafinConstant; import net.gepafin.tendermanagement.entities.*; +import net.gepafin.tendermanagement.enums.ApplicationEvaluationStatusTypeEnum; +import net.gepafin.tendermanagement.enums.AssignedApplicationEnum; +import net.gepafin.tendermanagement.enums.EmailScenarioTypeEnum; import net.gepafin.tendermanagement.enums.EmailServiceTypeEnum; import net.gepafin.tendermanagement.enums.StatusTypeEnum; import net.gepafin.tendermanagement.model.response.EmailLogResponse; import net.gepafin.tendermanagement.model.response.PecEmailLogResponse; import net.gepafin.tendermanagement.model.response.PecMailResponse; +import net.gepafin.tendermanagement.enums.ApplicationAmendmentRequestEnum; +import net.gepafin.tendermanagement.repositories.ApplicationAmendmentRequestRepository; +import net.gepafin.tendermanagement.repositories.ApplicationEvaluationRepository; +import net.gepafin.tendermanagement.repositories.ApplicationRepository; +import net.gepafin.tendermanagement.repositories.AssignedApplicationsRepository; import net.gepafin.tendermanagement.repositories.EmailLogRepository; import net.gepafin.tendermanagement.repositories.UserActionsRepository; +import net.gepafin.tendermanagement.repositories.VersionHistoryRepository; import net.gepafin.tendermanagement.service.ApplicationService; import net.gepafin.tendermanagement.service.CallService; import net.gepafin.tendermanagement.util.DateTimeUtil; @@ -28,6 +37,8 @@ import org.springframework.stereotype.Component; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; +import java.util.Map; +import java.util.Optional; @Component @Log4j2 @@ -51,6 +62,22 @@ public class PecMailDao { @Autowired private ApplicationService applicationService; + @Autowired + private ApplicationRepository applicationRepository; + + @Autowired + private ApplicationEvaluationRepository applicationEvaluationRepository; + + @Autowired + private AssignedApplicationsRepository assignedApplicationsRepository; + + @Autowired + private VersionHistoryRepository versionHistoryRepository; + + @Autowired + private ApplicationAmendmentRequestRepository applicationAmendmentRequestRepository; + + private static final String TABLE_APPLICATION = "APPLICATION"; public List sendPecMail(HttpServletRequest request, List userActionIds) { @@ -86,11 +113,104 @@ public class PecMailDao { } emailLogRepository.saveAll(emailLogs); EmailLogEntity firstLog = emailLogs.get(0); + + rollbackDomainChangesForRejectedEmail(firstLog, userActionId); + ApplicationEntity applicationEntity = applicationService.validateApplication(firstLog.getApplicationId()); String callName = applicationEntity.getCall().getName(); return createPecMailResponse(firstLog.getUserAction().getId(), firstLog, callName); } + /** + * Rolls back domain changes using version history (audit) for the user action that triggered the email. + * Restores application, application_evaluation, and assigned_applications from oldData in version history. + */ + private void rollbackDomainChangesForRejectedEmail(EmailLogEntity firstLog, Long userActionId) { + if (firstLog == null || firstLog.getApplicationId() == null || firstLog.getEmailType() == null || userActionId == null) { + return; + } + + String scenario = firstLog.getEmailType(); + Long applicationId = firstLog.getApplicationId(); + + // Only normal amendment emails are rejectable in PEC flow; set amendment to DRAFT so only instructor can update + if (EmailScenarioTypeEnum.APPLICATION_AMENDMENT_REQUESTED.getValue().equals(scenario)) { + Long amendmentId = firstLog.getAmendmentId(); + if (amendmentId != null) { + applicationAmendmentRequestRepository.findByIdAndIsDeletedFalse(amendmentId).ifPresent(amendment -> { + amendment.setStatus(ApplicationAmendmentRequestEnum.DRAFT.getValue()); + applicationAmendmentRequestRepository.save(amendment); + log.info("Set amendment id={} to DRAFT after email rejected (userActionId={})", amendmentId, userActionId); + }); + } + return; + } + + // Only rollback for the three evaluation-outcome email scenarios + if (!EmailScenarioTypeEnum.APPLICATION_TECHNICAL_EVALUATION_REJECTED.getValue().equals(scenario) + && !EmailScenarioTypeEnum.APPLICATION_REJECTED.getValue().equals(scenario) + && !EmailScenarioTypeEnum.APPLICATION_ADMISSIBLE.getValue().equals(scenario)) { + return; + } + + List appHistory = versionHistoryRepository.findVersionHistoryByUserActionIdAndTableName(userActionId, TABLE_APPLICATION); + Optional applicationVersion = appHistory.stream() + .filter(v -> applicationId.equals(v.getRecordId()) && v.getOldData() != null && !v.getOldData().isEmpty()) + .findFirst(); + + ApplicationEntity applicationEntity = applicationService.validateApplication(applicationId); + if (applicationVersion.isPresent()) { + Map oldDataMap = Utils.convertJsonStringToMap(applicationVersion.get().getOldData()); + if (oldDataMap != null) { + String previousStatus = Utils.extractString(oldDataMap, "status"); + if (previousStatus != null) { + applicationEntity.setStatus(previousStatus); + } + Object dateRejectedObj = oldDataMap.get("dateRejected"); + applicationEntity.setDateRejected(parseLocalDateTimeFromAudit(dateRejectedObj)); + applicationRepository.save(applicationEntity); + log.info("Rolled back application id={} from version history (userActionId={})", applicationId, userActionId); + } + } else { + log.warn("No APPLICATION version history with oldData found for userActionId={}, applicationId={}; skipping application rollback", userActionId, applicationId); + } + + // Only set application_evaluation and assigned_applications to OPEN for REJECTED and TECHNICAL_EVALUATION_REJECTED (not for ADMISSIBLE) + boolean reopenEvaluationAndAssigned = EmailScenarioTypeEnum.APPLICATION_TECHNICAL_EVALUATION_REJECTED.getValue().equals(scenario) + || EmailScenarioTypeEnum.APPLICATION_REJECTED.getValue().equals(scenario); + if (!reopenEvaluationAndAssigned) { + return; + } + + applicationEvaluationRepository.findByApplicationIdAndIsDeletedFalse(applicationId).ifPresent(evaluation -> { + evaluation.setStatus(ApplicationEvaluationStatusTypeEnum.OPEN.getValue()); + evaluation.setClosingDate(null); + evaluation.setActiveDays(null); + applicationEvaluationRepository.save(evaluation); + log.info("Set application_evaluation id={} to OPEN (userActionId={})", evaluation.getId(), userActionId); + }); + + assignedApplicationsRepository.findByApplicationIdAndIsDeletedFalse(applicationId).ifPresent(assigned -> { + assigned.setStatus(AssignedApplicationEnum.OPEN.getValue()); + assignedApplicationsRepository.save(assigned); + log.info("Set assigned_applications id={} to OPEN (userActionId={})", assigned.getId(), userActionId); + }); + } + + private static LocalDateTime parseLocalDateTimeFromAudit(Object value) { + if (value == null) { + return null; + } + if (value instanceof String str && !str.isEmpty()) { + try { + return DateTimeUtil.parseStringToLocalDateTime(str); + } catch (Exception e) { + return null; + } + } + return null; + } + private List getEmailLogEntities(HttpServletRequest request, Long userActionId) { UserActionEntity userActionEntity = userActionsRepository.findUserActionByIdAndIsDeletedFalse(userActionId); if (userActionEntity == null) { diff --git a/src/main/java/net/gepafin/tendermanagement/enums/ApplicationAmendmentRequestEnum.java b/src/main/java/net/gepafin/tendermanagement/enums/ApplicationAmendmentRequestEnum.java index d25ef71d..eeee1100 100644 --- a/src/main/java/net/gepafin/tendermanagement/enums/ApplicationAmendmentRequestEnum.java +++ b/src/main/java/net/gepafin/tendermanagement/enums/ApplicationAmendmentRequestEnum.java @@ -6,7 +6,9 @@ public enum ApplicationAmendmentRequestEnum { AWAITING("AWAITING"), RESPONSE_RECEIVED("RESPONSE_RECEIVED"), CLOSE("CLOSE"), - EXPIRED("EXPIRED"); + EXPIRED("EXPIRED"), + /** Amendment PEC email was rejected; only instructor can update until status is set back to AWAITING. */ + DRAFT("DRAFT"); private String value; diff --git a/src/main/java/net/gepafin/tendermanagement/repositories/VersionHistoryRepository.java b/src/main/java/net/gepafin/tendermanagement/repositories/VersionHistoryRepository.java index 853bb945..782a2cc6 100644 --- a/src/main/java/net/gepafin/tendermanagement/repositories/VersionHistoryRepository.java +++ b/src/main/java/net/gepafin/tendermanagement/repositories/VersionHistoryRepository.java @@ -13,4 +13,6 @@ public interface VersionHistoryRepository extends JpaRepository findVersionHistoryByUserActionIdAndUserIdNull(Long id); List findVersionHistoryByUserActionId(Long id); + + List findVersionHistoryByUserActionIdAndTableName(Long userActionId, String tableName); }