diff --git a/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java b/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java index 8cdf2848..c6eebdd7 100644 --- a/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java +++ b/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java @@ -478,6 +478,7 @@ public class GepafinConstant { public static final String SWITCH="switch"; public static final String IS_CHECK_LIST_ITEM="isChecklistItem"; public static final String VALIDATION_FAILED_FOR_CHECKLIST="validation.failed.checklist"; + public static final String INSUFFICIENT_SCORE_MESSAGE ="insufficient.score.msg"; public static final String PEC_SERVICE_URL="https://ws.pecmassiva.com"; public static final String PEC_SERVICE_SEND_MAIL="/send"; public static final String PEC_SERVICE_INBOX_MAIL="/quota/inbox"; @@ -486,6 +487,10 @@ public class GepafinConstant { public static final String DATA="data"; public static final String INVALID_LIMIT = "error.invalid.limit"; + public static final String PREFERRED_CALL_ID="preferredCallId"; + + public static final String REGION_ID="regionId"; + } diff --git a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationAmendmentRequestDao.java b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationAmendmentRequestDao.java index 23747301..e9621798 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationAmendmentRequestDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationAmendmentRequestDao.java @@ -1047,7 +1047,7 @@ public class ApplicationAmendmentRequestDao { List amendmentRequests = applicationAmendmentRequestRepository.findAllByApplicationEvaluationIdAndIsDeletedFalse( existingApplicationAmendment.getApplicationEvaluationEntity().getId()); - Boolean allClosed = amendmentRequests.stream().allMatch(amendment -> amendment.getStatus().equals(ApplicationAmendmentRequestEnum.CLOSE.getValue())); + Boolean allClosed = amendmentRequests.stream().allMatch(amendment -> (amendment.getStatus().equals(ApplicationAmendmentRequestEnum.CLOSE.getValue()) || amendment.getStatus().equals(ApplicationAmendmentRequestEnum.EXPIRED.getValue()))); ApplicationEntity application = applicationService.validateApplication(existingApplicationAmendment.getApplicationId()); ApplicationEntity oldApplicationEntityData = Utils.getClonedEntityForData(application); if (Boolean.TRUE.equals(allClosed)) { diff --git a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java index 92ffc2d3..9b3466fe 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java @@ -1,5 +1,9 @@ package net.gepafin.tendermanagement.dao; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.Root; import jakarta.persistence.criteria.*; import net.gepafin.tendermanagement.config.Translator; import net.gepafin.tendermanagement.constants.GepafinConstant; @@ -53,6 +57,7 @@ import java.text.ParseException; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; +import java.time.OffsetDateTime; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; import java.util.*; @@ -272,7 +277,8 @@ public class ApplicationDao { for (ApplicationFormFieldEntity applicationFormFieldEntity : applicationFormFieldEntities) { Optional fileUploadContent = contentResponseBeans.stream() - .filter(contentResponseBean -> "fileupload".equals(contentResponseBean.getName()) && + .filter(contentResponseBean -> ("fileupload".equals(contentResponseBean.getName()) || + "fileselect".equals(contentResponseBean.getName())) && contentResponseBean.getId().equals(applicationFormFieldEntity.getFieldId())) .findFirst(); @@ -418,7 +424,7 @@ public class ApplicationDao { responseBean.setCallTitle(applicationEntity.getCall().getName()); responseBean.setCallEndDate(applicationEntity.getCall().getEndDate()); responseBean.setCallEndTime(applicationEntity.getCall().getEndTime()); - responseBean.setModifiedDate(applicationEntity.getCall().getUpdatedDate()); + responseBean.setModifiedDate(applicationEntity.getUpdatedDate()); responseBean.setCallId(applicationEntity.getCall().getId()); responseBean.setSubmissionDate(applicationEntity.getSubmissionDate()); responseBean.setStatus(applicationEntity.getStatus()); @@ -608,7 +614,7 @@ public class ApplicationDao { List contentResponseBeans = formDao.convertFormEntityToFormResponseBean(formEntity).getContent(); for (ContentResponseBean contentResponseBean : contentResponseBeans) { - if (Boolean.FALSE.equals(contentResponseBean.getName().equals("fileupload"))) { + if (Boolean.FALSE.equals(contentResponseBean.getName().equals("fileupload") || contentResponseBean.getName().equals("fileselect"))) { return; } } @@ -664,7 +670,7 @@ public class ApplicationDao { // List contentResponseBeans=Utils.convertJsonStringToList(formEntity.getContent(),ContentResponseBean.class); List contentResponseBeans=formDao.convertFormEntityToFormResponseBean(formEntity).getContent(); for (ContentResponseBean contentResponseBean:contentResponseBeans){ - if(Boolean.TRUE.equals(contentResponseBean.getName().equals("fileupload"))) { + if(Boolean.TRUE.equals(contentResponseBean.getName().equals("fileupload")) || Boolean.TRUE.equals(contentResponseBean.getName().equals("fileselect"))) { if (contentResponseBean.getId().equals(applicationFormFieldRequestBean.getFieldId())) { Object fieldValueObject = applicationFormFieldRequestBean.getFieldValue(); if (fieldValueObject instanceof String) { @@ -918,8 +924,10 @@ public class ApplicationDao { 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); @@ -950,14 +958,14 @@ public class ApplicationDao { sendMailToUserAndCompany(userEntity, applicationEntity); sendMailTodefaultSystemAndGepafin(userEntity, applicationEntity); applicationEntity.setStatus(status.getValue()); + log.info("Status updated to SUBMIT for applicationId: " + applicationId); } if (status.equals(ApplicationStatusTypeEnum.DRAFT) && Boolean.TRUE.equals(applicationEntity.getStatus().equals(ApplicationStatusTypeEnum.AWAITING.getValue()))) { applicationEntity.setStatus(status.getValue()); - } - if(status.equals(ApplicationStatusTypeEnum.ADMISSIBLE) && Boolean.TRUE.equals(applicationEntity.getStatus().equals(ApplicationStatusTypeEnum.APPOINTMENT.getValue()))){ - applicationEntity.setStatus(status.getValue()); + log.info("Status updated to DRAFT for applicationId: " + applicationId); } applicationEntity = applicationRepository.save(applicationEntity); + log.info("Application status updated successfully | applicationId: {}, newStatus: {}", applicationId, applicationEntity.getStatus()); if (!status.equals(ApplicationStatusTypeEnum.SUBMIT)) { /** This code is responsible for adding a version history log for "Update application status" operation. **/ @@ -1353,7 +1361,7 @@ public class ApplicationDao { FormEntity formEntity = applicationForm.getForm(); if (formEntity != null) { List contentResponseBeans = formDao.convertFormEntityToFormResponseBean(formEntity).getContent(); - contentResponseBeans.stream().filter(content -> "fileupload".equals(content.getName())).forEach(content -> { + contentResponseBeans.stream().filter(content -> "fileupload".equals(content.getName()) || "fileselect".equals(content.getName())).forEach(content -> { Optional formField = applicationFormFieldRepository.findByFieldIdAndApplicationFormIdAndApplicationFormApplicationId( content.getId(), applicationForm.getId(), applicationId); formField.ifPresent(field -> { @@ -1653,15 +1661,44 @@ public class ApplicationDao { private void applyDateFilter(Path fieldPath, CriteriaBuilder criteriaBuilder, List predicates, Object value, MatchModeEnum matchMode, Root root) { if (fieldPath.getJavaType().equals(LocalDateTime.class)) { - LocalDateTime testDateTime = DateTimeUtil.parseStringToLocalDateTime(value.toString()); + // 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(fieldPath.as(Timestamp.class), Timestamp.valueOf(testDateTime))); - case DATEISNOT -> predicates.add(criteriaBuilder.notEqual(fieldPath.as(Timestamp.class), Timestamp.valueOf(testDateTime))); - case BEFORE -> predicates.add(criteriaBuilder.lessThan(fieldPath.as(Timestamp.class), Timestamp.valueOf(testDateTime))); - case AFTER -> predicates.add(criteriaBuilder.greaterThan(fieldPath.as(Timestamp.class), Timestamp.valueOf(testDateTime))); + 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))); } } } diff --git a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationEvaluationDao.java b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationEvaluationDao.java index 4902cc57..336db880 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationEvaluationDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationEvaluationDao.java @@ -206,23 +206,27 @@ public class ApplicationEvaluationDao { amendmentDocumentResponseBean.setAmendmentId(applicationAmendmentRequestEntity.getId()); String amendmentDocument=applicationAmendmentRequestEntity.getAmendmentDocument(); String formField=applicationAmendmentRequestEntity.getFormFields(); - AmendmentDetailsResponseBean amendmentDetails = Utils.convertStringToObject(amendmentDocument, AmendmentDetailsResponseBean.class); - if (amendmentDetails != null) { - if (amendmentDetails.getAmendmentDocuments() != null) { - List documentResponseBeans = Arrays.stream(amendmentDetails.getAmendmentDocuments().split(",")) - .map(String::trim) - .filter(id -> !id.isEmpty()) - .map(documentId -> applicationAmendmentRequestDao.createDocumentResponseBean(documentId)) - .filter(Objects::nonNull) - .collect(Collectors.toList()); + if (StringUtils.isNotBlank(amendmentDocument)) { + AmendmentDetailsResponseBean amendmentDetails = Utils.convertStringToObject(amendmentDocument, AmendmentDetailsResponseBean.class); - amendmentDocumentResponseBean.setFileDetail(documentResponseBeans); + if (amendmentDetails != null) { + if (StringUtils.isNotBlank(amendmentDetails.getAmendmentDocuments())) { + List documentResponseBeans = Arrays.stream(amendmentDetails.getAmendmentDocuments().split(",")) + .map(String::trim) + .filter(id -> !id.isEmpty()) + .map(documentId -> applicationAmendmentRequestDao.createDocumentResponseBean(documentId)) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + amendmentDocumentResponseBean.setFileDetail(documentResponseBeans); + } + + amendmentDocumentResponseBean.setFieldId("amend_" + applicationAmendmentRequestEntity.getId()); + amendmentDocumentResponseBean.setLabel(amendmentDetails.getAmendmentNotes()); + amendmentDocumentResponseBean.setValid(amendmentDetails.getValid()); + amendmentDocumentResponseBeans.add(amendmentDocumentResponseBean); } - amendmentDocumentResponseBean.setFieldId("amend_" + applicationAmendmentRequestEntity.getId()); - amendmentDocumentResponseBean.setLabel(amendmentDetails.getAmendmentNotes()); - amendmentDocumentResponseBean.setValid(amendmentDetails.getValid()); - amendmentDocumentResponseBeans.add(amendmentDocumentResponseBean); } + List amendmentFormFields = Utils.convertJsonStringToList(formField, AmendmentFormField.class); if (amendmentFormFields != null) { for (AmendmentFormField amendmentFormField : amendmentFormFields) { @@ -1858,7 +1862,21 @@ public class ApplicationEvaluationDao { // UserEntity userEntity = userService.validateUser(application.getUserId()); // callService.validatePublishedCall(application.getCall().getId(), userEntity.getHub().getId()); ApplicationEntity oldApplicationEntity = Utils.getClonedEntityForData(application); - application.setStatus(newStatus.getValue()); + + + if(newStatus.equals(ApplicationStatusForEvaluation.ADMISSIBLE) && Boolean.TRUE.equals(application.getStatus().equals(ApplicationStatusTypeEnum.APPOINTMENT.getValue()))){ + application.setStatus(newStatus.getValue()); + log.info("Status updated to ADMISSIBLE for applicationId: " + application.getId()); + emailNotificationDao.sendAdmissibilityNotificationEmailForAdmissibleApplication(application); + } + + if(newStatus.equals(ApplicationStatusForEvaluation.TECHNICAL_EVALUATION) && Boolean.TRUE.equals(application.getStatus().equals(ApplicationStatusTypeEnum.ADMISSIBLE.getValue()))){ + processTechnicalEvaluation(application.getId(), application, newStatus); + } + + if((newStatus.equals(ApplicationStatusForEvaluation.APPROVED) || newStatus.equals(ApplicationStatusForEvaluation.REJECTED)) && application.getStatus().equals(ApplicationStatusTypeEnum.EVALUATION.getValue())) { + application.setStatus(newStatus.getValue()); + } application = applicationRepository.save(application); /** This code is responsible for adding a version history log for the "Update Application" operation. **/ @@ -1899,7 +1917,7 @@ public class ApplicationEvaluationDao { application.setDateAccepted(DateTimeUtil.DateServerToUTC(LocalDateTime.now())); application.setUpdatedDate(DateTimeUtil.DateServerToUTC(LocalDateTime.now())); application = applicationRepository.save(application); - emailNotificationDao.sendAdmissibilityNotificationEmailForApprovedApplication(application); +// emailNotificationDao.sendAdmissibilityNotificationEmailForApprovedApplication(application); } if (Boolean.TRUE.equals(statusType.equals((ApplicationStatusTypeEnum.REJECTED.getValue())))) { application.setDateRejected(DateTimeUtil.DateServerToUTC(LocalDateTime.now())); @@ -2419,5 +2437,43 @@ public class ApplicationEvaluationDao { } return false; } + private void processTechnicalEvaluation(Long applicationId, ApplicationEntity applicationEntity, ApplicationStatusForEvaluation status){ + Optional evaluationEntityOpt = applicationEvaluationRepository.findByApplicationIdAndIsDeletedFalse(applicationId); + if (evaluationEntityOpt.isPresent()){ + ApplicationEvaluationEntity evaluationEntity = evaluationEntityOpt.get(); + String criteriaJson = evaluationEntity.getCriteria(); + if (criteriaJson != null){ + Integer totalScore = calculateTotalScore(evaluationEntity.getCriteria()); + if (totalScore > 40) { + applicationEntity.setStatus(status.getValue()); + log.info("Status updated to TECHNICAL_EVALUATION for applicationId: " + applicationId); + } + else{ + throw new CustomValidationException(Status.BAD_REQUEST,Translator.toLocale(GepafinConstant.INSUFFICIENT_SCORE_MESSAGE)); + } + } + } + } + + private Integer calculateTotalScore(String criteriaJson){ + try { + ObjectMapper objectMapper = new ObjectMapper(); + // Convert JSON string to List of Maps + List> criteriaList = objectMapper.readValue(criteriaJson, new TypeReference<>() { + }); + + // Sum all scores (ignoring null scores) + Integer totalScore = criteriaList.stream() + .mapToInt(obj -> obj.get("score") != null ? ((Number) obj.get("score")).intValue() : 0) + .sum(); + + return totalScore; + } + catch (Exception e) { + log.error(" Error parsing criteria JSON: {}", e.getMessage()); + return 0; + } + } + } diff --git a/src/main/java/net/gepafin/tendermanagement/dao/CallDao.java b/src/main/java/net/gepafin/tendermanagement/dao/CallDao.java index a913713f..bef8c76f 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/CallDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/CallDao.java @@ -4,17 +4,18 @@ 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.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.*; @@ -571,7 +572,7 @@ public class CallDao { } } - public CallResponse updateCallStep1(CallEntity callEntity, UpdateCallRequestStep1 updateCallRequest, UserEntity userEntity) { + public CallResponse updateCallStep1(HttpServletRequest request,CallEntity callEntity, UpdateCallRequestStep1 updateCallRequest, UserEntity userEntity) { CallEntity oldCallEntity = Utils.getClonedEntityForData(callEntity); isValidDateRange(updateCallRequest, callEntity); setIfUpdated(callEntity::getName, callEntity::setName, updateCallRequest.getName()); @@ -580,15 +581,49 @@ public class CallDao { 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) { - setIfUpdated(callEntity::getEndDate, callEntity::setEndDate, dates.get(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 (isEndDateUpdated || isEndTimeUpdated) { + + 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()); @@ -606,7 +641,6 @@ public class CallDao { 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::getEndTime, callEntity::setEndTime, DateTimeUtil.parseTime(updateCallRequest.getEndTime())); setIfUpdated(callEntity::getConfidi, callEntity::setConfidi, updateCallRequest.getConfidi()); setIfUpdated(callEntity::getEvaluationVersion, callEntity::setEvaluationVersion, updateCallRequest.getEvaluationVersion().getValue()); setIfUpdated(callEntity::getNumberOfCheck, callEntity::setNumberOfCheck, updateCallRequest.getNumberOfCheck()); @@ -1025,10 +1059,14 @@ public class CallDao { 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(); @@ -1071,7 +1109,7 @@ public class CallDao { .toList(); predicates.add(root.get(GepafinConstant.STATUS).in(statusValues)); } - + applyFilters(root, criteriaBuilder, predicates, filters); predicates.add(criteriaBuilder.equal(root.get(GepafinConstant.HUB).get(GepafinConstant.ID), userEntity.getHub().getId())); @@ -1079,6 +1117,130 @@ public class CallDao { } + 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) { convertToDocumentEntities(createCallRequest.getDocs(), callEntity.getId(), DocumentTypeEnum.DOCUMENT); @@ -1089,4 +1251,4 @@ public class CallDao { createCallResponseBean.setCurrentStep(GepafinConstant.EVALUATION_V2_STEP_2); return createCallResponseBean; } -} + } diff --git a/src/main/java/net/gepafin/tendermanagement/dao/CompanyDocumentDao.java b/src/main/java/net/gepafin/tendermanagement/dao/CompanyDocumentDao.java index 9cee39b6..0ef3349a 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/CompanyDocumentDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/CompanyDocumentDao.java @@ -1,5 +1,6 @@ package net.gepafin.tendermanagement.dao; +import com.amazonaws.services.s3.AmazonS3; import com.amazonaws.services.s3.AmazonS3Client; import com.amazonaws.services.s3.model.CopyObjectRequest; import jakarta.persistence.criteria.Predicate; @@ -33,6 +34,7 @@ import org.springframework.data.jpa.domain.Specification; import org.springframework.stereotype.Component; import org.springframework.web.multipart.MultipartFile; +import java.net.URL; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; @@ -86,6 +88,9 @@ public class CompanyDocumentDao { @Autowired private Validator validator; + @Autowired + private AmazonS3 amazonS3; + public List uploadFileForCompany(HttpServletRequest request, Long userId, List files, Long companyId, Long documentCategoryId, CompanyDocumentTypeEnum companyDocumentSourceTypeEnum, LocalDateTime expirationDate,String name){ DocumentCategoryEntity categoryEntity = categoryDao.validateCategory(documentCategoryId); validator.validateUserWithCompany(request,companyId); @@ -271,8 +276,11 @@ public class CompanyDocumentDao { s3Client.copyObject(copyRequest); log.info("File copied successfully from {} to {}", oldS3Path, newS3Path); + URL amazonS3Url = amazonS3.getUrl(bucketName, newS3Path); + String fileUrl = amazonS3Url.toString(); + DocumentEntity entity = new DocumentEntity(); - entity.setFilePath(newS3Path); + entity.setFilePath(fileUrl); entity.setFileName(companyDocumentEntity.getFileName()); entity.setSource(DocumentSourceTypeEnum.APPLICATION.getValue()); entity.setType(documentTypeEnum.getValue()); @@ -306,6 +314,8 @@ public class CompanyDocumentDao { return (root, query, builder) -> { Predicate predicate = builder.equal(root.get("companyId"), companyId); + predicate = builder.and(predicate, builder.isFalse(root.get("isDeleted"))); + if (typeEnum != null) { if (typeEnum == CompanyDocumentTypeEnum.COMPANY_DOCUMENT) { // Case 1: Fetch only COMPANY_DOCUMENT type documents for the given company diff --git a/src/main/java/net/gepafin/tendermanagement/dao/EmailNotificationDao.java b/src/main/java/net/gepafin/tendermanagement/dao/EmailNotificationDao.java index 3988b35a..25b54319 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/EmailNotificationDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/EmailNotificationDao.java @@ -249,7 +249,7 @@ public class EmailNotificationDao { sendEmail(applicationEntity, SystemEmailTemplatesEntity.SystemEmailTemplatesEntityTypeEnum.INADMISSIBILITY_NOTIFICATION_DUE_TO_FAILURE, bodyPlaceholders, null,amendmentRequest.getId()); } - public void sendAdmissibilityNotificationEmailForApprovedApplication(ApplicationEntity applicationEntity) { + public void sendAdmissibilityNotificationEmailForAdmissibleApplication(ApplicationEntity applicationEntity) { Map bodyPlaceholders = new HashMap<>(); bodyPlaceholders.put("{{call_name}}", applicationEntity.getCall().getName()); bodyPlaceholders.put("{{protocol_number}}", applicationEntity.getProtocol().getProtocolNumber().toString()); diff --git a/src/main/java/net/gepafin/tendermanagement/dao/PdfDao.java b/src/main/java/net/gepafin/tendermanagement/dao/PdfDao.java index 0569e28d..68129e98 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/PdfDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/PdfDao.java @@ -560,7 +560,7 @@ public class PdfDao { } // Process 'fileupload' and 'checkboxes' cases as in the original logic - if (name.equals("fileupload")) { + if (name.equals("fileupload") || name.equals("fileselect")) { if (fieldValue instanceof List && ((List) fieldValue).stream().allMatch(item -> item instanceof DocumentResponseBean)) { List documentList = (List) fieldValue; List names = documentList.stream() diff --git a/src/main/java/net/gepafin/tendermanagement/enums/ApplicationStatusForEvaluation.java b/src/main/java/net/gepafin/tendermanagement/enums/ApplicationStatusForEvaluation.java index 61c39b8e..f0f6acb8 100644 --- a/src/main/java/net/gepafin/tendermanagement/enums/ApplicationStatusForEvaluation.java +++ b/src/main/java/net/gepafin/tendermanagement/enums/ApplicationStatusForEvaluation.java @@ -4,7 +4,9 @@ import com.fasterxml.jackson.annotation.JsonValue; public enum ApplicationStatusForEvaluation { APPROVED("APPROVED"), - REJECTED("REJECTED"); + REJECTED("REJECTED"), + ADMISSIBLE("ADMISSIBLE"), + TECHNICAL_EVALUATION("TECHNICAL_EVALUATION"); private String value; diff --git a/src/main/java/net/gepafin/tendermanagement/enums/ApplicationStatusTypeEnum.java b/src/main/java/net/gepafin/tendermanagement/enums/ApplicationStatusTypeEnum.java index b6fd00b3..041ec85a 100644 --- a/src/main/java/net/gepafin/tendermanagement/enums/ApplicationStatusTypeEnum.java +++ b/src/main/java/net/gepafin/tendermanagement/enums/ApplicationStatusTypeEnum.java @@ -15,7 +15,8 @@ public enum ApplicationStatusTypeEnum { EVALUATION("EVALUATION"), APPOINTMENT("APPOINTMENT"), NDG("NDG"), - ADMISSIBLE("ADMISSIBLE"); + ADMISSIBLE("ADMISSIBLE"), + TECHNICAL_EVALUATION("TECHNICAL_EVALUATION"); private String value; diff --git a/src/main/java/net/gepafin/tendermanagement/enums/EmailScenarioTypeEnum.java b/src/main/java/net/gepafin/tendermanagement/enums/EmailScenarioTypeEnum.java index 415f1dd2..479912d7 100644 --- a/src/main/java/net/gepafin/tendermanagement/enums/EmailScenarioTypeEnum.java +++ b/src/main/java/net/gepafin/tendermanagement/enums/EmailScenarioTypeEnum.java @@ -8,7 +8,7 @@ public enum EmailScenarioTypeEnum { APPLICATION_AMENDMENT_REQUESTED("APPLICATION_AMENDMENT_REQUESTED"), APPLICATION_AMENDMENT_EXPIRED("APPLICATION_AMENDMENT_EXPIRED"), APPLICATION_AMENDMENT_REMINDER("APPLICATION_AMENDMENT_REMINDER"), - APPLICATION_APPROVED("APPLICATION_APPROVED"), + APPLICATION_ADMISSIBLE("APPLICATION_ADMISSIBLE"), USER_CREATION("USER_CREATION"), PASSWORD_RESET_REQUEST("PASSWORD_RESET_REQUEST"), APPLICATION_REJECTED("APPLICATION_REJECTED"); diff --git a/src/main/java/net/gepafin/tendermanagement/enums/MatchModeEnum.java b/src/main/java/net/gepafin/tendermanagement/enums/MatchModeEnum.java index f3ee252c..878c7f85 100644 --- a/src/main/java/net/gepafin/tendermanagement/enums/MatchModeEnum.java +++ b/src/main/java/net/gepafin/tendermanagement/enums/MatchModeEnum.java @@ -4,14 +4,14 @@ import com.fasterxml.jackson.annotation.JsonValue; public enum MatchModeEnum { - STARTSWITH("Starts with"), - ENDSWITH("Ends with"), - CONTAINS("Contains"), - EQUALS("Equals"), - DATEIS("Date is"), - DATEISNOT("Date is not"), - BEFORE("Date is before"), - AFTER("Date is after"); + STARTSWITH("startsWith"), + ENDSWITH("endsWith"), + CONTAINS("contains"), + EQUALS("equals"), + DATEIS("dateIs"), + DATEISNOT("dateIsNot"), + BEFORE("dateBefore"), + AFTER("dateAfter"); private String value; diff --git a/src/main/java/net/gepafin/tendermanagement/enums/UserActionContextEnum.java b/src/main/java/net/gepafin/tendermanagement/enums/UserActionContextEnum.java index 6b5b8ea4..95e140e3 100644 --- a/src/main/java/net/gepafin/tendermanagement/enums/UserActionContextEnum.java +++ b/src/main/java/net/gepafin/tendermanagement/enums/UserActionContextEnum.java @@ -213,7 +213,8 @@ public enum UserActionContextEnum { GET_ALL_ASSIGNED_APPLICATION_BY_PAGINATION("GET_ALL_ASSIGNED_APPLICATION_BY_PAGINATION"), GET_ALL_APPLICATION_AMENDMENT_BY_PAGINATION("GET_ALL_APPLICATION_AMENDMENT_BY_PAGINATION"), GET_ALL_USER_ACTION_BY_PAGINATION("GET_ALL_USER_ACTION_BY_PAGINATION"), - GET_ALL_USER_BY_PAGINATION("GET_ALL_USER_BY_PAGINATION"); + GET_ALL_USER_BY_PAGINATION("GET_ALL_USER_BY_PAGINATION"), + UPDATE_CALL_END_DATE_AND_TIME("UPDATE_CALL_END_DATE_AND_TIME"); private final String value; diff --git a/src/main/java/net/gepafin/tendermanagement/model/request/CallPageableRequestBean.java b/src/main/java/net/gepafin/tendermanagement/model/request/CallPageableRequestBean.java index 8b848c85..260a45e5 100644 --- a/src/main/java/net/gepafin/tendermanagement/model/request/CallPageableRequestBean.java +++ b/src/main/java/net/gepafin/tendermanagement/model/request/CallPageableRequestBean.java @@ -4,6 +4,7 @@ import lombok.Data; import net.gepafin.tendermanagement.enums.CallStatusEnum; import java.util.List; +import java.util.Map; @Data public class CallPageableRequestBean { @@ -11,4 +12,6 @@ public class CallPageableRequestBean { private GlobalFilters globalFilters; private List status; + + private Map filters; } diff --git a/src/main/java/net/gepafin/tendermanagement/repositories/ApplicationRepository.java b/src/main/java/net/gepafin/tendermanagement/repositories/ApplicationRepository.java index 6eba956e..3b817314 100644 --- a/src/main/java/net/gepafin/tendermanagement/repositories/ApplicationRepository.java +++ b/src/main/java/net/gepafin/tendermanagement/repositories/ApplicationRepository.java @@ -8,8 +8,6 @@ import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; import java.math.BigDecimal; -import java.time.LocalDate; -import java.time.LocalDateTime; import java.util.List; import java.util.Optional; @@ -112,24 +110,31 @@ public interface ApplicationRepository extends JpaRepository findRequestedVsApprovedAmountsPerMonth( - @Param("hubId") Long hubId, - @Param("userId") Long userId, - @Param("userWithCompanyId") Long userWithCompanyId - ); + @Query(value = """ + WITH months AS ( + SELECT TO_CHAR(generate_series(CURRENT_DATE - INTERVAL '5 months', CURRENT_DATE, INTERVAL '1 month'), 'Mon') AS month_label, + EXTRACT(YEAR FROM generate_series(CURRENT_DATE - INTERVAL '5 months', CURRENT_DATE, INTERVAL '1 month')) AS year_value, + EXTRACT(MONTH FROM generate_series(CURRENT_DATE - INTERVAL '5 months', CURRENT_DATE, INTERVAL '1 month')) AS month_value + ) + SELECT m.month_label AS month, + COALESCE(SUM(a.amount_requested), 0) AS totalRequested, + COALESCE(SUM(a.amount_accepted), 0) AS totalApproved + FROM months m + LEFT JOIN {h-schema}application a ON EXTRACT(YEAR FROM a.date_accepted) = m.year_value + AND EXTRACT(MONTH FROM a.date_accepted) = m.month_value + AND a.is_deleted = false + AND a.status = 'APPROVED' + AND a.hub_id = :hubId + AND a.user_id = :userId + AND a.user_with_company_id = :userWithCompanyId + GROUP BY m.month_label, m.year_value, m.month_value + ORDER BY m.year_value, m.month_value + """, nativeQuery = true) + List findRequestedVsApprovedAmountsPerMonth(@Param("hubId") Long hubId, + @Param("userId") Long userId, + @Param("userWithCompanyId") Long userWithCompanyId); + + @Query("SELECT COUNT(a) FROM ApplicationEntity a " + "WHERE a.hubId = :hubId " + @@ -164,4 +169,5 @@ public interface ApplicationRepository extends JpaRepository(ex.getErrors(), ex.getStatus(), ex.getMessage()); } + @ResponseStatus(value = HttpStatus.NOT_FOUND) @ExceptionHandler(ResourceNotFoundException.class) public ResponseEntity> handleResourceNotFoundException(ResourceNotFoundException ex) { log.error(ex.getMessage()); @@ -178,5 +180,14 @@ public class GlobalExceptionHandler { String exceptionString = ex.getMessage().substring(ex.getMessage().indexOf("]: [") + 4, ex.getMessage().length() - 1); return Utils.convertIntoJson(exceptionString); } - + + @ResponseStatus(value = HttpStatus.NOT_FOUND) + @ExceptionHandler(NoResourceFoundException.class) + public ResponseEntity> handlNoeResourceNotFoundException(NoResourceFoundException ex) { + log.error(ex.getMessage()); +// log.error(ex.getLocalizedMessage(), ex); + return ResponseEntity.status(HttpStatus.NOT_FOUND) + .body(new Response<>(null, Status.NOT_FOUND, ex.getMessage())); + } + } diff --git a/src/main/resources/db/changelog/db.changelog-1.0.0.xml b/src/main/resources/db/changelog/db.changelog-1.0.0.xml index 294865ea..04ed853f 100644 --- a/src/main/resources/db/changelog/db.changelog-1.0.0.xml +++ b/src/main/resources/db/changelog/db.changelog-1.0.0.xml @@ -2532,6 +2532,11 @@ newColumnName="appointment_template_id"/> + + + + @@ -2545,5 +2550,75 @@ - + + + + select + setval('gepafin_schema.form_field_id_seq', (select + max(id)+1 + from gepafin_schema.form_field), false) + + + + + select + setval('gepafin_schema.role_id_seq', (select + max(id)+1 + from gepafin_schema.role), false) + + select + setval('gepafin_schema.beneficiary_id_seq', (select + max(id)+1 + from gepafin_schema.beneficiary), false) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/db/dump/update_form_field_data_03-03-2025.sql b/src/main/resources/db/dump/update_form_field_data_03-03-2025.sql new file mode 100644 index 00000000..8f377847 --- /dev/null +++ b/src/main/resources/db/dump/update_form_field_data_03-03-2025.sql @@ -0,0 +1,13 @@ + +INSERT INTO FORM_FIELD (SORT_ORDER, NAME, LABEL, DESCRIPTION, SETTINGS, VALIDATORS, CREATED_DATE, UPDATED_DATE) +VALUES +( + 23, + 'fileselect', + 'Caricamento File', + 'Per selezionare di documenti o immagini', + '[{name: "label",value: "Seleziona File"},{ name: "isDelegation", value: false }]', + '{"isRequired":false}', + CURRENT_TIMESTAMP, + CURRENT_TIMESTAMP +); \ No newline at end of file diff --git a/src/main/resources/db/dump/updated_system_email_template_email_scenario_27_02_2025.sql b/src/main/resources/db/dump/updated_system_email_template_email_scenario_27_02_2025.sql new file mode 100644 index 00000000..2605b579 --- /dev/null +++ b/src/main/resources/db/dump/updated_system_email_template_email_scenario_27_02_2025.sql @@ -0,0 +1,2 @@ + +UPDATE gepafin_schema.system_email_template SET email_scenario='APPLICATION_ADMISSIBLE' WHERE "type"='ADMISSIBILITY_NOTIFICATION' AND "system"=true ; \ No newline at end of file diff --git a/src/main/resources/message_en.properties b/src/main/resources/message_en.properties index d29c4a80..c7b9a3f3 100644 --- a/src/main/resources/message_en.properties +++ b/src/main/resources/message_en.properties @@ -390,9 +390,9 @@ company.document.copied.successfully = Company Document Copied successfully. invalid.expiration.date = Invalid Expiration Date - appointment.cannot.be.created = Appointment cannot be created because call doesn't have the template id. appointment.not.created = Appointment not created please try again. validation.failed.checklist=Validation failed for checklist. error.invalid.limit=Limit should be between 1 and 3000. +insufficient.score.msg = Insufficient score to pass to the technical and economic-financial evaluation diff --git a/src/main/resources/message_it.properties b/src/main/resources/message_it.properties index 6aa57aa3..88d38ab1 100644 --- a/src/main/resources/message_it.properties +++ b/src/main/resources/message_it.properties @@ -386,3 +386,4 @@ appointment.not.created = Appuntamento non creato, riprova validation.failed.checklist=Convalida fallita per la checklist. error.invalid.limit=Il limite dovrebbe essere compreso tra 1 e 3000. +insufficient.score.msg = Punteggio non sufficiente per passaggio alla valutazione tecnica ed economico finanziaria