package net.gepafin.tendermanagement.dao; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.math.BigDecimal; import java.sql.Timestamp; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.OffsetDateTime; import java.time.format.DateTimeFormatter; import java.util.*; import java.util.stream.Collectors; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; import jakarta.persistence.criteria.CriteriaBuilder; import jakarta.persistence.criteria.Expression; import jakarta.persistence.criteria.Predicate; import jakarta.persistence.criteria.Root; import jakarta.persistence.criteria.*; import jakarta.servlet.http.HttpServletRequest; import net.gepafin.tendermanagement.entities.*; import net.gepafin.tendermanagement.enums.*; import net.gepafin.tendermanagement.model.request.*; import net.gepafin.tendermanagement.model.response.*; import net.gepafin.tendermanagement.model.util.SortBy; import net.gepafin.tendermanagement.repositories.*; import net.gepafin.tendermanagement.service.*; import net.gepafin.tendermanagement.util.DateTimeUtil; import net.gepafin.tendermanagement.util.LoggingUtil; import net.gepafin.tendermanagement.util.Utils; import net.gepafin.tendermanagement.util.Validator; import org.h2.util.IOUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.jpa.domain.Specification; import org.springframework.stereotype.Component; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; import net.gepafin.tendermanagement.config.Translator; import net.gepafin.tendermanagement.constants.GepafinConstant; import net.gepafin.tendermanagement.entities.LookUpDataEntity.LookUpDataTypeEnum; import net.gepafin.tendermanagement.service.impl.CallValidatorServiceImpl; import net.gepafin.tendermanagement.web.rest.api.errors.CustomValidationException; import net.gepafin.tendermanagement.web.rest.api.errors.ResourceNotFoundException; 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; @Component public class CallDao { @Autowired private CallRepository callRepository; @Autowired private DocumentRepository documentRepository; @Autowired private EvaluationCriteriaRepository evaluationCriteriaRepository; @Autowired private FaqRepository faqRepository; @Autowired private RegionRepository regionRepository; @Autowired private LookUpDataService lookUpDataService; @Autowired private CallTargetAudienceChecklistRepository callTargetAudienceChecklistRepository; @Autowired private FaqService faqService; @Autowired private FlowDao flowDao; @Autowired private FormDao formDao; // @Value("${aws.s3.url.folder}") // private String s3Folder; @Autowired private AmazonS3Service amazonS3Service; @Autowired private CriteriaFormFieldRepository criteriaFormFieldRepository; @Autowired private S3PathConfig s3PathConfig; @Autowired private BeneficiaryPreferredCallRepository beneficiaryPreferredCallRepository; @Autowired private Validator validator; @Autowired private CompanyService companyService; @Autowired private LoggingUtil loggingUtil; @Autowired private HttpServletRequest request; @Autowired private NotificationDao notificationDao; @Autowired private BeneficiaryRepository beneficiaryRepository; @Autowired private NotificationTypeRepository notificationTypeRepository; @Autowired private EvaluationFormDao evalualtionFormDao; @Autowired 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); updateFaq(createCallRequest.getFaq(), callEntity, userEntity,LookUpDataTypeEnum.FAQ); updateLookUpData(callEntity, createCallRequest.getAimedTo(), LookUpDataTypeEnum.AIMED_TO); 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)); } try (ByteArrayOutputStream zipOutputStream = new ByteArrayOutputStream(); 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,0L,0L); try (InputStream fileInputStream = amazonS3Service.getFile(s3Folder, document.getFilePath())) { String fileName = Utils.extractFileName(document.getFilePath()); ZipEntry zipEntry = new ZipEntry(fileName); zos.putNextEntry(zipEntry); 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); } } public CallEntity convertToCallEntity(CreateCallRequestStep1 createCallRequest, UserEntity userEntity) { CallEntity callEntity = new CallEntity(); // validateCallEntity(createCallRequest); RegionEntity region = regionRepository.findById(createCallRequest.getRegionId()) .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()); callEntity.setDescriptionLong(createCallRequest.getDescriptionLong()); List dates = createCallRequest.getDates(); if(dates!=null) { if(dates.size()>1) { callEntity.setStartDate(dates.get(0)); callEntity.setEndDate(dates.get(1)); } } callEntity.setStatus(CallStatusEnum.DRAFT.getValue()); callEntity.setEvaluationVersion(createCallRequest.getEvaluationVersion().getValue()); callEntity.setAmountMax(createCallRequest.getAmountMax()); callEntity.setAmount(createCallRequest.getAmount()); callEntity.setConfidi(false); if (createCallRequest.getConfidi() != null) { callEntity.setConfidi(createCallRequest.getConfidi()); } 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()); if(createCallRequest.getEmail()!=null && Boolean.FALSE.equals(Utils.isValidEmail(createCallRequest.getEmail()))){ throw new CustomValidationException(Status.VALIDATION_ERROR,Translator.toLocale(GepafinConstant.VALIDATION_EMAIL,createCallRequest.getEmail())); } callEntity.setEmail(createCallRequest.getEmail()); callEntity.setPhoneNumber(createCallRequest.getPhoneNumber()); callEntity.setStartTime(DateTimeUtil.parseTime(createCallRequest.getStartTime())); callEntity.setEndTime(DateTimeUtil.parseTime(createCallRequest.getEndTime())); callEntity.setHub(userEntity.getHub()); callEntity.setNumberOfCheck(createCallRequest.getNumberOfCheck()); callEntity.setAppointmentTemplateId(createCallRequest.getAppointmentTemplateId()); callEntity.setAllowMultipleApplications(false); if (createCallRequest.getAllowMultipleApplications() != null) { callEntity.setAllowMultipleApplications(createCallRequest.getAllowMultipleApplications()); } callEntity = callRepository.save(callEntity); log.info("CallEntity saved with ID: {} for call name: '{}'", callEntity.getId(), callEntity.getName()); /** 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()); return callEntity; } public List convertToEvaluationCriteriaEntities( List criteriaReqList, CallEntity callEntity, LookUpDataTypeEnum type) { if (criteriaReqList == null) { return null; } List existingCriteria = evaluationCriteriaRepository.findByCallIdAndLookupDataTypeAndIsDeletedFalse(callEntity.getId(), type.getValue()); List incomingIds = criteriaReqList.stream().map(EvaluationCriteriaReq::getId) .filter(id -> id != null && id > 0).collect(Collectors.toList()); existingCriteria.stream().filter(criteria -> !incomingIds.contains(criteria.getId())).forEach(this::softDeleteEvaluationCriteria); List evaluationCriteriaEntities = criteriaReqList.stream() .map(req -> convertToEvaluationCriteriaEntity(req, callEntity, type)).collect(Collectors.toList()); return evaluationCriteriaEntities; } 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()); List list = criteriaFormFieldRepository .findByEvaluationCriteriaIdAndIsDeletedFalse(evaluationCriteriaEntity.getId()); if(Boolean.FALSE.equals(CollectionUtils.isEmpty(list))) { list.stream().peek(data->{ CriteriaFormFieldEntity oldCriteriaFormFieldEntity = Utils.getClonedEntityForData(data); data.setIsDeleted(Boolean.TRUE); /** 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); log.info("Soft deleted all linked CriteriaFormFieldEntity records for EvaluationCriteriaEntity ID: {}", evaluationCriteriaEntity.getId()); } } private EvaluationCriteriaEntity convertToEvaluationCriteriaEntity(EvaluationCriteriaReq criteriaReq, CallEntity callEntity, LookUpDataTypeEnum type) { EvaluationCriteriaEntity criteriaEntity = null; EvaluationCriteriaEntity oldCriteriaEntity = null; VersionActionTypeEnum actionType = VersionActionTypeEnum.INSERT; LookUpDataEntity lookupDataEntity = lookUpDataService.getOrCreateLookUpDataEntity(criteriaReq, type); if (criteriaReq.getId() != null && criteriaReq.getId() > 0) { criteriaEntity = evaluationCriteriaRepository.findById(criteriaReq.getId()) .orElseThrow(() -> new ResourceNotFoundException(Status.NOT_FOUND, Translator.toLocale(GepafinConstant.EVALUATION_CRITERIA_NOT_FOUND))); oldCriteriaEntity = Utils.getClonedEntityForData(oldCriteriaEntity); actionType = VersionActionTypeEnum.UPDATE; } else { criteriaEntity = new EvaluationCriteriaEntity(); criteriaEntity.setCall(callEntity); criteriaEntity.setLookupData(lookupDataEntity); criteriaEntity.setScore(BigDecimal.ZERO); criteriaEntity.setIsDeleted(false); actionType = VersionActionTypeEnum.INSERT; } setIfUpdated(criteriaEntity::getScore, criteriaEntity::setScore, criteriaReq.getScore()); if (Boolean.FALSE.equals(criteriaEntity.getLookupData().getId().equals(lookupDataEntity.getId()))) { criteriaEntity.setLookupData(lookupDataEntity); } criteriaEntity = evaluationCriteriaRepository.save(criteriaEntity) ; /** This code is responsible for adding a version history log for the "create or update evaluation criteria" operation **/ loggingUtil.addVersionHistory(VersionHistoryRequest.builder().request(request).actionType(actionType).oldData(oldCriteriaEntity).newData(criteriaEntity).build()); return criteriaEntity; } public List convertToDocumentEntities(List documentReqList, Long sourceId, DocumentTypeEnum documentType) { if (documentReqList == null) { return null; } List existingDocuments = documentRepository .findBySourceIdAndSourceAndTypeAndIsDeletedFalse(sourceId, DocumentSourceTypeEnum.CALL.getValue(), documentType.getValue()); List incomingIds = documentReqList.stream().map(DocumentReq::getId).filter(id -> id != null && id > 0) .collect(Collectors.toList()); existingDocuments.stream().filter(document -> !incomingIds.contains(document.getId())) .forEach(this::softDeleteDocument); List documentEntities = documentReqList.stream() .map(req -> convertToDocumentEntity(req, sourceId)).collect(Collectors.toList()); // documentRepository.saveAll(documentEntities); return documentEntities; } private void softDeleteDocument(DocumentEntity documentEntity) { DocumentEntity oldDocumentEntity = Utils.getClonedEntityForData(documentEntity); documentEntity.setIsDeleted(true); documentEntity = documentRepository.save(documentEntity); /** This code is responsible for adding a version history log for the "soft delete for document" operation **/ loggingUtil.addVersionHistory(VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.SOFT_DELETE).oldData(oldDocumentEntity).newData(documentEntity).build()); } private DocumentEntity convertToDocumentEntity(DocumentReq documentReq,Long sourceId) { validateDocumentEntity(documentReq.getId()); DocumentEntity documentEntity = documentRepository.findByIdAndSourceIdAndSourceAndIsDeletedFalse(documentReq.getId(),sourceId, DocumentSourceTypeEnum.CALL.getValue()) .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; } public List updateFaq(List faqReqList, CallEntity callEntity, UserEntity userEntity, LookUpDataTypeEnum type) { if (faqReqList == null) { return null; } List existingFaqEntities = faqRepository.findByCallIdAndIsDeletedFalse(callEntity.getId()); List incomingIds = faqReqList.stream() .map(FaqReq::getId) .filter(id -> id != null && id > 0) .collect(Collectors.toList()); existingFaqEntities.stream() .filter(entity -> !incomingIds.contains(entity.getId())) .forEach(this::softDeleteFaq); List faqEntities = faqReqList.stream() .map(req -> faqService.createOrUpdateFaqEntity(req, callEntity, userEntity, type)) .collect(Collectors.toList()); return faqEntities; } 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)); } } public void validateEvaluationCriteriaEntity(String name) { if (!StringUtils.hasText(name)) { throw new CustomValidationException(Status.VALIDATION_ERROR, Translator.toLocale(GepafinConstant.NAME_NOT_EMPTY_MSG)); } } public CallResponse convertToCallResponseBean(CallEntity callEntity) { CallResponse createCallResponseBean = new CallResponse(); createCallResponseBean.setId(callEntity.getId()); createCallResponseBean.setName(callEntity.getName()); List dates = new ArrayList<>(); dates.add(callEntity.getStartDate()); dates.add(callEntity.getEndDate()); createCallResponseBean.setDates(dates); createCallResponseBean.setDescriptionShort(callEntity.getDescriptionShort()); createCallResponseBean.setDescriptionLong(callEntity.getDescriptionLong()); createCallResponseBean.setStatus(CallStatusEnum.valueOf(callEntity.getStatus())); createCallResponseBean.setEvaluationVersion(EvaluationVersionEnum.valueOf(callEntity.getEvaluationVersion())); createCallResponseBean.setRegionId(callEntity.getRegion().getId()); createCallResponseBean.setAmount(callEntity.getAmount()); createCallResponseBean.setAmountMax(callEntity.getAmountMax()); createCallResponseBean.setContactInfo(callEntity.getContactInfo()); createCallResponseBean.setSubmissionMethod(callEntity.getSubmissionMethod()); createCallResponseBean.setThreshold(callEntity.getThreshold()); createCallResponseBean.setDocumentationRequested(callEntity.getDocumentationRequested()); createCallResponseBean.setPriorityArea(callEntity.getPriorityArea()); createCallResponseBean.setConfidi(callEntity.getConfidi()); createCallResponseBean.setAllowMultipleApplications(callEntity.getAllowMultipleApplications()); createCallResponseBean.setAmountMin(callEntity.getAmountMin()); createCallResponseBean.setPhoneNumber(callEntity.getPhoneNumber()); createCallResponseBean.setEndTime(callEntity.getEndTime()); createCallResponseBean.setStartTime(callEntity.getStartTime()); createCallResponseBean.setEmail(callEntity.getEmail()); createCallResponseBean.setCreatedDate(callEntity.getCreatedDate()); createCallResponseBean.setUpdatedDate(callEntity.getUpdatedDate()); return createCallResponseBean; } public EvaluationCriteriaResponseBean convertToEvaluationCriteriaResponseBean(EvaluationCriteriaEntity entity) { EvaluationCriteriaResponseBean responseBean = new EvaluationCriteriaResponseBean(); responseBean.setId(entity.getId()); responseBean.setLookUpDataId(entity.getLookupData().getId()); responseBean.setTitle(entity.getLookupData().getTitle()); responseBean.setValue(entity.getLookupData().getValue()); responseBean.setResponse(entity.getLookupData().getResponse()); responseBean.setScore(entity.getScore()); responseBean.setCreatedDate(entity.getCreatedDate()); responseBean.setUpdatedDate(entity.getUpdatedDate()); return responseBean; } public DocumentResponseBean convertToDocumentResponseBean(DocumentEntity entity) { DocumentResponseBean responseBean = new DocumentResponseBean(); responseBean.setId(entity.getId()); responseBean.setName(entity.getFileName()); responseBean.setType(DocumentTypeEnum.valueOf(entity.getType())); responseBean.setSource(DocumentSourceTypeEnum.valueOf(entity.getSource())); responseBean.setSourceId(entity.getSourceId()); responseBean.setFilePath(entity.getFilePath()); responseBean.setCreatedDate(entity.getCreatedDate()); responseBean.setUpdatedDate(entity.getUpdatedDate()); return responseBean; } public CallResponse assembleCreateCallResponseBean(CallEntity callEntity, List evaluationCriteriaEntities, List documentEntities, List images) { CallResponse callResponseBean = convertToCallResponseBean(callEntity); List evaluationCriteriaResponseBeans = evaluationCriteriaEntities.stream() .map(this::convertToEvaluationCriteriaResponseBean).collect(Collectors.toList()); List documentResponseBeans = documentEntities.stream() .map(this::convertToDocumentResponseBean).collect(Collectors.toList()); List imagesResponseBean = images.stream().map(this::convertToDocumentResponseBean) .collect(Collectors.toList()); CallResponse createCallResponseBean = callResponseBean; createCallResponseBean.setCriteria(evaluationCriteriaResponseBeans); createCallResponseBean.setDocs(documentResponseBeans); createCallResponseBean.setImages(imagesResponseBean); return createCallResponseBean; } // public List convertLookUpDataEntities(List lookUpData, CallEntity callEntity, // LookUpDataEntity.LookUpDataTypeEnum type) { // if(lookUpData == null) { // return null; // } // List lookUpDataEntities = lookUpData.stream() // .map(req -> lookUpDataService.getOrCreateLookUpDataEntity(req, type)).collect(Collectors.toList()); // // return createCallTargetAudienceCheckList(callEntity, lookUpDataEntities); // } // private List createCallTargetAudienceCheckList(CallEntity callEntity, // List lookUpDataEntities) { // List lookUpDataResponses = new ArrayList<>(); // for (LookUpDataEntity lookUpDataEntity : lookUpDataEntities) { // CallTargetAudienceChecklistEntity callTargetAudienceChecklistEntity = new CallTargetAudienceChecklistEntity(); // callTargetAudienceChecklistEntity.setIsValidated(false); // callTargetAudienceChecklistEntity.setLookupData(lookUpDataEntity); // callTargetAudienceChecklistEntity.setCall(callEntity); // callTargetAudienceChecklistEntity.setIsDeleted(false); // callTargetAudienceChecklistEntity = callTargetAudienceChecklistRepository // .save(callTargetAudienceChecklistEntity); // versionHistoryRequest.setOldData(null); // versionHistoryRequest.setNewData(callTargetAudienceChecklistEntity); // versionHistoryRequest.setActionType(VersionActionTypeEnum.INSERT); // versionHistoryRequest.setRequest(request); // loggingUtil.addVersionHistory(versionHistoryRequest); // lookUpDataResponses.add(convertToLookUpDataResponseBean(callTargetAudienceChecklistEntity)); // } // return lookUpDataResponses; // } public LookUpDataResponse convertToLookUpDataResponseBean( CallTargetAudienceChecklistEntity callTargetAudienceChecklistEntity) { LookUpDataResponse lookUpDataResponse = new LookUpDataResponse(); LookUpDataEntity lookUpDataEntity = callTargetAudienceChecklistEntity.getLookupData(); lookUpDataResponse.setId(callTargetAudienceChecklistEntity.getId()); lookUpDataResponse.setLookUpDataId(lookUpDataEntity.getId()); lookUpDataResponse.setValue(lookUpDataEntity.getValue()); lookUpDataResponse.setTitle(lookUpDataEntity.getTitle()); lookUpDataResponse.setResponse(lookUpDataEntity.getResponse()); lookUpDataResponse.setCreatedDate(callTargetAudienceChecklistEntity.getCreatedDate()); lookUpDataResponse.setUpdatedDate(callTargetAudienceChecklistEntity.getUpdatedDate()); return lookUpDataResponse; } public CallEntity validateCall(Long callId) { 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) { validator.validateUserWithCompany(request, companyId); UserWithCompanyEntity userWithCompanyEntity=companyService.getUserWithCompany(user.getId(),companyId); preferredCall = beneficiaryPreferredCallRepository .findByUserIdAndCallIdAndUserWithCompanyIdAndIsDeletedFalse(userId, callId, userWithCompanyEntity.getId()) .orElse(null); } else { preferredCall = beneficiaryPreferredCallRepository .findByUserIdAndCallIdInAndIsDeletedFalse(userId, List.of(callId)) .stream() .findFirst() .orElse(null); } CallResponse callResponse = getCallResponseBean(callEntity); callResponse.setPreferredCallId(preferredCall != null ? preferredCall.getId() : null); return callResponse; } 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()); } convertToEvaluationCriteriaEntities(createCallRequest.getCriteria(), callEntity, LookUpDataTypeEnum.EVALUATION_CRITERIA); convertToDocumentEntities(createCallRequest.getDocs(), callEntity.getId(), DocumentTypeEnum.DOCUMENT); convertToDocumentEntities(createCallRequest.getImages(), callEntity.getId(), DocumentTypeEnum.IMAGES); updateLookUpData(callEntity, createCallRequest.getCheckList(), LookUpDataTypeEnum.CHECKLIST); CallResponse createCallResponseBean = getCallResponseBean(callEntity); createCallResponseBean.setCurrentStep(GepafinConstant.STEP_2); return createCallResponseBean; } 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)); } } public void isValidDateRange(UpdateCallRequestStep1 updateCallRequest, CallEntity callEntity) { List dates = updateCallRequest.getDates(); LocalDate startDate = (dates != null && dates.size() > 0 && dates.get(0) != null) ? dates.get(0).toLocalDate() : null; LocalDate endDate = (dates != null && dates.size() > 1 && dates.get(1) != null) ? dates.get(1).toLocalDate() : null; Boolean isValid = true; if (startDate != null && endDate != null && startDate.isAfter(endDate)) { isValid = false; } else if (startDate != null && endDate == null && callEntity.getEndDate() != null && startDate.isAfter(callEntity.getEndDate().toLocalDate())) { isValid = false; } else if (startDate == null && endDate != null && callEntity.getStartDate() != null && callEntity.getStartDate().toLocalDate().isAfter(endDate)) { isValid = false; } 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()); setIfUpdated(callEntity::getDescriptionShort, callEntity::setDescriptionShort, updateCallRequest.getDescriptionShort()); setIfUpdated(callEntity::getDescriptionLong, callEntity::setDescriptionLong, updateCallRequest.getDescriptionLong()); List dates=updateCallRequest.getDates(); boolean isEndDateUpdated = false; boolean isEndTimeUpdated = false; if (dates != null && dates.size()>1) { if (dates.size() > 0) { setIfUpdated(callEntity::getStartDate, callEntity::setStartDate, dates.get(0)); } // if (dates.size() > 1) { // LocalDate requestEndDate = dates.get(1).toLocalDate(); // Extract only the date // LocalDate storedEndDate = callEntity.getEndDate().toLocalDate(); // Extract only the date // // if (!requestEndDate.equals(storedEndDate)) { // Check if dates are different // // setIfUpdated(callEntity::getEndDate, callEntity::setEndDate, dates.get(1)); //// callEntity.setStatus(CallStatusEnum.PUBLISH.getValue()); //// callRepository.save(callEntity); // isEndDateUpdated = true; // } // } // } if (updateCallRequest.getEndTime() != null) { LocalTime requestEndTime = DateTimeUtil.parseTime(updateCallRequest.getEndTime()); LocalTime storedEndTime = callEntity.getEndTime(); if (!requestEndTime.equals(storedEndTime)) { setIfUpdated(callEntity::getEndTime, callEntity::setEndTime, DateTimeUtil.parseTime(updateCallRequest.getEndTime())); // callEntity.setStatus(CallStatusEnum.PUBLISH.getValue()); // callRepository.save(callEntity); isEndTimeUpdated = true; } } if (dates.size() > 1) { LocalDate requestEndDate = dates.get(1).toLocalDate(); // Extract only the date LocalDate storedEndDate = callEntity.getEndDate().toLocalDate(); // Extract only the date if (!requestEndDate.equals(storedEndDate)) { // Check if dates are different setIfUpdated(callEntity::getEndDate, callEntity::setEndDate, dates.get(1)); if(callEntity.getStatus().equals(CallStatusEnum.EXPIRED.getValue())) { LocalDateTime newEndDate = LocalDateTime.of(requestEndDate, callEntity.getEndTime()); if(newEndDate.isBefore(LocalDateTime.now())){ throw new CustomValidationException(Status.VALIDATION_ERROR,Translator.toLocale(GepafinConstant.END_DATE_GREATER_THAN_NOW)); } if (requestEndDate.isAfter(LocalDate.now()) || requestEndDate.isEqual(LocalDate.now())) { callEntity.setStatus(CallStatusEnum.PUBLISH.getValue()); callRepository.save(callEntity); } } isEndDateUpdated = true; } } } if (isEndDateUpdated || isEndTimeUpdated) { callRepository.save(callEntity); loggingUtil.logUserAction(UserActionRequest.builder() .request(request) .actionType(UserActionLogsEnum.UPDATE) .actionContext(UserActionContextEnum.UPDATE_CALL_END_DATE_AND_TIME) .build()); /** This code is responsible for adding a version history log for the "update call end date and time" operation **/ loggingUtil.addVersionHistory(VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldCallEntity).newData(callEntity).build()); } // setIfUpdated(callEntity::getStartDate, callEntity::setStartDate, updateCallRequest.getStartDate()); // setIfUpdated(callEntity::getEndDate, callEntity::setEndDate, updateCallRequest.getEndDate()); setIfUpdated(callEntity::getAmount, callEntity::setAmount, updateCallRequest.getAmount()); setIfUpdated(callEntity::getAmountMax, callEntity::setAmountMax, updateCallRequest.getAmountMax()); setIfUpdated(callEntity::getDocumentationRequested, callEntity::setDocumentationRequested, 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()); setIfUpdated(callEntity::getEmail, callEntity::setEmail, updateCallRequest.getEmail()); setIfUpdated(callEntity::getPhoneNumber, callEntity::setPhoneNumber, updateCallRequest.getPhoneNumber()); setIfUpdated(callEntity::getStartTime, callEntity::setStartTime, DateTimeUtil.parseTime(updateCallRequest.getStartTime())); setIfUpdated(callEntity::getConfidi, callEntity::setConfidi, updateCallRequest.getConfidi()); setIfUpdated(callEntity::getEvaluationVersion, callEntity::setEvaluationVersion, updateCallRequest.getEvaluationVersion().getValue()); setIfUpdated(callEntity::getNumberOfCheck, callEntity::setNumberOfCheck, updateCallRequest.getNumberOfCheck()); setIfUpdated(callEntity::getAppointmentTemplateId, callEntity::setAppointmentTemplateId, updateCallRequest.getAppointmentTemplateId()); setIfUpdated(callEntity::getAllowMultipleApplications, callEntity::setAllowMultipleApplications, updateCallRequest.getAllowMultipleApplications()); callEntity = callRepository.save(callEntity); /** This code is responsible for adding a version history log for the "update call step 1" operation **/ loggingUtil.addVersionHistory(VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldCallEntity).newData(callEntity).build()); updateLookUpData(callEntity, updateCallRequest.getAimedTo(), LookUpDataTypeEnum.AIMED_TO); 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; } private void softDeleteFaq(FaqEntity faqEntity) { FaqEntity oldFaqEntity = Utils.getClonedEntityForData(faqEntity); faqEntity.setIsDeleted(true); faqRepository.save(faqEntity); /** This code is responsible for adding a version history log for the "soft delete faq" operation **/ loggingUtil.addVersionHistory(VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.SOFT_DELETE).oldData(oldFaqEntity).newData(faqEntity).build()); } private void updateLookUpData(CallEntity callEntity, List lookupDataReqList, LookUpDataTypeEnum type) { if (lookupDataReqList == null) { return; } List existingChecklist = callTargetAudienceChecklistRepository .findByCallIdAndLookupDataTypeAndIsDeletedFalse(callEntity.getId(), type.getValue()); List incomingIds = lookupDataReqList.stream().map(LookUpDataReq::getId) .filter(id -> id != null && id > 0).collect(Collectors.toList()); existingChecklist.stream().filter(checklist -> !incomingIds.contains(checklist.getId())) .forEach(this::softDeleteCallTargetAudienceChecklist); lookupDataReqList .forEach(lookUpDataReq -> createOrUpdateCallTargetAudienceChecklist(lookUpDataReq, callEntity, type)); } private void createOrUpdateCallTargetAudienceChecklist(LookUpDataReq lookUpDataReq, CallEntity callEntity, LookUpDataTypeEnum type) { CallTargetAudienceChecklistEntity checklistEntity = null; CallTargetAudienceChecklistEntity oldChecklistEntity = null; VersionActionTypeEnum actionType = VersionActionTypeEnum.INSERT; LookUpDataEntity lookupDataEntity = lookUpDataService.getOrCreateLookUpDataEntity(lookUpDataReq, type); if (lookUpDataReq.getId() != null && lookUpDataReq.getId() > 0) { checklistEntity = callTargetAudienceChecklistRepository.findById(lookUpDataReq.getId()) .orElseThrow(() -> new ResourceNotFoundException(Status.NOT_FOUND, Translator.toLocale(GepafinConstant.CALL_NOT_FOUND))); if (Boolean.FALSE.equals(checklistEntity.getLookupData().getId().equals(lookupDataEntity.getId()))) { checklistEntity.setLookupData(lookupDataEntity); } oldChecklistEntity = Utils.getClonedEntityForData(checklistEntity); actionType = VersionActionTypeEnum.UPDATE; } else { checklistEntity = new CallTargetAudienceChecklistEntity(); checklistEntity.setCall(callEntity); checklistEntity.setLookupData(lookupDataEntity); checklistEntity.setIsValidated(false); checklistEntity.setIsDeleted(false); actionType = VersionActionTypeEnum.INSERT; } checklistEntity = callTargetAudienceChecklistRepository.save(checklistEntity); /** This code is responsible for adding a version history log for the "create or update aimedTo Checklist" operation **/ loggingUtil.addVersionHistory(VersionHistoryRequest.builder().request(request).actionType(actionType).oldData(oldChecklistEntity).newData(checklistEntity).build()); } private void softDeleteCallTargetAudienceChecklist( CallTargetAudienceChecklistEntity callTargetAudienceChecklistEntity) { CallTargetAudienceChecklistEntity oldCallTargetAudienceChecklistEntity = Utils.getClonedEntityForData(callTargetAudienceChecklistEntity); callTargetAudienceChecklistEntity.setIsDeleted(true); callTargetAudienceChecklistRepository.save(callTargetAudienceChecklistEntity); /** This code is responsible for adding a version history log for the "soft delete for aimedTo or Checklist" operation **/ loggingUtil.addVersionHistory(VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.SOFT_DELETE).oldData(oldCallTargetAudienceChecklistEntity).newData(callTargetAudienceChecklistEntity).build()); } public CallDetailsResponseBean convertToCallDetailsResponseBean(CallEntity callEntity) { CallDetailsResponseBean callDetailsResponseBean = new CallDetailsResponseBean(); callDetailsResponseBean.setId(callEntity.getId()); callDetailsResponseBean.setName(callEntity.getName()); List dates = new ArrayList<>(); dates.add(callEntity.getStartDate()); dates.add(callEntity.getEndDate()); callDetailsResponseBean.setDates(dates); callDetailsResponseBean.setConfidi(callEntity.getConfidi()); callDetailsResponseBean.setDescriptionShort(callEntity.getDescriptionShort()); callDetailsResponseBean.setDescriptionLong(callEntity.getDescriptionLong()); callDetailsResponseBean.setStatus(CallStatusEnum.valueOf(callEntity.getStatus())); callDetailsResponseBean.setEvaluationVersion(EvaluationVersionEnum.valueOf(callEntity.getEvaluationVersion())); callDetailsResponseBean.setRegionId(callEntity.getRegion().getId()); callDetailsResponseBean.setAmount(callEntity.getAmount()); callDetailsResponseBean.setAmountMax(callEntity.getAmountMax()); callDetailsResponseBean.setContactInfo(callEntity.getContactInfo()); callDetailsResponseBean.setSubmissionMethod(callEntity.getSubmissionMethod()); callDetailsResponseBean.setThreshold(callEntity.getThreshold()); callDetailsResponseBean.setDocumentationRequested(callEntity.getDocumentationRequested()); callDetailsResponseBean.setPriorityArea(callEntity.getPriorityArea()); callDetailsResponseBean.setAmountMin(callEntity.getAmountMin()); callDetailsResponseBean.setEmail(callEntity.getEmail()); callDetailsResponseBean.setEndTime(callEntity.getEndTime()); callDetailsResponseBean.setStartTime(callEntity.getStartTime()); callDetailsResponseBean.setPhoneNumber(callEntity.getPhoneNumber()); callDetailsResponseBean.setCreatedDate(callEntity.getCreatedDate()); callDetailsResponseBean.setUpdatedDate(callEntity.getUpdatedDate()); callDetailsResponseBean.setNumberOfCheck(callEntity.getNumberOfCheck()); callDetailsResponseBean.setAppointmentTemplateId(callEntity.getAppointmentTemplateId()); callDetailsResponseBean.setAllowMultipleApplications(callEntity.getAllowMultipleApplications()); return callDetailsResponseBean; } 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() , DocumentTypeEnum.IMAGES.getValue()); List amiedTo = callTargetAudienceChecklistRepository .findByCallIdAndLookupDataTypeAndIsDeletedFalse(callEntity.getId(), LookUpDataTypeEnum.AIMED_TO.getValue()).stream() .map(this::convertToLookUpDataResponseBean).toList(); List checkList = callTargetAudienceChecklistRepository .findByCallIdAndLookupDataTypeAndIsDeletedFalse(callEntity.getId(), LookUpDataTypeEnum.CHECKLIST.getValue()).stream() .map(this::convertToLookUpDataResponseBean).toList(); List evaluationCriteriaEntities = evaluationCriteriaRepository .findByCallIdAndLookupDataTypeAndIsDeletedFalse(callEntity.getId(), LookUpDataTypeEnum.EVALUATION_CRITERIA.getValue()); CallResponse createCallResponseBean = assembleCreateCallResponseBean(callEntity, evaluationCriteriaEntities, documentEntities, imageEntities); createCallResponseBean.setFaq(faqService.getFaqByCallId(callEntity.getId())); createCallResponseBean.setAimedTo(amiedTo); createCallResponseBean.setCheckList(checkList); createCallResponseBean.setNumberOfCheck(callEntity.getNumberOfCheck()); createCallResponseBean.setAppointmentTemplateId(callEntity.getAppointmentTemplateId()); return createCallResponseBean; } 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()); } // List calls = List.of(); if (Boolean.TRUE.equals(onlyPreferredCall) && companyId == null) { throw new CustomValidationException(Status.VALIDATION_ERROR, Translator.toLocale(GepafinConstant.COMPANY_ID_REQUIRED_FOR_PREFERRED_CALL)); } expirePublishedCalls(request); Specification spec = buildCallSpecification(request, user, companyId, onlyPreferredCall, onlyConfidiCall, callStatusList); List calls = callRepository.findAll(spec); LocalDateTime now = LocalDateTime.now(); for (CallEntity call : calls) { CallEntity oldCallEntity = Utils.getClonedEntityForData(call); if (CallStatusEnum.PUBLISH.getValue().equals(call.getStatus()) && 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); } if (Boolean.FALSE.equals(oldCallEntity.getStatus().equals(call.getStatus()))) { loggingUtil.logUserAction(UserActionRequest.builder() .request(request) .actionType(UserActionLogsEnum.UPDATE) .actionContext(UserActionContextEnum.UPDATE_EXPIRED_CALL) .build()); /** This code is responsible for adding a version history log for the "update call status to EXPIRED" operation **/ loggingUtil.addVersionHistory(VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldCallEntity).newData(call).build()); } } } List callIds = calls.stream().map(CallEntity::getId).collect(Collectors.toList()); Map preferredCallsMap = getBeneficiaryPreferredCallsForUser(request,user, callIds, companyId); return calls.stream() .map(call -> { CallDetailsResponseBean responseBean = convertToCallDetailsResponseBean(call); String key = user.getId() + "_" + call.getId(); BeneficiaryPreferredCallEntity preferredCall = preferredCallsMap.get(key); Long preferredId = (preferredCall != null && !preferredCall.getIsDeleted()) ? preferredCall.getId() : null; responseBean.setPreferredCallId(preferredId); return responseBean; }) .collect(Collectors.toList()); } 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() .collect(Collectors.collectingAndThen( Collectors.toMap( BeneficiaryPreferredCallEntity::getCallId, call -> call, (existing, replacement) -> existing ), map -> new ArrayList<>(map.values()) )); } return beneficiaryPreferredCalls.stream() .collect(Collectors.toMap( call -> user.getId() + "_" + call.getCallId(), call -> call )); } 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); FlowResponseBean flowResponseBean = flowDao.getFlowByCallId(callEntity.getId()); List formResponseBean = formDao.getFormsByCallId(callEntity); EvaluationFormResponseBean evaluationFormResponseBean = evalualtionFormDao.getEvaluationFormByCallId(callEntity); 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()); callResponseBean.setCurrentStep(GepafinConstant.VALIDATE_REQUEST); callResponseBean.setStatus(CallStatusEnum.valueOf(callEntity.getStatus())); return callResponseBean; } // public CallEntity getCallEntityById(Long id){ // CallEntity callEntity=callRepository.findByIdAndStatusNotInAndHubId(id, List.of(CallStatusEnum.PUBLISH.getValue())); // if(callEntity==null){ // throw new ResourceNotFoundException(Status.NOT_FOUND, Translator.toLocale(GepafinConstant.CALL_NOT_FOUND)); // } // return callEntity; // } 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()); Map placeholders = new HashMap<>(); placeholders.put("{{call_name}}", callEntity.getName()); userIds.forEach(userId -> { List companyIds = notificationDao.getAllCompanyIdsForUser(userId); NotificationReq notificationReq = notificationDao.createNotificationReq(NotificationTypeEnum.CALL_CREATED.getValue(), placeholders, userId, null, companyIds); notificationDao.sendNotification(notificationReq); }); /** This code is responsible for adding a version history log for the "update call status" operation **/ loggingUtil.addVersionHistory(VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldCallEntity).newData(callEntity).build()); return convertToCallResponseBean(callEntity); } 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: break; default: break; } } 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)); } LocalDate currentDate = DateTimeUtil.DateServerToUTC(LocalDateTime.now()).toLocalDate(); LocalTime currentTime = DateTimeUtil.LocalTimeServerToEurope(LocalTime.now()); 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) ); } 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) ); } return callEntity; } 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) { pageNo = callPageableRequestBean.getGlobalFilters().getPage(); pageLimit = callPageableRequestBean.getGlobalFilters().getLimit(); } if (pageLimit == null || pageLimit <= 0) { pageLimit = GepafinConstant.DEFAULT_PAGE_LIMIT; } if (pageNo == null || pageNo <= 0) { pageNo = GepafinConstant.DEFAULT_PAGE; } if (Boolean.TRUE.equals(onlyPreferredCall) && companyId == null) { throw new CustomValidationException( Status.VALIDATION_ERROR, Translator.toLocale(GepafinConstant.COMPANY_ID_REQUIRED_FOR_PREFERRED_CALL) ); } expirePublishedCalls(request); 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 .findByUserIdAndUserWithCompanyIdAndIsDeletedFalse(user.getId(), userWithCompanyEntity.getId()); List preferredCallIds = preferredCalls.stream() .map(BeneficiaryPreferredCallEntity::getCallId) .collect(Collectors.toList()); // Add preferredCallIds filtering to the specification spec = spec.and((root, query, criteriaBuilder) -> criteriaBuilder.and( root.get(GepafinConstant.ID).in(preferredCallIds), criteriaBuilder.isTrue(root.get("confidi")) ) ); } entityPage = callRepository.findAll(spec, PageRequest.of(pageNo - 1, pageLimit)); List callIds=new ArrayList<>(); if(entityPage!=null && entityPage.getContent()!=null && Boolean.FALSE.equals(entityPage.getContent().isEmpty())) { callIds = entityPage.getContent().stream().map(CallEntity::getId).collect(Collectors.toList()); } Map preferredCallsMap = getBeneficiaryPreferredCallsForUser(request,user, callIds, companyId); List callDetailsResponseBeans = entityPage.getContent().stream() .map(callEntity -> { CallDetailsResponseBean responseBean = convertToCallDetailsResponseBean(callEntity); String key = user.getId() + "_" + callEntity.getId(); BeneficiaryPreferredCallEntity preferredCall = preferredCallsMap.get(key); Long preferredId = preferredCall != null ? preferredCall.getId() : null; responseBean.setPreferredCallId(preferredId); return responseBean; }) .collect(Collectors.toList()); PageableResponseBean> pageableResponseBean = new PageableResponseBean<>(); pageableResponseBean.setBody(callDetailsResponseBeans); pageableResponseBean.setCurrentPage(entityPage.getNumber() + 1); // Page numbers typically start from 0, so add 1 for user-friendly indexing pageableResponseBean.setTotalPages(entityPage.getTotalPages()); pageableResponseBean.setTotalRecords(entityPage.getTotalElements()); pageableResponseBean.setPageSize(entityPage.getSize()); return pageableResponseBean; } public Specification search(HttpServletRequest request,UserEntity userEntity, CallPageableRequestBean callPageableRequestBean,Boolean onlyConfidiCall) { return (root, query, criteriaBuilder) -> { List predicates = getPredicates(request,callPageableRequestBean, criteriaBuilder, root, userEntity,onlyConfidiCall); SortBy sortBy = new SortBy(GepafinConstant.CREATED_DATE, true); if (callPageableRequestBean.getGlobalFilters() != null && callPageableRequestBean.getGlobalFilters().getSortBy() != null && callPageableRequestBean.getGlobalFilters().getSortBy().getColumnName() != null && Boolean.FALSE.equals( isEmpty(callPageableRequestBean.getGlobalFilters().getSortBy().getColumnName()))) { sortBy.setColumnName(callPageableRequestBean.getGlobalFilters().getSortBy().getColumnName()); sortBy.setSortDesc(true); if (callPageableRequestBean.getGlobalFilters().getSortBy().getSortDesc() != null) { sortBy.setSortDesc(callPageableRequestBean.getGlobalFilters().getSortBy().getSortDesc()); } } query.orderBy(criteriaBuilder.desc(root.get(sortBy.getColumnName()))); if (Boolean.FALSE.equals(sortBy.getSortDesc())) { query.orderBy(criteriaBuilder.asc(root.get(sortBy.getColumnName()))); } return query.where(criteriaBuilder.and(predicates.toArray(new Predicate[0]))).getRestriction(); }; } private List getPredicates(HttpServletRequest request,CallPageableRequestBean callPageableRequestBean, CriteriaBuilder criteriaBuilder, Root root, UserEntity userEntity,Boolean onlyConfidiCall) { Integer year = null; String search = null; Map filters = new HashMap<>(); if (callPageableRequestBean.getGlobalFilters() != null) { year = callPageableRequestBean.getGlobalFilters().getYear(); search = callPageableRequestBean.getGlobalFilters().getSearch(); } if (callPageableRequestBean.getFilters() != null) { filters = callPageableRequestBean.getFilters(); } List predicates = new ArrayList<>(); if (year != null && year > 0) { int filterYear = callPageableRequestBean.getGlobalFilters().getYear(); // Create LocalDateTime boundaries for the start and end of the year LocalDateTime startOfYear = LocalDateTime.of(filterYear, 1, 1, 0, 0); LocalDateTime endOfYear = LocalDateTime.of(filterYear, 12, 31, 23, 59, 59); // Add the range comparison to filter records within the year predicates.add(criteriaBuilder.between(root.get(GepafinConstant.CREATED_DATE), startOfYear, endOfYear)); } // Search in `title` and `message` (if search term is provided) if (search != null && !search.isEmpty()) { Predicate descriptionShort = criteriaBuilder.like( criteriaBuilder.upper(root.get(GepafinConstant.DESCRIPTION_SHORT)), "%" + search.toUpperCase() + "%" ); predicates.add(criteriaBuilder.or(descriptionShort)); Predicate descriptionLong = criteriaBuilder.like( criteriaBuilder.upper(root.get(GepafinConstant.DESCRIPTION_LONG)), "%" + search.toUpperCase() + "%" ); predicates.add(criteriaBuilder.or(descriptionLong)); Predicate name = criteriaBuilder.like( criteriaBuilder.upper(root.get(GepafinConstant.NAME)), "%" + search.toUpperCase() + "%" ); predicates.add(criteriaBuilder.or(name)); } // Filter by `status` (if status list is provided) if (callPageableRequestBean.getStatus() != null && !callPageableRequestBean.getStatus().isEmpty()) { List statusValues = callPageableRequestBean.getStatus().stream() .map(CallStatusEnum::name) // Convert enum to string .toList(); predicates.add(root.get(GepafinConstant.STATUS).in(statusValues)); } applyFilters(root, criteriaBuilder, predicates, filters); Boolean isConfidi =onlyConfidiCall; if (validator.checkIsConfidi()) { if (isConfidi == null || isConfidi.equals(Boolean.FALSE)) { // Ensure no records are returned return List.of(criteriaBuilder.disjunction()); } if (isConfidi.equals(Boolean.TRUE)) { predicates.add(criteriaBuilder.isTrue(root.get("confidi"))); } } else if (Boolean.TRUE.equals(validator.checkIsBeneficiary())) { predicates.add(criteriaBuilder.isFalse(root.get("confidi"))); } else if( Boolean.FALSE.equals(validator.checkIsConfidi()) && isConfidi!=null){ if (isConfidi.equals(Boolean.TRUE)) { predicates.add(criteriaBuilder.isTrue(root.get("confidi"))); } if (isConfidi.equals(Boolean.FALSE)) { predicates.add(criteriaBuilder.isFalse(root.get("confidi"))); } } predicates.add(criteriaBuilder.equal(root.get(GepafinConstant.HUB).get(GepafinConstant.ID), userEntity.getHub().getId())); return predicates; } public void expirePublishedCalls(HttpServletRequest request) { 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); if (!expirdedCallList.isEmpty()) { loggingUtil.logUserAction(UserActionRequest.builder() .request(request) .actionType(UserActionLogsEnum.UPDATE) .actionContext(UserActionContextEnum.UPDATE_EXPIRED_CALL) .build()); for (CallEntity call : expirdedCallList) { CallEntity oldCallEntity = Utils.getClonedEntityForData(call); // Clone before modification call.setStatus(CallStatusEnum.EXPIRED.getValue()); // Add version history log loggingUtil.addVersionHistory(VersionHistoryRequest.builder() .request(request) .actionType(VersionActionTypeEnum.UPDATE) .oldData(oldCallEntity) .newData(call) .build()); } callRepository.saveAll(expirdedCallList); // Save all modified calls at once } } private void applyFilters(Root root, CriteriaBuilder criteriaBuilder, List predicates, Map filters) { if (Boolean.FALSE.equals(filters.isEmpty())) { for (Map.Entry entry : filters.entrySet()) { String fieldName = entry.getKey(); FilterCriteria filterCriteria = entry.getValue(); Object value = filterCriteria.getValue(); MatchModeEnum matchMode = filterCriteria.getMatchMode(); if (value != null && matchMode != null) { Path fieldPath = getFieldPath(root, fieldName); if (fieldPath != null) { applyStringFilter(fieldPath, criteriaBuilder, predicates, value, matchMode); applyNumberFilter(fieldPath, criteriaBuilder, predicates, value, matchMode); applyDateFilter(fieldPath, criteriaBuilder, predicates, value, matchMode,root); } } } } } private void applyStringFilter(Path fieldPath, CriteriaBuilder criteriaBuilder, List predicates, Object value, MatchModeEnum matchMode) { if (value instanceof String) { String valueStr = (String) value; if (fieldPath.getJavaType().equals(String.class)) { MatchModeEnum mode = MatchModeEnum.fromObject(matchMode.getValue()); switch (mode) { case CONTAINS -> predicates.add(criteriaBuilder.like(criteriaBuilder.lower(fieldPath.as(String.class)), "%" + valueStr.toLowerCase() + "%")); case EQUALS -> predicates.add(criteriaBuilder.equal(fieldPath, valueStr)); case STARTSWITH -> predicates.add(criteriaBuilder.like(criteriaBuilder.lower(fieldPath.as(String.class)), valueStr.toLowerCase() + "%")); case ENDSWITH -> predicates.add(criteriaBuilder.like(criteriaBuilder.lower(fieldPath.as(String.class)), "%" + valueStr.toLowerCase())); } } } } private void applyNumberFilter(Path fieldPath, CriteriaBuilder criteriaBuilder, List predicates, Object value, MatchModeEnum matchMode) { if (Number.class.isAssignableFrom(fieldPath.getJavaType())) { Number numberValue = null; if (value instanceof Number) { numberValue = (Number) value; } MatchModeEnum mode = MatchModeEnum.fromObject(matchMode.getValue()); switch (mode) { case EQUALS -> predicates.add(criteriaBuilder.equal(fieldPath, numberValue)); } } } private void applyDateFilter(Path fieldPath, CriteriaBuilder criteriaBuilder, List predicates, Object value, MatchModeEnum matchMode, Root root) { if (fieldPath.getJavaType().equals(LocalDateTime.class)) { // Convert input string: Replace 'T' with space String formattedValue = value.toString().replace("T", " "); // Handle timezones and UTC (`Z` or `+HH:mm`) if (formattedValue.contains("Z") || formattedValue.matches(".*[+-]\\d{2}:\\d{2}$")) { OffsetDateTime offsetDateTime = OffsetDateTime.parse(value.toString(), DateTimeFormatter.ISO_OFFSET_DATE_TIME); formattedValue = offsetDateTime.toLocalDateTime().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS")); } // Check if more than 3 decimal places exist if (formattedValue.contains(".")) { int dotIndex = formattedValue.indexOf("."); if (formattedValue.length() > dotIndex + 4) { formattedValue = formattedValue.substring(0, dotIndex + 4); // Keep only 3 decimals } } else { formattedValue += ".000"; // Ensure 3 decimals } // Define correct date-time format DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS"); // Parse the formatted value into LocalDateTime LocalDateTime dateTimeValue = LocalDateTime.parse(formattedValue, formatter); // Extract only the date portion LocalDate dateValue = dateTimeValue.toLocalDate(); // Convert database field to LocalDate for date-only comparison Expression dateField = criteriaBuilder.function("DATE", LocalDate.class, fieldPath); MatchModeEnum mode = MatchModeEnum.fromObject(matchMode.getValue()); switch (mode) { case DATEIS -> predicates.add(criteriaBuilder.equal(dateField, dateValue)); case DATEISNOT -> predicates.add(criteriaBuilder.notEqual(dateField, dateValue)); case BEFORE -> predicates.add(criteriaBuilder.lessThan(fieldPath.as(Timestamp.class), Timestamp.valueOf(dateTimeValue))); case AFTER -> predicates.add(criteriaBuilder.greaterThan(fieldPath.as(Timestamp.class), Timestamp.valueOf(dateTimeValue))); } } } private Path getFieldPath(Root root, String fieldName) { try { return switch (fieldName) { case GepafinConstant.REGION_ID -> { // Ensure join is only created if not already present Join regionJoin = root.getJoins().stream() .filter(j -> j.getAttribute().getName().equals("region")) .findFirst() .map(j -> (Join) j) .orElseGet(() -> root.join("region", JoinType.LEFT)); yield regionJoin.get("id"); } default -> root.get(fieldName); }; } catch (IllegalArgumentException e) { return null; } } 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); CallResponse createCallResponseBean = getCallResponseBean(callEntity); createCallResponseBean.setCurrentStep(GepafinConstant.EVALUATION_V2_STEP_2); return createCallResponseBean; } private Specification buildCallSpecification(HttpServletRequest request, UserEntity user, Long companyId, Boolean onlyPreferredCall, Boolean onlyConfidiCall, List callStatusList) { return (root, query, criteriaBuilder) -> { List predicates = new ArrayList<>(); predicates.add(root.get("status").in(callStatusList)); if (Boolean.TRUE.equals(onlyPreferredCall)) { validator.validateUserWithCompany(request, companyId); UserWithCompanyEntity userWithCompanyEntity = companyService.getUserWithCompany(user.getId(), companyId); List preferredCalls = beneficiaryPreferredCallRepository .findByUserIdAndUserWithCompanyIdAndIsDeletedFalse(user.getId(), userWithCompanyEntity.getId()); List preferredCallIds = preferredCalls.stream() .map(BeneficiaryPreferredCallEntity::getCallId) .collect(Collectors.toList()); predicates.add(root.get("id").in(preferredCallIds)); } else { predicates.add(criteriaBuilder.equal(root.get("hub").get("id"), user.getHub().getId())); } if (validator.checkIsConfidi()) { if (onlyConfidiCall==null || Boolean.FALSE.equals(onlyConfidiCall)) { return criteriaBuilder.disjunction(); // Returns an empty predicate (no results) } if (onlyConfidiCall!=null && Boolean.TRUE.equals(onlyConfidiCall)) { predicates.add(criteriaBuilder.isTrue(root.get("confidi"))); } } else if (Boolean.TRUE.equals(validator.checkIsBeneficiary())) { predicates.add(criteriaBuilder.isFalse(root.get("confidi"))); } else { if(onlyConfidiCall!=null) { if (Boolean.TRUE.equals(onlyConfidiCall)) { predicates.add(criteriaBuilder.isTrue(root.get("confidi"))); } if (Boolean.FALSE.equals(onlyConfidiCall)) { predicates.add(criteriaBuilder.isFalse(root.get("confidi"))); } } } return criteriaBuilder.and(predicates.toArray(new Predicate[0])); }; } }