From 8e0cffb61c57536030c0e9dd9408242fbe913024 Mon Sep 17 00:00:00 2001 From: rajesh Date: Wed, 8 Apr 2026 15:53:34 +0530 Subject: [PATCH] Done ticket GEPAFINBE-6326 Ranking Management --- .../constants/GepafinConstant.java | 6 + .../tendermanagement/dao/ApplicationDao.java | 117 ++++++++++ .../gepafin/tendermanagement/dao/CallDao.java | 71 ++++++- .../entities/ApplicationEntity.java | 6 + .../entities/ApplicationRankingView.java | 68 ++++++ .../tendermanagement/entities/CallEntity.java | 3 + .../ApplicationRankingActionTypeEnum.java | 20 ++ .../enums/CallRankingTypeEnum.java | 20 ++ .../enums/UserActionContextEnum.java | 5 +- .../ApplicationRankingActionRequest.java | 13 ++ .../model/request/CreateCallRequestStep1.java | 3 + .../model/request/UpdateCallRequestStep1.java | 5 +- .../response/ApplicationRankingResponse.java | 28 +++ .../model/response/ApplicationResponse.java | 5 + .../response/CallRankingSummaryResponse.java | 19 ++ .../model/response/CallResponse.java | 3 + .../ApplicationRankingViewRepository.java | 11 + .../repositories/ApplicationRepository.java | 1 + .../service/ApplicationService.java | 7 + .../tendermanagement/service/CallService.java | 3 + .../service/impl/ApplicationServiceImpl.java | 17 ++ .../service/impl/CallServiceImpl.java | 16 ++ .../web/rest/api/RankingApi.java | 84 ++++++++ .../rest/api/impl/RankingApiController.java | 74 +++++++ .../db/changelog/db.changelog-1.0.0.xml | 18 ++ ...te_application_ranking_view_07_04_2026.sql | 201 ++++++++++++++++++ src/main/resources/message_en.properties | 6 + src/main/resources/message_it.properties | 12 +- 28 files changed, 834 insertions(+), 8 deletions(-) create mode 100644 src/main/java/net/gepafin/tendermanagement/entities/ApplicationRankingView.java create mode 100644 src/main/java/net/gepafin/tendermanagement/enums/ApplicationRankingActionTypeEnum.java create mode 100644 src/main/java/net/gepafin/tendermanagement/enums/CallRankingTypeEnum.java create mode 100644 src/main/java/net/gepafin/tendermanagement/model/request/ApplicationRankingActionRequest.java create mode 100644 src/main/java/net/gepafin/tendermanagement/model/response/ApplicationRankingResponse.java create mode 100644 src/main/java/net/gepafin/tendermanagement/model/response/CallRankingSummaryResponse.java create mode 100644 src/main/java/net/gepafin/tendermanagement/repositories/ApplicationRankingViewRepository.java create mode 100644 src/main/java/net/gepafin/tendermanagement/web/rest/api/RankingApi.java create mode 100644 src/main/java/net/gepafin/tendermanagement/web/rest/api/impl/RankingApiController.java create mode 100644 src/main/resources/db/dump/create_application_ranking_view_07_04_2026.sql diff --git a/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java b/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java index 43e5d1ea..dbdf043b 100644 --- a/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java +++ b/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java @@ -108,6 +108,12 @@ public class GepafinConstant { public static final String INVALID_STATUS_CHANGE_FROM_PUBLISH_TO_DRAFT = "invalid.status.change.from.publish.to.draft"; public static final String STATUS_CANNOT_BE_CHANGED = "status.cannot.be.changed"; public static final String PUBLISHED_CALL_NOT_UPDATE = "published.call.not.update"; + public static final String PUBLISHED_CALL_STEP1_ONLY_RANKING_TYPE_ALLOWED = "published.call.step1.only.ranking.type.allowed"; + public static final String CALL_RANKING_TYPE_UPDATED_SUCCESSFULLY = "call.ranking.type.updated.successfully"; + public static final String APPLICATION_RANKING_ACTION_UPDATED_SUCCESSFULLY = "application.ranking.action.updated.successfully"; + public static final String APPLICATION_RANKING_FETCHED_SUCCESSFULLY = "application.ranking.fetched.successfully"; + public static final String CALL_MUST_BE_CLOSED_FOR_RANKING_ACTION = "call.must.be.closed.for.ranking.action"; + public static final String APPLICATION_RANKING_ACTION_INVALID = "application.ranking.action.invalid"; public static final String INVALID_USER = "invalid_user"; public static final String FLOW_CREATED_SUCCESSFULLY = "flow.created.successfully"; public static final String FLOW_FETCHED_SUCCESSFULLY = "flow.fetched.successfully"; diff --git a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java index 346554b6..631f8733 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java @@ -72,6 +72,7 @@ import java.time.OffsetDateTime; import java.time.temporal.ChronoUnit; import java.time.format.DateTimeFormatter; import java.util.*; +import java.util.Objects; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -215,6 +216,9 @@ public class ApplicationDao { @Autowired private ApplicationFormViewRepository applicationFormViewRepository; + @Autowired + private ApplicationRankingViewRepository applicationRankingViewRepository; + @Autowired private FormRepository formRepository; @@ -509,6 +513,11 @@ public class ApplicationDao { responseBean.setDateAccepted(applicationEntity.getDateAccepted()); responseBean.setDateRejected(applicationEntity.getDateRejected()); responseBean.setNdg(applicationEntity.getNdg()); + if (applicationEntity.getRankingActionType() != null && !applicationEntity.getRankingActionType().isBlank()) { + responseBean.setRankingActionType( + ApplicationRankingActionTypeEnum.valueOf(applicationEntity.getRankingActionType().trim())); + } + responseBean.setManualRanking(applicationEntity.getManualRanking()); return responseBean; } @@ -1152,6 +1161,114 @@ public class ApplicationDao { return getApplicationResponse(applicationEntity); } + public ApplicationResponse updateApplicationRankingAction(HttpServletRequest request, Long applicationId, + ApplicationRankingActionTypeEnum rankingActionType, Long manualRanking) { + ApplicationEntity applicationEntity = validateApplication(applicationId); + validator.validateRequest(request, RoleStatusEnum.ROLE_SUPER_ADMIN); + validateCallClosedForRankingAction(applicationEntity.getCall()); + if (!ApplicationStatusTypeEnum.APPROVED.getValue().equals(applicationEntity.getStatus())) { + throw new CustomValidationException(Status.BAD_REQUEST, + Translator.toLocale(GepafinConstant.APPLICATION_RANKING_ACTION_INVALID)); + } + validateRankingActionRequest(rankingActionType, manualRanking); + ApplicationEntity oldApplicationEntity = Utils.getClonedEntityForData(applicationEntity); + if (rankingActionType == null) { + applicationEntity.setRankingActionType(null); + applicationEntity.setManualRanking(null); + } else { + if (rankingActionType == ApplicationRankingActionTypeEnum.REPOSITION + && applicationRepository.existsByCallIdAndManualRankingAndIsDeletedFalseAndIdNot( + applicationEntity.getCall().getId(), manualRanking, applicationEntity.getId())) { + throw new CustomValidationException(Status.BAD_REQUEST, + Translator.toLocale(GepafinConstant.APPLICATION_RANKING_ACTION_INVALID)); + } + applicationEntity.setRankingActionType(rankingActionType.getValue()); + applicationEntity.setManualRanking( + rankingActionType == ApplicationRankingActionTypeEnum.REPOSITION ? manualRanking : null); + } + applicationEntity = applicationRepository.save(applicationEntity); + loggingUtil.addVersionHistory( + VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE) + .oldData(oldApplicationEntity).newData(applicationEntity).build()); + return getApplicationResponse(applicationEntity); + } + + public CallRankingSummaryResponse getApplicationRanking(Long callId, + List rankingActionTypes) { + CallEntity call = callRepository.findById(callId) + .orElseThrow(() -> new ResourceNotFoundException(Status.NOT_FOUND, + Translator.toLocale(GepafinConstant.CALL_NOT_FOUND))); + + Specification spec = (root, query, criteriaBuilder) -> { + List predicates = new ArrayList<>(); + predicates.add(criteriaBuilder.equal(root.get("callId"), callId)); + if (rankingActionTypes != null && !rankingActionTypes.isEmpty()) { + List types = rankingActionTypes.stream() + .filter(Objects::nonNull) + .map(ApplicationRankingActionTypeEnum::getValue) + .distinct() + .collect(Collectors.toList()); + if (!types.isEmpty()) { + predicates.add(root.get("rankingActionType").in(types)); + } + } + query.orderBy(criteriaBuilder.asc(root.get("rank"))); + return criteriaBuilder.and(predicates.toArray(new jakarta.persistence.criteria.Predicate[0])); + }; + List rows = applicationRankingViewRepository.findAll(spec); + + CallRankingSummaryResponse summary = new CallRankingSummaryResponse(); + summary.setCallId(call.getId()); + summary.setCallName(call.getName()); + summary.setAmount(call.getAmount()); + if (call.getRankingType() != null && !call.getRankingType().isBlank()) { + summary.setRankingType(CallRankingTypeEnum.valueOf(call.getRankingType().trim())); + } + summary.setApplications(rows.stream() + .map(this::convertToApplicationRankingResponse) + .collect(Collectors.toList())); + return summary; + } + + private ApplicationRankingResponse convertToApplicationRankingResponse(ApplicationRankingView entity) { + ApplicationRankingResponse response = new ApplicationRankingResponse(); + response.setApplicationId(entity.getApplicationId()); + response.setCallId(entity.getCallId()); + response.setUserId(entity.getUserId()); + response.setStatus(entity.getStatus()); + response.setSubmissionDate(entity.getSubmissionDate()); + response.setProtocolDatetime(entity.getProtocolDatetime()); + response.setProtocolNumber(entity.getProtocolNumber()); + response.setNdg(entity.getNdg()); + response.setAmountAccepted(entity.getAmountAccepted()); + response.setPecEmail(entity.getPecEmail()); + response.setManualRanking(entity.getManualRanking()); + response.setRank(entity.getRank()); + response.setTotalScore(entity.getTotalScore()); + if (entity.getRankingActionType() != null && !entity.getRankingActionType().isBlank()) { + response.setRankingActionType(ApplicationRankingActionTypeEnum.valueOf(entity.getRankingActionType().trim())); + } + if (entity.getRankingType() != null && !entity.getRankingType().isBlank()) { + response.setRankingType(CallRankingTypeEnum.valueOf(entity.getRankingType().trim())); + } + return response; + } + + private void validateCallClosedForRankingAction(CallEntity callEntity) { + if (!CallStatusEnum.EXPIRED.getValue().equals(callEntity.getStatus())) { + throw new CustomValidationException(Status.BAD_REQUEST, + Translator.toLocale(GepafinConstant.CALL_MUST_BE_CLOSED_FOR_RANKING_ACTION)); + } + } + + private void validateRankingActionRequest(ApplicationRankingActionTypeEnum rankingActionType, Long manualRanking) { + if (rankingActionType == ApplicationRankingActionTypeEnum.REPOSITION + && (manualRanking == null || manualRanking <= 0)) { + throw new CustomValidationException(Status.BAD_REQUEST, + Translator.toLocale(GepafinConstant.APPLICATION_RANKING_ACTION_INVALID)); + } + } + /** * Sets all non-terminal amendments, the application evaluation (if any), and the assigned application row to CLOSE. */ diff --git a/src/main/java/net/gepafin/tendermanagement/dao/CallDao.java b/src/main/java/net/gepafin/tendermanagement/dao/CallDao.java index f5c2b7c0..fddfe702 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/CallDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/CallDao.java @@ -228,6 +228,9 @@ public class CallDao { if (createCallRequest.getAllowMultipleApplications() != null) { callEntity.setAllowMultipleApplications(createCallRequest.getAllowMultipleApplications()); } + if (createCallRequest.getRankingType() != null) { + callEntity.setRankingType(createCallRequest.getRankingType().getValue()); + } callEntity = callRepository.save(callEntity); log.info("CallEntity saved with ID: {} for call name: '{}'", callEntity.getId(), callEntity.getName()); @@ -418,6 +421,9 @@ public class CallDao { createCallResponseBean.setEmail(callEntity.getEmail()); createCallResponseBean.setCreatedDate(callEntity.getCreatedDate()); createCallResponseBean.setUpdatedDate(callEntity.getUpdatedDate()); + if (callEntity.getRankingType() != null && !callEntity.getRankingType().isBlank()) { + createCallResponseBean.setRankingType(CallRankingTypeEnum.valueOf(callEntity.getRankingType().trim())); + } return createCallResponseBean; } @@ -583,6 +589,41 @@ public class CallDao { } } + private boolean hasNonRankingStep1Field(UpdateCallRequestStep1 r) { + return r.getName() != null + || r.getDescriptionShort() != null + || r.getDescriptionLong() != null + || r.getDates() != null + || r.getAmount() != null + || r.getAmountMax() != null + || r.getAimedTo() != null + || r.getDocumentationRequested() != null + || r.getAmountMin() != null + || r.getEmail() != null + || r.getPhoneNumber() != null + || r.getStartTime() != null + || r.getEndTime() != null + || r.getConfidi() != null + || r.getAllowMultipleApplications() != null + || r.getFaq() != null + || r.getNumberOfCheck() != null + || r.getAppointmentTemplateId() != null + || r.getEvaluationVersion() != null; + } + + private CallResponse updatePublishedCallStep1RankingTypeOnly(HttpServletRequest request, CallEntity callEntity, + CallEntity oldCallEntity, UpdateCallRequestStep1 updateCallRequest) { + setIfUpdated(callEntity::getRankingType, callEntity::setRankingType, + updateCallRequest.getRankingType() != null ? updateCallRequest.getRankingType().getValue() : null); + callEntity = callRepository.save(callEntity); + loggingUtil.addVersionHistory( + VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldCallEntity).newData(callEntity).build()); + CallResponse response = getCallResponseBean(callEntity); + response.setCurrentStep(GepafinConstant.STEP_1); + log.info("Published call step 1: ranking type only | callId={}", callEntity.getId()); + return response; + } + public void isValidDateRange(UpdateCallRequestStep1 updateCallRequest, CallEntity callEntity) { List dates = updateCallRequest.getDates(); @@ -615,6 +656,18 @@ public class CallDao { 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); + if (CallStatusEnum.PUBLISH.getValue().equals(callEntity.getStatus())) { + if (hasNonRankingStep1Field(updateCallRequest)) { + throw new CustomValidationException(Status.VALIDATION_ERROR, + Translator.toLocale(GepafinConstant.PUBLISHED_CALL_STEP1_ONLY_RANKING_TYPE_ALLOWED)); + } + if (updateCallRequest.getRankingType() != null) { + return updatePublishedCallStep1RankingTypeOnly(request, callEntity, oldCallEntity, updateCallRequest); + } + CallResponse unchanged = getCallResponseBean(callEntity); + unchanged.setCurrentStep(GepafinConstant.STEP_1); + return unchanged; + } isValidDateRange(updateCallRequest, callEntity); setIfUpdated(callEntity::getName, callEntity::setName, updateCallRequest.getName()); setIfUpdated(callEntity::getDescriptionShort, callEntity::setDescriptionShort, @@ -648,8 +701,6 @@ public class CallDao { if (!requestEndTime.equals(storedEndTime)) { setIfUpdated(callEntity::getEndTime, callEntity::setEndTime, DateTimeUtil.parseTime(updateCallRequest.getEndTime())); -// callEntity.setStatus(CallStatusEnum.PUBLISH.getValue()); -// callRepository.save(callEntity); isEndTimeUpdated = true; } } @@ -708,10 +759,15 @@ public class CallDao { 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()); + if (updateCallRequest.getEvaluationVersion() != null) { + 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()); + setIfUpdated(callEntity::getRankingType, callEntity::setRankingType, + updateCallRequest.getRankingType() != null ? updateCallRequest.getRankingType().getValue() : null); callEntity = callRepository.save(callEntity); /** This code is responsible for adding a version history log for the "update call step 1" operation **/ @@ -725,6 +781,15 @@ public class CallDao { return createCallResponseBean; } + public CallResponse updateCallRankingType(HttpServletRequest request, CallEntity callEntity, CallRankingTypeEnum rankingType) { + CallEntity oldCallEntity = Utils.getClonedEntityForData(callEntity); + callEntity.setRankingType(rankingType != null ? rankingType.getValue() : null); + callEntity = callRepository.save(callEntity); + loggingUtil.addVersionHistory( + VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldCallEntity).newData(callEntity).build()); + return getCallResponseBean(callEntity); + } + private void softDeleteFaq(FaqEntity faqEntity) { FaqEntity oldFaqEntity = Utils.getClonedEntityForData(faqEntity); diff --git a/src/main/java/net/gepafin/tendermanagement/entities/ApplicationEntity.java b/src/main/java/net/gepafin/tendermanagement/entities/ApplicationEntity.java index 7670d520..c8d93923 100644 --- a/src/main/java/net/gepafin/tendermanagement/entities/ApplicationEntity.java +++ b/src/main/java/net/gepafin/tendermanagement/entities/ApplicationEntity.java @@ -90,4 +90,10 @@ public class ApplicationEntity extends BaseEntity { @Column(name = "COMPANY_DOCUMENT") private String companyDocument; + + @Column(name = "ranking_action_type") + private String rankingActionType; + + @Column(name = "manual_ranking") + private Long manualRanking; } \ No newline at end of file diff --git a/src/main/java/net/gepafin/tendermanagement/entities/ApplicationRankingView.java b/src/main/java/net/gepafin/tendermanagement/entities/ApplicationRankingView.java new file mode 100644 index 00000000..ffa56217 --- /dev/null +++ b/src/main/java/net/gepafin/tendermanagement/entities/ApplicationRankingView.java @@ -0,0 +1,68 @@ +package net.gepafin.tendermanagement.entities; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import lombok.Data; +import org.hibernate.annotations.Immutable; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.time.LocalDateTime; + +/** + * Maps {@code application_ranking_view} (read-only). + */ +@Data +@Entity +@Immutable +@Table(name = "application_ranking_view") +public class ApplicationRankingView implements Serializable { + + @Id + @Column(name = "application_id") + private Long applicationId; + + @Column(name = "listing_rank") + private Long rank; + + @Column(name = "call_id") + private Long callId; + + @Column(name = "ranking_action_type") + private String rankingActionType; + + @Column(name = "total_score") + private BigDecimal totalScore; + + @Column(name = "user_id") + private Long userId; + + @Column(name = "status") + private String status; + + @Column(name = "submission_date") + private LocalDateTime submissionDate; + + @Column(name = "protocol_datetime") + private LocalDateTime protocolDatetime; + + @Column(name = "protocol_number") + private Long protocolNumber; + + @Column(name = "ndg") + private String ndg; + + @Column(name = "amount_accepted") + private BigDecimal amountAccepted; + + @Column(name = "pec_email") + private String pecEmail; + + @Column(name = "manual_ranking") + private Long manualRanking; + + @Column(name = "ranking_type") + private String rankingType; +} diff --git a/src/main/java/net/gepafin/tendermanagement/entities/CallEntity.java b/src/main/java/net/gepafin/tendermanagement/entities/CallEntity.java index dd2655ed..e7089670 100644 --- a/src/main/java/net/gepafin/tendermanagement/entities/CallEntity.java +++ b/src/main/java/net/gepafin/tendermanagement/entities/CallEntity.java @@ -102,5 +102,8 @@ public class CallEntity extends BaseEntity { @Column(name = "allow_multiple_applications") private Boolean allowMultipleApplications; + + @Column(name = "ranking_type") + private String rankingType; } diff --git a/src/main/java/net/gepafin/tendermanagement/enums/ApplicationRankingActionTypeEnum.java b/src/main/java/net/gepafin/tendermanagement/enums/ApplicationRankingActionTypeEnum.java new file mode 100644 index 00000000..44d0dbd1 --- /dev/null +++ b/src/main/java/net/gepafin/tendermanagement/enums/ApplicationRankingActionTypeEnum.java @@ -0,0 +1,20 @@ +package net.gepafin.tendermanagement.enums; + +import com.fasterxml.jackson.annotation.JsonValue; + +public enum ApplicationRankingActionTypeEnum { + + REMOVE("REMOVE"), + REPOSITION("REPOSITION"); + + private final String value; + + ApplicationRankingActionTypeEnum(String value) { + this.value = value; + } + + @JsonValue + public String getValue() { + return value; + } +} diff --git a/src/main/java/net/gepafin/tendermanagement/enums/CallRankingTypeEnum.java b/src/main/java/net/gepafin/tendermanagement/enums/CallRankingTypeEnum.java new file mode 100644 index 00000000..115218eb --- /dev/null +++ b/src/main/java/net/gepafin/tendermanagement/enums/CallRankingTypeEnum.java @@ -0,0 +1,20 @@ +package net.gepafin.tendermanagement.enums; + +import com.fasterxml.jackson.annotation.JsonValue; + +public enum CallRankingTypeEnum { + + SCORE("SCORE"), + PROTOCOL_DATE_TIME("PROTOCOL_DATE_TIME"); + + private final String value; + + CallRankingTypeEnum(String value) { + this.value = value; + } + + @JsonValue + public String getValue() { + return value; + } +} diff --git a/src/main/java/net/gepafin/tendermanagement/enums/UserActionContextEnum.java b/src/main/java/net/gepafin/tendermanagement/enums/UserActionContextEnum.java index 5cf65f56..6069386d 100644 --- a/src/main/java/net/gepafin/tendermanagement/enums/UserActionContextEnum.java +++ b/src/main/java/net/gepafin/tendermanagement/enums/UserActionContextEnum.java @@ -236,7 +236,10 @@ public enum UserActionContextEnum { REJECT_PEC_MAIL("REJECT_PEC_MAIL"), FETCH_EMAIL_LOG("FETCH_EMAIL_LOG"), FETCH_ALL_EMAIL_LOG("FETCH_ALL_EMAIL_LOG"), - UPLOAD_COMPANY_DOCUMENT_TO_APPLICATION("UPLOAD_COMPANY_DOCUMENT_TO_APPLICATION"); + UPLOAD_COMPANY_DOCUMENT_TO_APPLICATION("UPLOAD_COMPANY_DOCUMENT_TO_APPLICATION"), + UPDATE_CALL_RANKING_TYPE("UPDATE_CALL_RANKING_TYPE"), + UPDATE_APPLICATION_RANKING_ACTION("UPDATE_APPLICATION_RANKING_ACTION"), + GET_APPLICATION_RANKING("GET_APPLICATION_RANKING"); private final String value; diff --git a/src/main/java/net/gepafin/tendermanagement/model/request/ApplicationRankingActionRequest.java b/src/main/java/net/gepafin/tendermanagement/model/request/ApplicationRankingActionRequest.java new file mode 100644 index 00000000..a56c7368 --- /dev/null +++ b/src/main/java/net/gepafin/tendermanagement/model/request/ApplicationRankingActionRequest.java @@ -0,0 +1,13 @@ +package net.gepafin.tendermanagement.model.request; + +import lombok.Data; +import net.gepafin.tendermanagement.enums.ApplicationRankingActionTypeEnum; + +@Data +public class ApplicationRankingActionRequest { + + private ApplicationRankingActionTypeEnum rankingActionType; + + /** Required when {@code rankingActionType} is {@link ApplicationRankingActionTypeEnum#REPOSITION}. */ + private Long manualRanking; +} diff --git a/src/main/java/net/gepafin/tendermanagement/model/request/CreateCallRequestStep1.java b/src/main/java/net/gepafin/tendermanagement/model/request/CreateCallRequestStep1.java index c364db5a..b576cbf3 100644 --- a/src/main/java/net/gepafin/tendermanagement/model/request/CreateCallRequestStep1.java +++ b/src/main/java/net/gepafin/tendermanagement/model/request/CreateCallRequestStep1.java @@ -5,6 +5,7 @@ import java.time.LocalDateTime; import java.util.List; import lombok.Data; +import net.gepafin.tendermanagement.enums.CallRankingTypeEnum; import net.gepafin.tendermanagement.enums.EvaluationVersionEnum; @Data @@ -49,4 +50,6 @@ public class CreateCallRequestStep1 { private List faq; private EvaluationVersionEnum evaluationVersion; + + private CallRankingTypeEnum rankingType; } diff --git a/src/main/java/net/gepafin/tendermanagement/model/request/UpdateCallRequestStep1.java b/src/main/java/net/gepafin/tendermanagement/model/request/UpdateCallRequestStep1.java index 30530381..4df6cb5a 100644 --- a/src/main/java/net/gepafin/tendermanagement/model/request/UpdateCallRequestStep1.java +++ b/src/main/java/net/gepafin/tendermanagement/model/request/UpdateCallRequestStep1.java @@ -5,6 +5,7 @@ import java.time.LocalDateTime; import java.util.List; import lombok.Data; +import net.gepafin.tendermanagement.enums.CallRankingTypeEnum; import net.gepafin.tendermanagement.enums.EvaluationVersionEnum; @Data @@ -46,6 +47,8 @@ public class UpdateCallRequestStep1 { private Long appointmentTemplateId; - private EvaluationVersionEnum evaluationVersion; + private EvaluationVersionEnum evaluationVersion; + + private CallRankingTypeEnum rankingType; } diff --git a/src/main/java/net/gepafin/tendermanagement/model/response/ApplicationRankingResponse.java b/src/main/java/net/gepafin/tendermanagement/model/response/ApplicationRankingResponse.java new file mode 100644 index 00000000..f02f3276 --- /dev/null +++ b/src/main/java/net/gepafin/tendermanagement/model/response/ApplicationRankingResponse.java @@ -0,0 +1,28 @@ +package net.gepafin.tendermanagement.model.response; + +import lombok.Data; +import net.gepafin.tendermanagement.enums.ApplicationRankingActionTypeEnum; +import net.gepafin.tendermanagement.enums.CallRankingTypeEnum; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +@Data +public class ApplicationRankingResponse { + + private Long rank; + private Long applicationId; + private Long callId; + private ApplicationRankingActionTypeEnum rankingActionType; + private BigDecimal totalScore; + private Long userId; + private String status; + private LocalDateTime submissionDate; + private LocalDateTime protocolDatetime; + private Long protocolNumber; + private String ndg; + private BigDecimal amountAccepted; + private String pecEmail; + private Long manualRanking; + private CallRankingTypeEnum rankingType; +} diff --git a/src/main/java/net/gepafin/tendermanagement/model/response/ApplicationResponse.java b/src/main/java/net/gepafin/tendermanagement/model/response/ApplicationResponse.java index abbfd164..3c8f1113 100644 --- a/src/main/java/net/gepafin/tendermanagement/model/response/ApplicationResponse.java +++ b/src/main/java/net/gepafin/tendermanagement/model/response/ApplicationResponse.java @@ -1,6 +1,7 @@ package net.gepafin.tendermanagement.model.response; import lombok.Data; +import net.gepafin.tendermanagement.enums.ApplicationRankingActionTypeEnum; import net.gepafin.tendermanagement.enums.EvaluationVersionEnum; import net.gepafin.tendermanagement.model.response.ApplicationFormFieldResponseBean; @@ -54,4 +55,8 @@ public class ApplicationResponse{ private String ndg; + private ApplicationRankingActionTypeEnum rankingActionType; + + private Long manualRanking; + } \ No newline at end of file diff --git a/src/main/java/net/gepafin/tendermanagement/model/response/CallRankingSummaryResponse.java b/src/main/java/net/gepafin/tendermanagement/model/response/CallRankingSummaryResponse.java new file mode 100644 index 00000000..4568ef1e --- /dev/null +++ b/src/main/java/net/gepafin/tendermanagement/model/response/CallRankingSummaryResponse.java @@ -0,0 +1,19 @@ +package net.gepafin.tendermanagement.model.response; + +import lombok.Data; +import net.gepafin.tendermanagement.enums.CallRankingTypeEnum; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; + +/** Call-level ranking payload: tender info plus ordered ranked applications. */ +@Data +public class CallRankingSummaryResponse { + + private Long callId; + private String callName; + private BigDecimal amount; + private CallRankingTypeEnum rankingType; + private List applications; +} diff --git a/src/main/java/net/gepafin/tendermanagement/model/response/CallResponse.java b/src/main/java/net/gepafin/tendermanagement/model/response/CallResponse.java index 13a90715..48ce3363 100644 --- a/src/main/java/net/gepafin/tendermanagement/model/response/CallResponse.java +++ b/src/main/java/net/gepafin/tendermanagement/model/response/CallResponse.java @@ -7,6 +7,7 @@ import java.util.List; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import lombok.Data; +import net.gepafin.tendermanagement.enums.CallRankingTypeEnum; import net.gepafin.tendermanagement.enums.CallStatusEnum; import net.gepafin.tendermanagement.enums.EvaluationVersionEnum; import net.gepafin.tendermanagement.util.DynamicLocalTimeSerializer; @@ -83,6 +84,8 @@ public class CallResponse { private Long preferredCallId; private EvaluationVersionEnum evaluationVersion; + + private CallRankingTypeEnum rankingType; } diff --git a/src/main/java/net/gepafin/tendermanagement/repositories/ApplicationRankingViewRepository.java b/src/main/java/net/gepafin/tendermanagement/repositories/ApplicationRankingViewRepository.java new file mode 100644 index 00000000..7fbd36d3 --- /dev/null +++ b/src/main/java/net/gepafin/tendermanagement/repositories/ApplicationRankingViewRepository.java @@ -0,0 +1,11 @@ +package net.gepafin.tendermanagement.repositories; + +import net.gepafin.tendermanagement.entities.ApplicationRankingView; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.stereotype.Repository; + +@Repository +public interface ApplicationRankingViewRepository extends JpaRepository, + JpaSpecificationExecutor { +} diff --git a/src/main/java/net/gepafin/tendermanagement/repositories/ApplicationRepository.java b/src/main/java/net/gepafin/tendermanagement/repositories/ApplicationRepository.java index 37d2f270..72c2b678 100644 --- a/src/main/java/net/gepafin/tendermanagement/repositories/ApplicationRepository.java +++ b/src/main/java/net/gepafin/tendermanagement/repositories/ApplicationRepository.java @@ -183,5 +183,6 @@ public interface ApplicationRepository extends JpaRepository findByCallIdAndIsDeletedFalseAndStatusIn(Long callId,List status); + boolean existsByCallIdAndManualRankingAndIsDeletedFalseAndIdNot(Long callId, Long manualRanking, Long id); } diff --git a/src/main/java/net/gepafin/tendermanagement/service/ApplicationService.java b/src/main/java/net/gepafin/tendermanagement/service/ApplicationService.java index bce96ba9..66115c59 100644 --- a/src/main/java/net/gepafin/tendermanagement/service/ApplicationService.java +++ b/src/main/java/net/gepafin/tendermanagement/service/ApplicationService.java @@ -5,6 +5,7 @@ import jakarta.servlet.http.HttpServletResponse; import net.gepafin.tendermanagement.entities.ApplicationEntity; import net.gepafin.tendermanagement.model.request.ApplicationPageableRequestBean; import net.gepafin.tendermanagement.model.request.ApplicationRequest; +import net.gepafin.tendermanagement.enums.ApplicationRankingActionTypeEnum; import net.gepafin.tendermanagement.enums.ApplicationStatusTypeEnum; import net.gepafin.tendermanagement.enums.FormActionEnum; import net.gepafin.tendermanagement.model.request.ApplicationRequestBean; @@ -57,4 +58,10 @@ public interface ApplicationService { public byte[] downloadRankingCsv(HttpServletRequest request, Long callId); public void uploadCompanyDocumentsToApplication(HttpServletRequest request, Long applicationId, List companyDocumentIds); + + ApplicationResponse updateApplicationRankingAction(HttpServletRequest request, Long applicationId, + ApplicationRankingActionTypeEnum rankingActionType, Long manualRanking); + + CallRankingSummaryResponse getApplicationRanking(HttpServletRequest request, Long callId, + List rankingActionTypes); } diff --git a/src/main/java/net/gepafin/tendermanagement/service/CallService.java b/src/main/java/net/gepafin/tendermanagement/service/CallService.java index f0130c29..2c51f3e0 100644 --- a/src/main/java/net/gepafin/tendermanagement/service/CallService.java +++ b/src/main/java/net/gepafin/tendermanagement/service/CallService.java @@ -4,6 +4,7 @@ import java.util.List; import jakarta.servlet.http.HttpServletRequest; import net.gepafin.tendermanagement.entities.CallEntity; +import net.gepafin.tendermanagement.enums.CallRankingTypeEnum; import net.gepafin.tendermanagement.enums.CallStatusEnum; import net.gepafin.tendermanagement.enums.EvaluationVersionEnum; import net.gepafin.tendermanagement.model.request.*; @@ -37,4 +38,6 @@ public interface CallService { CallResponse createCallStep2EvaluationV2(HttpServletRequest request, Long callId, CreateCallRequestStep2EvaluationV2 createCallRequest); + CallResponse updateCallRankingType(HttpServletRequest request, Long callId, CallRankingTypeEnum rankingType); + } diff --git a/src/main/java/net/gepafin/tendermanagement/service/impl/ApplicationServiceImpl.java b/src/main/java/net/gepafin/tendermanagement/service/impl/ApplicationServiceImpl.java index c3abf79e..1ff7aa07 100644 --- a/src/main/java/net/gepafin/tendermanagement/service/impl/ApplicationServiceImpl.java +++ b/src/main/java/net/gepafin/tendermanagement/service/impl/ApplicationServiceImpl.java @@ -12,6 +12,7 @@ import net.gepafin.tendermanagement.entities.CompanyEntity; import net.gepafin.tendermanagement.entities.UserEntity; import net.gepafin.tendermanagement.model.request.ApplicationPageableRequestBean; import net.gepafin.tendermanagement.model.request.ApplicationRequest; +import net.gepafin.tendermanagement.enums.ApplicationRankingActionTypeEnum; import net.gepafin.tendermanagement.enums.ApplicationStatusTypeEnum; import net.gepafin.tendermanagement.enums.FormActionEnum; import net.gepafin.tendermanagement.model.request.ApplicationRequestBean; @@ -195,4 +196,20 @@ public class ApplicationServiceImpl implements ApplicationService { UserEntity userEntity = validator.validateUser(request); applicationDao.uploadCompanyDocumentsToApplication(applicationId,companyDocumentIds,userEntity); } + + @Override + @Transactional(rollbackFor = Exception.class) + public ApplicationResponse updateApplicationRankingAction(HttpServletRequest request, Long applicationId, + ApplicationRankingActionTypeEnum rankingActionType, Long manualRanking) { + return applicationDao.updateApplicationRankingAction(request, applicationId, rankingActionType, manualRanking); + } + + @Override + @Transactional(readOnly = true) + public CallRankingSummaryResponse getApplicationRanking(HttpServletRequest request, Long callId, + List rankingActionTypes) { + validator.validateUser(request); + validator.validateSuperAdminOrDirector(); + return applicationDao.getApplicationRanking(callId, rankingActionTypes); + } } diff --git a/src/main/java/net/gepafin/tendermanagement/service/impl/CallServiceImpl.java b/src/main/java/net/gepafin/tendermanagement/service/impl/CallServiceImpl.java index 0036cbb7..e6a71750 100644 --- a/src/main/java/net/gepafin/tendermanagement/service/impl/CallServiceImpl.java +++ b/src/main/java/net/gepafin/tendermanagement/service/impl/CallServiceImpl.java @@ -1,9 +1,12 @@ package net.gepafin.tendermanagement.service.impl; import jakarta.servlet.http.HttpServletRequest; +import net.gepafin.tendermanagement.config.Translator; +import net.gepafin.tendermanagement.constants.GepafinConstant; import net.gepafin.tendermanagement.dao.CallDao; import net.gepafin.tendermanagement.entities.CallEntity; import net.gepafin.tendermanagement.entities.UserEntity; +import net.gepafin.tendermanagement.enums.CallRankingTypeEnum; import net.gepafin.tendermanagement.enums.CallStatusEnum; import net.gepafin.tendermanagement.enums.EvaluationVersionEnum; import net.gepafin.tendermanagement.model.request.*; @@ -12,6 +15,8 @@ import net.gepafin.tendermanagement.model.response.CallResponse; import net.gepafin.tendermanagement.model.response.PageableResponseBean; import net.gepafin.tendermanagement.service.CallService; import net.gepafin.tendermanagement.util.Validator; +import net.gepafin.tendermanagement.web.rest.api.errors.ForbiddenAccessException; +import net.gepafin.tendermanagement.web.rest.api.errors.Status; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -116,4 +121,15 @@ public class CallServiceImpl implements CallService { CallEntity call = validator.validateUserWithCall(user, callId); return callDao.createCallStep2EvaluationV2(call, createCallRequest, user); } + + @Override + @Transactional(rollbackFor = Exception.class) + public CallResponse updateCallRankingType(HttpServletRequest request, Long callId, CallRankingTypeEnum rankingType) { + validator.validateUser(request); + if (Boolean.FALSE.equals(validator.checkIsSuperAdmin())) { + throw new ForbiddenAccessException(Status.FORBIDDEN, Translator.toLocale(GepafinConstant.PERMISSION_DENIED)); + } + CallEntity call = callDao.validateCall(callId); + return callDao.updateCallRankingType(request, call, rankingType); + } } diff --git a/src/main/java/net/gepafin/tendermanagement/web/rest/api/RankingApi.java b/src/main/java/net/gepafin/tendermanagement/web/rest/api/RankingApi.java new file mode 100644 index 00000000..44ab7031 --- /dev/null +++ b/src/main/java/net/gepafin/tendermanagement/web/rest/api/RankingApi.java @@ -0,0 +1,84 @@ +package net.gepafin.tendermanagement.web.rest.api; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.Valid; +import net.gepafin.tendermanagement.enums.ApplicationRankingActionTypeEnum; +import net.gepafin.tendermanagement.enums.CallRankingTypeEnum; +import net.gepafin.tendermanagement.model.request.ApplicationRankingActionRequest; +import net.gepafin.tendermanagement.model.response.CallRankingSummaryResponse; +import net.gepafin.tendermanagement.model.response.ApplicationResponse; +import net.gepafin.tendermanagement.model.response.CallResponse; +import net.gepafin.tendermanagement.model.util.Response; +import net.gepafin.tendermanagement.web.rest.api.errors.ErrorConstants; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; + +import java.util.List; + +@Validated +public interface RankingApi { + + @Operation(summary = "API to update call ranking type", + responses = { + @ApiResponse(responseCode = "200", description = "OK"), + @ApiResponse(responseCode = "404", description = "Not Found", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, examples = { + @io.swagger.v3.oas.annotations.media.ExampleObject(value = ErrorConstants.NOTFOUND_ERROR_EXAMPLE)})), + @ApiResponse(responseCode = "401", description = "Unauthorized", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, examples = { + @io.swagger.v3.oas.annotations.media.ExampleObject(value = ErrorConstants.UNAUTHORIZED_ERROR_EXAMPLE)})), + @ApiResponse(responseCode = "400", description = "Bad Request", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, examples = { + @io.swagger.v3.oas.annotations.media.ExampleObject(value = ErrorConstants.BADREQUEST_ERROR_EXAMPLE)})) + }) + @PutMapping(value = "/call/{callId}/type", produces = MediaType.APPLICATION_JSON_VALUE) + @PreAuthorize("hasRole('ROLE_SUPER_ADMIN')") + ResponseEntity> updateCallRankingType(HttpServletRequest request, + @PathVariable("callId") Long callId, + @RequestParam("rankingType") CallRankingTypeEnum rankingType); + + @Operation(summary = "API to update application ranking action type in application", + responses = { + @ApiResponse(responseCode = "200", description = "OK"), + @ApiResponse(responseCode = "404", description = "Not Found", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, examples = { + @io.swagger.v3.oas.annotations.media.ExampleObject(value = ErrorConstants.NOTFOUND_ERROR_EXAMPLE)})), + @ApiResponse(responseCode = "401", description = "Unauthorized", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, examples = { + @io.swagger.v3.oas.annotations.media.ExampleObject(value = ErrorConstants.UNAUTHORIZED_ERROR_EXAMPLE)})), + @ApiResponse(responseCode = "400", description = "Bad Request", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, examples = { + @io.swagger.v3.oas.annotations.media.ExampleObject(value = ErrorConstants.BADREQUEST_ERROR_EXAMPLE)})) + }) + @PatchMapping(value = "/application/{applicationId}/action", produces = MediaType.APPLICATION_JSON_VALUE, + consumes = MediaType.APPLICATION_JSON_VALUE) + @PreAuthorize("hasRole('ROLE_SUPER_ADMIN')") + ResponseEntity> updateApplicationRankingAction(HttpServletRequest request, + @PathVariable("applicationId") Long applicationId, + @Valid @RequestBody ApplicationRankingActionRequest rankingActionRequest); + + @Operation(summary = "API to get ranking list with callId", + responses = { + @ApiResponse(responseCode = "200", description = "OK"), + @ApiResponse(responseCode = "404", description = "Not Found", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, examples = { + @io.swagger.v3.oas.annotations.media.ExampleObject(value = ErrorConstants.NOTFOUND_ERROR_EXAMPLE)})), + @ApiResponse(responseCode = "401", description = "Unauthorized", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, examples = { + @io.swagger.v3.oas.annotations.media.ExampleObject(value = ErrorConstants.UNAUTHORIZED_ERROR_EXAMPLE)})) + }) + @GetMapping(produces = MediaType.APPLICATION_JSON_VALUE) + @PreAuthorize("hasRole('ROLE_SUPER_ADMIN') || hasRole('ROLE_DIRECTOR')") + ResponseEntity> getApplicationRanking(HttpServletRequest request, + @Parameter(description = "Call id", required = true) + @RequestParam("callId") Long callId, + @Parameter(description = "ranking action types", required = false, + array = @ArraySchema(schema = @Schema(implementation = ApplicationRankingActionTypeEnum.class))) + @RequestParam(value = "rankingActionType", required = false) List rankingActionTypes); +} diff --git a/src/main/java/net/gepafin/tendermanagement/web/rest/api/impl/RankingApiController.java b/src/main/java/net/gepafin/tendermanagement/web/rest/api/impl/RankingApiController.java new file mode 100644 index 00000000..8d2b794f --- /dev/null +++ b/src/main/java/net/gepafin/tendermanagement/web/rest/api/impl/RankingApiController.java @@ -0,0 +1,74 @@ +package net.gepafin.tendermanagement.web.rest.api.impl; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.Valid; +import net.gepafin.tendermanagement.config.Translator; +import net.gepafin.tendermanagement.constants.GepafinConstant; +import net.gepafin.tendermanagement.enums.ApplicationRankingActionTypeEnum; +import net.gepafin.tendermanagement.enums.CallRankingTypeEnum; +import net.gepafin.tendermanagement.enums.UserActionContextEnum; +import net.gepafin.tendermanagement.enums.UserActionLogsEnum; +import net.gepafin.tendermanagement.model.request.ApplicationRankingActionRequest; +import net.gepafin.tendermanagement.model.request.UserActionRequest; +import net.gepafin.tendermanagement.model.response.CallRankingSummaryResponse; +import net.gepafin.tendermanagement.model.response.ApplicationResponse; +import net.gepafin.tendermanagement.model.response.CallResponse; +import net.gepafin.tendermanagement.model.util.Response; +import net.gepafin.tendermanagement.service.ApplicationService; +import net.gepafin.tendermanagement.service.CallService; +import net.gepafin.tendermanagement.util.LoggingUtil; +import net.gepafin.tendermanagement.web.rest.api.RankingApi; +import net.gepafin.tendermanagement.web.rest.api.errors.Status; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@RestController +@RequestMapping("/v1/ranking") +public class RankingApiController implements RankingApi { + + @Autowired + private CallService callService; + + @Autowired + private ApplicationService applicationService; + + @Autowired + private LoggingUtil loggingUtil; + + @Override + public ResponseEntity> updateCallRankingType(HttpServletRequest request, Long callId, + CallRankingTypeEnum rankingType) { + loggingUtil.logUserAction(UserActionRequest.builder().request(request).actionType(UserActionLogsEnum.UPDATE) + .actionContext(UserActionContextEnum.UPDATE_CALL_RANKING_TYPE).build()); + CallResponse updated = callService.updateCallRankingType(request, callId, rankingType); + return ResponseEntity.status(HttpStatus.OK) + .body(new Response<>(updated, Status.SUCCESS, Translator.toLocale(GepafinConstant.CALL_RANKING_TYPE_UPDATED_SUCCESSFULLY))); + } + + @Override + public ResponseEntity> updateApplicationRankingAction(HttpServletRequest request, + Long applicationId, @Valid @RequestBody ApplicationRankingActionRequest rankingActionRequest) { + loggingUtil.logUserAction(UserActionRequest.builder().request(request).actionType(UserActionLogsEnum.UPDATE) + .actionContext(UserActionContextEnum.UPDATE_APPLICATION_RANKING_ACTION).build()); + ApplicationResponse body = applicationService.updateApplicationRankingAction(request, applicationId, + rankingActionRequest.getRankingActionType(), rankingActionRequest.getManualRanking()); + return ResponseEntity.status(HttpStatus.OK) + .body(new Response<>(body, Status.SUCCESS, Translator.toLocale(GepafinConstant.APPLICATION_RANKING_ACTION_UPDATED_SUCCESSFULLY))); + } + + @Override + public ResponseEntity> getApplicationRanking(HttpServletRequest request, + Long callId, List rankingActionTypes) { + loggingUtil.logUserAction(UserActionRequest.builder().request(request).actionType(UserActionLogsEnum.VIEW) + .actionContext(UserActionContextEnum.GET_APPLICATION_RANKING).build()); + CallRankingSummaryResponse ranking = applicationService.getApplicationRanking(request, callId, rankingActionTypes); + return ResponseEntity.status(HttpStatus.OK) + .body(new Response<>(ranking, Status.SUCCESS, Translator.toLocale(GepafinConstant.APPLICATION_RANKING_FETCHED_SUCCESSFULLY))); + } +} 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 407ba720..3bba5cbd 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 @@ -3223,4 +3223,22 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/db/dump/create_application_ranking_view_07_04_2026.sql b/src/main/resources/db/dump/create_application_ranking_view_07_04_2026.sql new file mode 100644 index 00000000..afa68d8f --- /dev/null +++ b/src/main/resources/db/dump/create_application_ranking_view_07_04_2026.sql @@ -0,0 +1,201 @@ +DROP VIEW IF EXISTS application_ranking_view; + +CREATE OR REPLACE VIEW application_ranking_view AS +WITH evaluation_scores AS ( + SELECT + ae.application_id, + COALESCE( + SUM( + COALESCE(NULLIF(TRIM(score_item ->> 'score'), ''), '0')::numeric + ), + 0 + ) AS total_score + FROM application_evaluation ae + LEFT JOIN LATERAL jsonb_array_elements( + CASE + WHEN ae.criteria IS NULL OR BTRIM(ae.criteria) = '' THEN '[]'::jsonb + ELSE ae.criteria::jsonb + END + ) score_item ON TRUE + WHERE ae.is_deleted = false + GROUP BY ae.application_id +), +approved_applications AS ( + SELECT + a.id AS application_id, + a.call_id, + a.company_id, + a.user_id, + a.status AS status, + a.submission_date, + a.amount_requested, + a.amount_accepted, + a.manual_ranking, + a.ranking_action_type, + a.ndg, + a.pec_email, + c.ranking_type, + p.created_date AS protocol_datetime, + p.protocol_number AS protocol_number, + COALESCE(a.amount_accepted, a.amount_requested, 0) AS amount, + COALESCE(es.total_score, 0) AS total_score + FROM application a + JOIN call c ON c.id = a.call_id + AND (c.is_deleted = false OR c.is_deleted IS NULL) + AND NULLIF(BTRIM(c.ranking_type), '') IS NOT NULL + LEFT JOIN protocol p ON p.id = a.protocol_number + LEFT JOIN evaluation_scores es ON es.application_id = a.id + WHERE (a.is_deleted = false OR a.is_deleted IS NULL) + AND a.status = 'APPROVED' + AND COALESCE(a.ranking_action_type, '') <> 'EXCLUDE' +), +ranking_core AS ( + SELECT * + FROM approved_applications + WHERE COALESCE(ranking_action_type, '') <> 'REMOVE' +), +remove_apps AS ( + SELECT * + FROM approved_applications + WHERE COALESCE(ranking_action_type, '') = 'REMOVE' +), +natural_ranked AS ( + SELECT + rc.*, + ROW_NUMBER() OVER ( + PARTITION BY rc.call_id + ORDER BY + CASE WHEN rc.ranking_type = 'SCORE' THEN rc.total_score END DESC NULLS LAST, + CASE WHEN rc.ranking_type = 'PROTOCOL_DATE_TIME' THEN rc.protocol_datetime END ASC NULLS LAST, + rc.protocol_datetime ASC NULLS LAST, + rc.application_id ASC + ) AS natural_rank + FROM ranking_core rc +), +manual_repositioned AS ( + SELECT * + FROM natural_ranked + WHERE COALESCE(ranking_action_type, '') = 'REPOSITION' + AND manual_ranking IS NOT NULL +), +non_manual AS ( + SELECT * + FROM natural_ranked + WHERE COALESCE(ranking_action_type, '') <> 'REPOSITION' + OR manual_ranking IS NULL +), +manual_slots AS ( + SELECT + mr.call_id, + mr.application_id, + mr.company_id, + mr.user_id, + mr.status, + mr.submission_date, + mr.amount_requested, + mr.amount_accepted, + mr.amount, + mr.ranking_type, + mr.protocol_datetime, + mr.protocol_number, + mr.total_score, + mr.ranking_action_type, + mr.manual_ranking, + mr.ndg, + mr.pec_email, + mr.natural_rank, + mr.manual_ranking AS effective_rank_slot + FROM manual_repositioned mr +), +non_manual_shifted AS ( + SELECT + nm.call_id, + nm.application_id, + nm.company_id, + nm.user_id, + nm.status, + nm.submission_date, + nm.amount_requested, + nm.amount_accepted, + nm.amount, + nm.ranking_type, + nm.protocol_datetime, + nm.protocol_number, + nm.total_score, + nm.ranking_action_type, + nm.manual_ranking, + nm.ndg, + nm.pec_email, + nm.natural_rank, + nm.natural_rank + + COALESCE(( + SELECT COUNT(*)::bigint + FROM manual_repositioned mr + WHERE mr.call_id = nm.call_id + AND mr.manual_ranking <= nm.natural_rank + ), 0) AS effective_rank_slot + FROM non_manual nm +), +combined AS ( + SELECT * FROM manual_slots + UNION ALL + SELECT * FROM non_manual_shifted +), +max_slot_by_call AS ( + SELECT call_id, MAX(effective_rank_slot) AS max_slot + FROM combined + GROUP BY call_id +), +remove_placed AS ( + SELECT + ra.call_id, + ra.application_id, + ra.company_id, + ra.user_id, + ra.status, + ra.submission_date, + ra.amount_requested, + ra.amount_accepted, + ra.amount, + ra.ranking_type, + ra.protocol_datetime, + ra.protocol_number, + ra.total_score, + ra.ranking_action_type, + ra.manual_ranking, + ra.ndg, + ra.pec_email, + NULL::bigint AS natural_rank, + COALESCE(ms.max_slot, 0) + + ROW_NUMBER() OVER ( + PARTITION BY ra.call_id + ORDER BY ra.protocol_datetime ASC NULLS LAST, ra.application_id + ) AS effective_rank_slot + FROM remove_apps ra + LEFT JOIN max_slot_by_call ms ON ms.call_id = ra.call_id +), +final_rows AS ( + SELECT * FROM combined + UNION ALL + SELECT * FROM remove_placed +) +SELECT + ROW_NUMBER() OVER ( + PARTITION BY fr.call_id + ORDER BY fr.effective_rank_slot, fr.protocol_datetime ASC NULLS LAST, fr.application_id + ) AS listing_rank, + fr.application_id, + fr.call_id, + fr.ranking_action_type, + fr.total_score, + fr.user_id, + fr.status, + fr.submission_date, + fr.protocol_datetime, + fr.protocol_number, + fr.ndg, + fr.amount_accepted, + fr.pec_email, + fr.manual_ranking, + fr.ranking_type +FROM final_rows fr; diff --git a/src/main/resources/message_en.properties b/src/main/resources/message_en.properties index 4d8bb42b..6d19f024 100644 --- a/src/main/resources/message_en.properties +++ b/src/main/resources/message_en.properties @@ -61,6 +61,12 @@ status.same.error=Status is already set. invalid.status.change.from.draft=Status cannot be changed to READY_TO_PUBLISH or PUBLISH from DRAFT. status.cannot.be.changed=Status cannot be changed. published.call.not.update=Published call cannot be updated. +published.call.step1.only.ranking.type.allowed=For a published call, this step accepts only ranking type. +call.ranking.type.updated.successfully=Call ranking type updated successfully. +application.ranking.action.updated.successfully=Application ranking action updated successfully. +application.ranking.fetched.successfully=Application ranking fetched successfully. +call.must.be.closed.for.ranking.action=Ranking action is allowed only when the call is closed. +application.ranking.action.invalid=Invalid application ranking action request. invalid.status.change.from.publish=Status cannot be changed to READY_TO_PUBLISH from PUBLISH. invalid.status.change.from.publish.to.draft=Status cannot be changed to DRAFT from PUBLISH as Applications are already created for this CALL. validation.table.message=Data for field {0} is not present. diff --git a/src/main/resources/message_it.properties b/src/main/resources/message_it.properties index 75c0488e..1462b30f 100644 --- a/src/main/resources/message_it.properties +++ b/src/main/resources/message_it.properties @@ -61,6 +61,12 @@ status.same.error=Lo stato ? gi? impostato. invalid.status.change.from.draft=Lo stato non pu? essere cambiato in READY_TO_PUBLISH o PUBLISH da DRAFT. status.cannot.be.changed=Lo stato non pu? essere cambiato. published.call.not.update=Il bando pubblicato non pu? essere aggiornato. +published.call.step1.only.ranking.type.allowed=Per un bando pubblicato, questo passaggio accetta solo il tipo di graduatoria; omettere tutti gli altri campi. +call.ranking.type.updated.successfully=Tipo di graduatoria del bando aggiornato correttamente. +application.ranking.action.updated.successfully=Azione di graduatoria sulla domanda aggiornata correttamente. +application.ranking.fetched.successfully=Graduatoria recuperata correttamente. +call.must.be.closed.for.ranking.action=L'azione di graduatoria � consentita solo se il bando � chiuso (scaduto). +application.ranking.action.invalid=Richiesta di azione sulla graduatoria non valida. invalid.status.change.from.publish=Lo stato non pu? essere modificato in READY_TO_PUBLISH da PUBLISH. invalid.status.change.from.publish.to.draft=Lo stato non pu essere modificato da PUBLISH a DRAFT poich sono gi state create applicazioni per questa CALL. @@ -71,7 +77,7 @@ email.already.exists=Esiste gi? un utente con questa email. invalid_user=Validazione utente fallita. Controlla le informazioni, lo stato dell'account e la scadenza del token. #Global messages -common_message=Qualcosa è andato storto. Riprova. +common_message=Qualcosa � andato storto. Riprova. invalid_signature=Gettone non valido. invalid_login=Nome utente o password errati req_validation_er=Errore di convalida @@ -425,7 +431,7 @@ amendment.must.be.approved.first=L'emendamento deve essere approvato dal diretto upload.company.document.to.application=Documento aziendale caricato correttamente nell'applicazione. company.document.not.found.with.ids=Documento aziendale non trovato. ID mancanti: {0} amount.field.not.provided= Si prega di fornire i campi obbligatori per l'importo. -vat.or.tax.code.required=È obbligatorio il numero di partita IVA o il codice fiscale. +vat.or.tax.code.required=� obbligatorio il numero di partita IVA o il codice fiscale. provide.valid.vat.number=Inserisci un numero di partita IVA valido per procedere con NDG. -application.status.transition.restricted=Questa modifica di stato non è disponibile tramite questa operazione. +application.status.transition.restricted=Questa modifica di stato non � disponibile tramite questa operazione. application.registry.segment.invalid=Valore del segmento non valido. \ No newline at end of file