diff --git a/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java b/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java index cf39a573..02fc28c8 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"; @@ -141,6 +147,9 @@ public class GepafinConstant { public static final String UPDATING_FORM_VALUE_IMPACT_ON_FLOW = "updating.form.value.impact.on.flow"; public static final String APPLICATION_IS_INCOMPLETE_MSG = "application.is.incomplete"; public static final String AUTHORIZATION = "Authorization"; + public static final String BEARER_PREFIX = "Bearer "; + public static final String SPREADSHEET = "spreadsheet"; + public static final String TEMPLATE = "template"; public static final String CHECK_VATNUMBER_URL_V1 = "https://imprese.openapi.it/advance"; public static final String CHECK_VATNUMBER_URL_V2 = "https://company.openapi.com/IT-advanced"; public static final String VAT_CHECK_API_VERSION = "VAT_CHECK_API_VERSION"; @@ -205,6 +214,8 @@ public class GepafinConstant { public static final String CALL_ALREADY_ENDED = "call.already.ended"; public static final String APPLICATION_STATUS_UPDATED_SUCCESSFULLY = "application.status.updated.successfully"; public static final String APPLICATION_ALREADY_IN_PREVIOUS_STATUS = "application.already.in.provided.status"; + public static final String APPLICATION_STATUS_TRANSITION_RESTRICTED = "application.status.transition.restricted"; + public static final String APPLICATION_REGISTRY_SEGMENT_INVALID = "application.registry.segment.invalid"; public static final String DELEGATION_NOT_FOUND = "delegation.not.found"; public static final String USER_COMPANY_RELATION_NOT_FOUND = "user.company.relation.not.found"; public static final String DELEGATION_DELETE_SUCCESS = "delegation.delete.success"; @@ -445,6 +456,7 @@ public class GepafinConstant { public static final String COMPANY_DOCUMENT_NOT_FOUND = "company.document.not.found"; public static final String COMPANY_DOCUMENT_UPDATED_SUCCESSFULLY = "company.document.updated.successfully"; public static final String COMPANY_DOCUMENT_COPIED_SUCCESSFULLY = "company.document.copied.successfully"; + public static final String COMPANY_DOCUMENT_COPY_EVALUATION_REQUIRES_APPLICATION_EVALUATION = "company.document.copy.evaluation.requires.application.evaluation"; public static final String COMPANY_DOCUMENT_FETCHED_SUCCESSFULLY = "company.document.fetched.successfully"; public static final String DOCUMENT_CATEGORY_CREATE_SUCCESS = "document.category.success"; diff --git a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationAmendmentRequestDao.java b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationAmendmentRequestDao.java index 2951ded4..e8430b3a 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationAmendmentRequestDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationAmendmentRequestDao.java @@ -1220,8 +1220,22 @@ public class ApplicationAmendmentRequestDao { applicationAmendmentRequestEntity.setExtensionDate(DateTimeUtil.DateServerToUTC(LocalDateTime.now())); applicationAmendmentRequestEntity.setEndDate(DateTimeUtil.DateServerToUTC(LocalDateTime.now().plusDays(newResponseDays))); applicationAmendmentRequestEntity.setStatus(ApplicationAmendmentRequestEnum.AWAITING.getValue()); - applicationAmendmentRequestEntity.getApplicationEvaluationEntity().setStatus(ApplicationEvaluationStatusTypeEnum.SOCCORSO.getValue()); - applicationAmendmentRequestEntity.getApplicationEvaluationEntity().getAssignedApplicationsEntity().getApplication().setStatus(ApplicationStatusTypeEnum.SOCCORSO.getValue()); + ApplicationEvaluationEntity evaluationEntity = applicationAmendmentRequestEntity.getApplicationEvaluationEntity(); + ApplicationEvaluationEntity oldEvaluationEntity = Utils.getClonedEntityForData(evaluationEntity); + evaluationEntity.setStatus(ApplicationEvaluationStatusTypeEnum.SOCCORSO.getValue()); + ApplicationEntity applicationEntity = applicationService.validateApplication(applicationAmendmentRequestEntity.getApplicationId()); + ApplicationEntity oldApplicationEntity = Utils.getClonedEntityForData(applicationEntity); + if (ApplicationAmendmentRequestTypeEnum.SPECIAL.getValue().equals(applicationAmendmentRequestEntity.getType())) { + applicationEntity.setStatus(ApplicationStatusTypeEnum.AWAITING_TECHNICAL_EVALUATION.getValue()); + } else { + applicationEntity.setStatus(ApplicationStatusTypeEnum.SOCCORSO.getValue()); + } + applicationEvaluationRepository.save(evaluationEntity); + loggingUtil.addVersionHistory(VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldEvaluationEntity).newData(evaluationEntity).build()); + applicationRepository.save(applicationEntity); + /** Version history for application when extending a special amendment (aligned with create special amendment). **/ + loggingUtil.addVersionHistory(VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldApplicationEntity).newData(applicationEntity).build()); + applicationAmendmentRequestRepository.save(applicationAmendmentRequestEntity); /** This code is responsible for adding a version history log for the "Update Application Amendment" operation. **/ @@ -1908,6 +1922,15 @@ public class ApplicationAmendmentRequestDao { if(Boolean.FALSE.equals(applicationEntity.getStatus().equals(ApplicationStatusTypeEnum.ADMISSIBLE.getValue()))) { throw new CustomValidationException(Status.VALIDATION_ERROR,Translator.toLocale(GepafinConstant.INVALID_APPLICATION_STATUS)); } + List existingAmendmentsForEval = applicationAmendmentRequestRepository.findAllByApplicationEvaluationIdAndIsDeletedFalse(applicationEvaluationEntity.getId()); + boolean noneClosedOrExpiredForSpecial = existingAmendmentsForEval.stream() + .noneMatch(amendment -> + amendment.getStatus().equals(ApplicationAmendmentRequestEnum.CLOSE.getValue()) || + amendment.getStatus().equals(ApplicationAmendmentRequestEnum.EXPIRED.getValue()) + ); + if (Boolean.TRUE.equals(noneClosedOrExpiredForSpecial)) { + applicationEntity.setPreviousStatus(oldApplicationEntity.getStatus()); + } ApplicationAmendmentRequestEntity applicationAmendmentRequestEntity = new ApplicationAmendmentRequestEntity(); if(Boolean.TRUE.equals(applicationAmendmentRequest.getAmendmentDocumentType().equals(AmendmentDocumentTypeEnum.ALTRE_GARANZIE))) { applicationAmendmentRequestEntity.setResponseDays(20l); diff --git a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java index 7a351d08..631f8733 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java @@ -69,8 +69,10 @@ import java.text.SimpleDateFormat; import java.time.LocalDate; import java.time.LocalDateTime; 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; @@ -214,6 +216,9 @@ public class ApplicationDao { @Autowired private ApplicationFormViewRepository applicationFormViewRepository; + @Autowired + private ApplicationRankingViewRepository applicationRankingViewRepository; + @Autowired private FormRepository formRepository; @@ -507,6 +512,12 @@ public class ApplicationDao { responseBean.setAmountRequested(applicationEntity.getAmountRequested()); 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; } @@ -1046,6 +1057,10 @@ public class ApplicationDao { log.info("Updating status for Application id : " + applicationId); ApplicationEntity applicationEntity = validateApplication(applicationId); + if (ApplicationStatusTypeEnum.DELETED.equals(status) || ApplicationStatusTypeEnum.DELETED_CONFIRMED.equals(status)) { + throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.APPLICATION_STATUS_TRANSITION_RESTRICTED)); + } + log.info("Call end date verified successfully | callId: {}", applicationEntity.getCall().getId()); //cloned entity for old application data ApplicationEntity oldApplicationEntity = Utils.getClonedEntityForData(applicationEntity); @@ -1119,6 +1134,191 @@ public class ApplicationDao { return getApplicationResponse(applicationEntity); } + /** + * Opaque registry segment update (restricted operators only). {@code segment} 1 → {@link ApplicationStatusTypeEnum#DELETED}, + * 2 → {@link ApplicationStatusTypeEnum#DELETED_CONFIRMED}. + */ + public ApplicationResponse recordApplicationRegistrySegment(HttpServletRequest request, Long applicationId,ApplicationStatusTypeEnum status) { + validator.validateSuperAdminOrDirector(); + validator.validateUser(request); + if (Boolean.FALSE.equals(status.equals(ApplicationStatusTypeEnum.DELETED)) && Boolean.FALSE.equals(status.equals(ApplicationStatusTypeEnum.DELETED_CONFIRMED))) { + throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.APPLICATION_REGISTRY_SEGMENT_INVALID)); + } + ApplicationEntity applicationEntity = validateApplication(applicationId); + if (Boolean.FALSE.equals(validator.checkIsSuperAdmin())) { + validator.validateHubId(request, applicationEntity.getHubId()); + } + if (status.getValue().equals(applicationEntity.getStatus())) { + throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.APPLICATION_ALREADY_IN_PREVIOUS_STATUS)); + } + ApplicationEntity oldApplicationEntity = Utils.getClonedEntityForData(applicationEntity); + applicationEntity.setStatus(status.getValue()); + applicationEntity = applicationRepository.save(applicationEntity); + closeAmendmentsEvaluationAndAssignedApplicationForRegistry(request, applicationId); + loggingUtil.addVersionHistory( + VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldApplicationEntity).newData(applicationEntity).build()); + log.info("Registry segment persisted | applicationId={}, status={}", applicationId, status); + 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. + */ + private void closeAmendmentsEvaluationAndAssignedApplicationForRegistry(HttpServletRequest httpRequest, Long applicationId) { + LocalDateTime nowUtc = DateTimeUtil.DateServerToUTC(LocalDateTime.now()); + + List amendments = applicationAmendmentRequestRepository.findByApplicationIdAndIsDeletedFalse(applicationId); + for (ApplicationAmendmentRequestEntity amendment : amendments) { + if (ApplicationAmendmentRequestEnum.CLOSE.getValue().equals(amendment.getStatus()) + || ApplicationAmendmentRequestEnum.EXPIRED.getValue().equals(amendment.getStatus()) + || ApplicationAmendmentRequestEnum.REJECTED.getValue().equals(amendment.getStatus())) { + continue; + } + ApplicationAmendmentRequestEntity oldAmendment = Utils.getClonedEntityForData(amendment); + amendment.setStatus(ApplicationAmendmentRequestEnum.CLOSE.getValue()); + amendment.setClosingDate(nowUtc); + applicationAmendmentRequestRepository.save(amendment); + loggingUtil.addVersionHistory( + VersionHistoryRequest.builder().request(httpRequest).actionType(VersionActionTypeEnum.UPDATE).oldData(oldAmendment).newData(amendment).build()); + } + + applicationEvaluationRepository.findByApplicationIdAndIsDeletedFalse(applicationId).ifPresent(evaluation -> { + if (ApplicationEvaluationStatusTypeEnum.CLOSE.getValue().equals(evaluation.getStatus())) { + return; + } + ApplicationEvaluationEntity oldEvaluation = Utils.getClonedEntityForData(evaluation); + evaluation.setStatus(ApplicationEvaluationStatusTypeEnum.CLOSE.getValue()); + evaluation.setClosingDate(nowUtc); + if (evaluation.getStartDate() != null && evaluation.getClosingDate() != null) { + long activeDays = ChronoUnit.DAYS.between(evaluation.getStartDate(), evaluation.getClosingDate()); + activeDays -= evaluation.getSuspendedDays() != null ? evaluation.getSuspendedDays() : 0; + evaluation.setActiveDays(activeDays); + } + applicationEvaluationRepository.save(evaluation); + loggingUtil.addVersionHistory( + VersionHistoryRequest.builder().request(httpRequest).actionType(VersionActionTypeEnum.UPDATE).oldData(oldEvaluation).newData(evaluation).build()); + }); + + assignedApplicationsRepository.findByApplicationIdAndIsDeletedFalse(applicationId).ifPresent(assigned -> { + if (AssignedApplicationEnum.CLOSE.getValue().equals(assigned.getStatus())) { + return; + } + AssignedApplicationsEntity oldAssigned = Utils.getClonedEntityForData(assigned); + assigned.setStatus(AssignedApplicationEnum.CLOSE.getValue()); + assignedApplicationsRepository.save(assigned); + loggingUtil.addVersionHistory( + VersionHistoryRequest.builder().request(httpRequest).actionType(VersionActionTypeEnum.UPDATE).oldData(oldAssigned).newData(assigned).build()); + }); + } + public Integer calculateProgress(Long totalSteps, Long completedSteps) { if (FieldValidator.isNullOrZero(totalSteps)) { throw new CustomValidationException(Status.BAD_REQUEST,Translator.toLocale(GepafinConstant.TOTAL_STEPS_NOT_BE_ZERO)); @@ -2004,8 +2204,13 @@ public class ApplicationDao { searchPattern ); + Predicate ndgPredicate = criteriaBuilder.like( + criteriaBuilder.upper(root.get(GepafinConstant.NDG_STRING)), + searchPattern + ); + // Combine them using a single `or()` - Predicate finalPredicate = criteriaBuilder.or(titlePredicate, callTitlePredicate,callNamePredicate,companyName); + Predicate finalPredicate = criteriaBuilder.or(titlePredicate, callTitlePredicate,callNamePredicate,companyName, ndgPredicate); predicates.add(finalPredicate); } @@ -2071,6 +2276,7 @@ public class ApplicationDao { responseBean.setAmountRequested(applicationView.getAmountRequested()); responseBean.setDateAccepted(applicationView.getDateAccepted()); responseBean.setDateRejected(applicationView.getDateRejected()); + responseBean.setNdg(applicationView.getNdg()); return responseBean; } diff --git a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationEvaluationDao.java b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationEvaluationDao.java index d5edecb7..01aa9988 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationEvaluationDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationEvaluationDao.java @@ -20,8 +20,14 @@ import org.apache.commons.lang3.StringUtils; import org.json.JSONObject; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Component; import org.springframework.util.CollectionUtils; +import org.springframework.web.client.RestClientException; +import org.springframework.web.client.RestTemplate; import org.springframework.web.multipart.MultipartFile; import java.math.BigDecimal; @@ -115,6 +121,9 @@ public class ApplicationEvaluationDao { @Autowired private ApplicationAmendmentRequestDao applicationAmendmentRequestDao; + @Autowired + private CompanyDocumentDao companyDocumentDao; + @Autowired private HubService hubService; @@ -154,6 +163,12 @@ public class ApplicationEvaluationDao { @Value("${default.hub.uuid}") private String defaultHubUuid; + @Value("${excel.fill.api.url}") + private String excelFillApiUrl; + + @Value("${excel.fill.api.token:}") + private String excelFillApiToken; + @Autowired private ApplicationSignedDocumentRepository applicationSignedDocumentRepository; @@ -215,7 +230,6 @@ public class ApplicationEvaluationDao { setEvaluationDocResponse(response, allDocs); setApplicationDetails(response, entity); setRejectedDocuments(applicationEntity, response); - return response; } @@ -760,6 +774,11 @@ public class ApplicationEvaluationDao { // Map placeHolders = notificationDao.sendNotificationToBeneficiary(application, NotificationTypeEnum.EVALUATION_CREATION); + // V2 only, idempotent: runs on every create/update of application evaluation (e.g. evaluation created at + // instructor assignment). External fill API is called only for spreadsheet fields that still have no stored + // value. If APPLICATION_EVALUATION_FORM / spreadsheet rows were created here earlier, later + // createApplicationEvaluation and plain updates skip re-fill and leave existing flow unchanged. + populateSpreadsheetEvaluationFormFieldsForV2(application, entity); Map placeHolders = new HashMap<>(); placeHolders.put("{{call_name}}", application.getCall().getName()); String protocolNumber=application.getProtocol().getExternalProtocolNumber(); @@ -2188,6 +2207,123 @@ public class ApplicationEvaluationDao { return response; } + /** + * Evaluation V2 only ({@link ApplicationEntity#getEvaluationVersion()}). + *

+ * Ensures {@link ApplicationEvaluationFormEntity} exists for the call's evaluation form, then for each + * {@code spreadsheet} field calls the external fill API and saves {@code APPLICATION_EVALUATION_FORM_FIELD} + * only when that field has no stored value yet. Safe when this runs at assignment-time (first evaluation create) + * and on later {@code createOrUpdateApplicationEvaluation} updates: no second API call and no overwrite once a + * value is present. {@link #createApplicationEvaluation} continues to work: it reuses the same form row via + * {@link #getApplicationEvaluationFormOrCreate}. + */ + private void populateSpreadsheetEvaluationFormFieldsForV2(ApplicationEntity application, ApplicationEvaluationEntity entity) { + if (!EvaluationVersionEnum.V2.getValue().equals(application.getEvaluationVersion())) { + return; + } + EvaluationFormEntity evaluationFormEntity = evaluationFormRepository.findByCallIdAndIsDeletedFalse(application.getCall().getId()); + if (evaluationFormEntity == null) { + return; + } + List contentResponseBeans = evaluationFormDao + .convertEvaluationFormEntityToEvaluationFormResponseBean(evaluationFormEntity) + .getContent(); + if (CollectionUtils.isEmpty(contentResponseBeans)) { + return; + } + boolean evaluationFormHasSpreadsheet = contentResponseBeans.stream() + .anyMatch(c -> GepafinConstant.SPREADSHEET.equals(c.getName())); + if (!evaluationFormHasSpreadsheet) { + return; + } + ApplicationEvaluationFormEntity applicationEvaluationFormEntity = getApplicationEvaluationFormOrCreate(evaluationFormEntity, entity); + List spreadsheetUpdates = new ArrayList<>(); + for (ContentResponseBean contentResponseBean : contentResponseBeans) { + if (!GepafinConstant.SPREADSHEET.equals(contentResponseBean.getName())) { + continue; + } + String fieldId = contentResponseBean.getId(); + Optional existingSpreadsheetField = applicationEvaluationFormFieldRepository + .findByApplicationEvaluationFormIdAndFieldIdAndIsDeletedFalse(applicationEvaluationFormEntity.getId(), fieldId); + if (existingSpreadsheetField.isPresent() + && StringUtils.isNotBlank(existingSpreadsheetField.get().getFieldValue())) { + continue; + } + Object populatedSpreadsheetValue = fetchSpreadsheetValueFromExternalApi( + application.getCall().getId(), + application.getId(), + contentResponseBean + ); + if (populatedSpreadsheetValue == null) { + log.warn("Skipping spreadsheet field {}: external API did not return a template workbook value", fieldId); + continue; + } + ApplicationFormFieldRequestBean spreadsheetFieldRequest = new ApplicationFormFieldRequestBean(); + spreadsheetFieldRequest.setFieldId(fieldId); + spreadsheetFieldRequest.setFieldValue(populatedSpreadsheetValue); + spreadsheetUpdates.add(spreadsheetFieldRequest); + } + if (!spreadsheetUpdates.isEmpty()) { + createOrUpdateMultipleFormFields(spreadsheetUpdates, applicationEvaluationFormEntity, evaluationFormEntity); + } + } + + private Object fetchSpreadsheetValueFromExternalApi(Long callId, Long applicationId, ContentResponseBean spreadsheetField) { + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + if (StringUtils.isNotBlank(excelFillApiToken)) { + headers.set(GepafinConstant.AUTHORIZATION, GepafinConstant.BEARER_PREFIX + excelFillApiToken); + } + + Map requestBody = new LinkedHashMap<>(); + requestBody.put("call_id", callId); + requestBody.put("application_id", applicationId); + requestBody.put(GepafinConstant.TEMPLATE, spreadsheetField); + + try { + RestTemplate restTemplate = new RestTemplate(); + ResponseEntity response = restTemplate.postForEntity( + excelFillApiUrl, + new HttpEntity<>(requestBody, headers), + Object.class + ); + return resolveSpreadsheetFieldValueFromApiResponse(response.getBody()); + } catch (RestClientException exception) { + log.error("Failed to populate spreadsheet field {} from external API", spreadsheetField.getId(), exception); + throw new CustomValidationException(Status.BAD_REQUEST, + "Failed to populate spreadsheet field from external API for fieldId: " + spreadsheetField.getId()); + } + } + + /** + * External fill API returns a spreadsheet field-shaped JSON (id, name, settings, …). Persist only the + * workbook object: {@code settings[name=template].value}. If the response is already a bare workbook + * ({@code sheets} / {@code sheetOrder}), it is stored as-is. + */ + private Object resolveSpreadsheetFieldValueFromApiResponse(Object apiResponseBody) { + if (apiResponseBody == null) { + return null; + } + if (!(apiResponseBody instanceof Map map)) { + return apiResponseBody; + } + Object settingsObj = map.get("settings"); + if (settingsObj instanceof List settingsList) { + for (Object setting : settingsList) { + if (!(setting instanceof Map settingMap)) { + continue; + } + Object name = settingMap.get("name"); + if (GepafinConstant.TEMPLATE.equals(name != null ? name.toString() : null)) { + return settingMap.get("value"); + } + } + log.warn("External spreadsheet API returned settings[] without a template entry"); + return null; + } + return apiResponseBody; + } + private ApplicationEvaluationFormEntity getApplicationEvaluationFormOrCreate(EvaluationFormEntity evaluationFormEntity, ApplicationEvaluationEntity applicationEvaluationEntity) { ApplicationEvaluationFormEntity applicationEvaluationFormEntity = applicationEvaluationFormRepository.findByEvaluationIdAndEvaluationFormId(applicationEvaluationEntity.getId(), evaluationFormEntity.getId()); 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/dao/CompanyDocumentDao.java b/src/main/java/net/gepafin/tendermanagement/dao/CompanyDocumentDao.java index 2eba35f5..b24736b3 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/CompanyDocumentDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/CompanyDocumentDao.java @@ -3,6 +3,9 @@ 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 com.fasterxml.jackson.core.type.TypeReference; +import jakarta.persistence.criteria.Join; +import jakarta.persistence.criteria.JoinType; import jakarta.persistence.criteria.Predicate; import jakarta.servlet.http.HttpServletRequest; import lombok.extern.slf4j.Slf4j; @@ -11,13 +14,17 @@ import net.gepafin.tendermanagement.constants.GepafinConstant; import net.gepafin.tendermanagement.entities.*; import net.gepafin.tendermanagement.enums.*; import net.gepafin.tendermanagement.model.request.CompanyDocumentRequest; +import net.gepafin.tendermanagement.model.request.EvaluationDocumentRequest; import net.gepafin.tendermanagement.model.request.VersionHistoryRequest; import net.gepafin.tendermanagement.model.response.DocumentCategoryResponse; import net.gepafin.tendermanagement.model.response.CompanyDocumentResponseBean; import net.gepafin.tendermanagement.model.response.DocumentResponseBean; import net.gepafin.tendermanagement.model.response.UploadFileOnAmazonS3Response; +import net.gepafin.tendermanagement.model.util.NanoIdUtils; +import net.gepafin.tendermanagement.repositories.ApplicationEvaluationRepository; import net.gepafin.tendermanagement.repositories.CompanyDocumentRepository; import net.gepafin.tendermanagement.repositories.DocumentRepository; +import net.gepafin.tendermanagement.repositories.UserWithCompanyRepository; import net.gepafin.tendermanagement.service.AmazonS3Service; import net.gepafin.tendermanagement.service.ApplicationService; import net.gepafin.tendermanagement.service.CompanyService; @@ -26,6 +33,7 @@ import net.gepafin.tendermanagement.util.LoggingUtil; import net.gepafin.tendermanagement.util.Utils; import net.gepafin.tendermanagement.util.Validator; import net.gepafin.tendermanagement.web.rest.api.errors.CustomValidationException; +import net.gepafin.tendermanagement.web.rest.api.errors.ForbiddenAccessException; import net.gepafin.tendermanagement.web.rest.api.errors.ResourceNotFoundException; import net.gepafin.tendermanagement.web.rest.api.errors.Status; import org.springframework.beans.factory.annotation.Autowired; @@ -35,9 +43,13 @@ import org.springframework.stereotype.Component; import org.springframework.web.multipart.MultipartFile; import java.net.URL; +import java.security.SecureRandom; import java.time.LocalDateTime; +import org.apache.commons.lang3.StringUtils; + import java.util.ArrayList; import java.util.List; +import java.util.Optional; import java.util.stream.Collectors; import static net.gepafin.tendermanagement.util.Utils.setIfUpdated; @@ -91,6 +103,64 @@ public class CompanyDocumentDao { @Autowired private AmazonS3 amazonS3; + @Autowired + private UserWithCompanyRepository userWithCompanyRepository; + + @Autowired + private ApplicationEvaluationRepository applicationEvaluationRepository; + + /** + * Instructor uploads a company document tied to an application. Files are stored under the same S3 layout as + * {@link CompanyDocumentTypeEnum#COMPANY_DOCUMENT}; persisted row type remains {@link CompanyDocumentTypeEnum#APPLICATION_DOCUMENT}. + */ + public List uploadInstructorCompanyDocumentToApplication(Long userId, List files, Long companyId, Long applicationId, Long documentCategoryId, LocalDateTime expirationDate, String name) { + log.info("Instructor upload company document to application. userId={}, companyId={}, applicationId={}", userId, companyId, applicationId); + ApplicationEntity application = applicationService.validateApplicationWithCompany(applicationId, companyId); + DocumentCategoryEntity categoryEntity = categoryDao.validateCategory(documentCategoryId); + Optional userWithCompanyOpt = userWithCompanyRepository.findByUserIdAndCompanyIdAndIsDeletedFalse(userId, companyId); + + LocalDateTime currentDate = LocalDateTime.now(); + if (expirationDate.isBefore(currentDate)) { + log.warn("Expiration date {} is before current time {}", expirationDate, currentDate); + throw new CustomValidationException(Status.VALIDATION_ERROR, Translator.toLocale(GepafinConstant.INVALID_EXPIRATION_DATE)); + } + CompanyDocumentTypeEnum storedType = CompanyDocumentTypeEnum.COMPANY_DOCUMENT; + List companyDocumentEntities = new ArrayList<>(); + for (MultipartFile file : files) { + log.info("Uploading instructor company document '{}' for companyId={}, applicationId={}", file.getOriginalFilename(), companyId, applicationId); + UploadFileOnAmazonS3Response uploadFileOnAmazonS3Response = uploadFileOnAmazonS3(file, CompanyDocumentTypeEnum.COMPANY_DOCUMENT, companyId); + if (uploadFileOnAmazonS3Response != null) { + CompanyDocumentEntity companyDocumentEntity = new CompanyDocumentEntity(); + companyDocumentEntity.setFileName(uploadFileOnAmazonS3Response.getFileName()); + companyDocumentEntity.setCompanyId(companyId); + companyDocumentEntity.setApplicationId(applicationId); + companyDocumentEntity.setType(storedType.getValue()); + companyDocumentEntity.setFilePath(uploadFileOnAmazonS3Response.getFilePath()); + companyDocumentEntity.setIsDeleted(false); + companyDocumentEntity.setUploadedBy(userId); + companyDocumentEntity.setName(name); + if (expirationDate.isBefore(currentDate.plusDays(7))) { + companyDocumentEntity.setStatus(CompanyDocumentStatusEnum.DUE.getValue()); + } else { + companyDocumentEntity.setStatus(CompanyDocumentStatusEnum.VALID.getValue()); + } + companyDocumentEntity.setCategoryEntity(categoryEntity); + companyDocumentEntity.setUserWithCompany(userWithCompanyOpt.orElse(null)); + companyDocumentEntity.setExpirationDate(expirationDate); + companyDocumentEntities.add(companyDocumentEntity); + } + } + companyDocumentRepository.saveAll(companyDocumentEntities); + companyDocumentEntities.forEach(entity -> loggingUtil.addVersionHistory( + VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.INSERT).oldData(null).newData(entity).build())); + + copyInstructorCompanyDocumentsToEvaluationDocuments(application, companyDocumentEntities, userId); + + return companyDocumentEntities.stream() + .map(this::convertToCompanyDocumentResponseBean) + .collect(Collectors.toList()); + } + public List uploadFileForCompany(Long userId, List files, Long companyId, Long documentCategoryId, CompanyDocumentTypeEnum companyDocumentSourceTypeEnum, LocalDateTime expirationDate,String name){ log.info("Uploading files for company. userId={}, companyId={}, documentCategoryId={}", userId, companyId, documentCategoryId); @@ -173,7 +243,9 @@ public class CompanyDocumentDao { public CompanyDocumentResponseBean convertToCompanyDocumentResponseBean(CompanyDocumentEntity entity) { CompanyDocumentResponseBean responseBean = new CompanyDocumentResponseBean(); DocumentCategoryEntity categoryEntity = entity.getCategoryEntity(); - DocumentCategoryResponse responseCategory = categoryDao.convertToResponseBean(categoryEntity); + if (categoryEntity != null) { + responseBean.setCategory(categoryDao.convertToResponseBean(categoryEntity)); + } responseBean.setId(entity.getId()); responseBean.setFileName(entity.getFileName()); responseBean.setType(CompanyDocumentTypeEnum.valueOf(entity.getType())); @@ -182,9 +254,9 @@ public class CompanyDocumentDao { responseBean.setExpirationDate(entity.getExpirationDate()); responseBean.setStatus(entity.getStatus()); responseBean.setUploadedBy(entity.getUploadedBy()); - responseBean.setCategory(responseCategory); responseBean.setName(entity.getName()); - responseBean.setUserWithCompanyId(entity.getUserWithCompany().getId()); + responseBean.setUserWithCompanyId(entity.getUserWithCompany() != null ? entity.getUserWithCompany().getId() : null); + responseBean.setApplicationId(entity.getApplicationId()); responseBean.setCreatedDate(entity.getCreatedDate()); responseBean.setUpdatedDate(entity.getUpdatedDate()); @@ -271,25 +343,205 @@ public class CompanyDocumentDao { else if(type.equals(CompanyDocumentTypeEnum.PERSONAL_DOCUMENT)){ userActionContext = UserActionContextEnum.UPLOAD_COMPANY_PERSONAL_DOCUMENT; } + else if (type.equals(CompanyDocumentTypeEnum.APPLICATION_DOCUMENT)) { + userActionContext = UserActionContextEnum.UPLOAD_COMPANY_DOCUMENT_TO_APPLICATION; + } return userActionContext; } + /** + * Beneficiary or confidi: duplicate into application storage. Super admin, pre-instructor (instructor), or + * instructor manager: evaluation S3 folder + {@link ApplicationEvaluationEntity#getEvaluationDocument()} merge. + * Other roles are not allowed. + */ public DocumentResponseBean createDuplicateCompanyDocument(HttpServletRequest request , Long userId ,Long companyDocumentId , Long applicationId , DocumentTypeEnum documentTypeEnum){ log.info("Creating duplicate of company document ID '{}' for application ID '{}'", companyDocumentId, applicationId); - ApplicationEntity applicationEntity = applicationService.validateApplication(applicationId); CompanyDocumentEntity companyDocumentEntity = validateCompanyDocument(companyDocumentId); - validator.validateUserWithCompany(request,companyDocumentEntity.getCompanyId()); + boolean beneficiaryOrConfidi = Boolean.TRUE.equals(validator.checkIsBeneficiary()) + || Boolean.TRUE.equals(validator.checkIsConfidi()); + boolean staffEvaluationRoles = Boolean.TRUE.equals(validator.checkIsSuperAdmin()) + || Boolean.TRUE.equals(validator.checkIsPreInstructor()) + || Boolean.TRUE.equals(validator.checkIsInstructorManager()); + + if (beneficiaryOrConfidi) { + ApplicationEntity applicationEntity = applicationService.validateApplication(applicationId); + validator.validateUserWithCompany(request, companyDocumentEntity.getCompanyId()); + + String companyDocumentPath = companyDocumentEntity.getFilePath(); + String documentPath = s3ConfigBean.generateDocumentPath(DocumentSourceTypeEnum.APPLICATION, applicationEntity.getCall().getId(), applicationId, 0L, 0L, 0L); + + log.info("Original Paths - oldPath: {}, newPath: {}", companyDocumentPath, documentPath); + + UploadFileOnAmazonS3Response response; + try { + response = amazonS3ServiceImpl.copyFile(companyDocumentEntity.getName(), companyDocumentPath, documentPath); + } catch (Exception e) { + log.error("Error occurred while uploading file from Amazon S3: {} for application ID '{}' and company Document ID '{}' ", e, applicationId, companyDocumentId); + throw new CustomValidationException(Status.VALIDATION_ERROR, + Translator.toLocale(GepafinConstant.UPLOAD_ERROR_S3)); + } + + DocumentEntity entity = new DocumentEntity(); + entity.setFilePath(response.getFilePath()); + entity.setFileName(response.getFileName()); + entity.setSource(DocumentSourceTypeEnum.APPLICATION.getValue()); + entity.setType(documentTypeEnum.getValue()); + entity.setSourceId(applicationId); + entity.setUploadedBy(userId); + + documentRepository.save(entity); + + /** This code is responsible for adding a version history log for the "inserting data" operation. **/ + loggingUtil.addVersionHistory( + VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.INSERT).oldData(null).newData(entity).build()); + + return callDao.convertToDocumentResponseBean(entity); + } + + if (staffEvaluationRoles) { + ApplicationEntity applicationEntity = applicationService.validateApplicationWithCompany(applicationId, companyDocumentEntity.getCompanyId()); + CompanyEntity companyEntity = companyService.validateCompany(companyDocumentEntity.getCompanyId()); + validator.validateHubId(request, companyEntity.getHub().getId()); + return addCompanyDocumentToEvaluation(applicationEntity, companyDocumentEntity, userId); + } + + throw new ForbiddenAccessException(Status.FORBIDDEN, Translator.toLocale(GepafinConstant.PERMISSION_DENIED)); + } + + /** + * Copies one company document into the evaluation S3 folder, persists a {@link DocumentEntity} with source + * {@link DocumentSourceTypeEnum#EVALUATION}, and appends the corresponding entry to + * {@link ApplicationEvaluationEntity#getEvaluationDocument()} (same JSON shape as instructor upload). + */ + private DocumentResponseBean addCompanyDocumentToEvaluation(ApplicationEntity application, + CompanyDocumentEntity companyDoc, Long userId) { + Long evaluationId = application.getApplicationEvaluationId(); + if (evaluationId == null || evaluationId <= 0) { + throw new CustomValidationException(Status.VALIDATION_ERROR, + Translator.toLocale(GepafinConstant.COMPANY_DOCUMENT_COPY_EVALUATION_REQUIRES_APPLICATION_EVALUATION)); + } + Optional evaluationOpt = + applicationEvaluationRepository.findByIdAndIsDeletedFalse(evaluationId); + if (evaluationOpt.isEmpty()) { + throw new CustomValidationException(Status.VALIDATION_ERROR, + Translator.toLocale(GepafinConstant.APPLICATION_EVALUATION_NOT_FOUND, evaluationId)); + } + + Long callId = application.getCall().getId(); + Long applicationId = application.getId(); + String evaluationFolderPath = s3ConfigBean.generateDocumentPath( + DocumentSourceTypeEnum.EVALUATION, callId, applicationId, evaluationId, 0L, 0L); + + DocumentEntity saved = persistInstructorCompanyDocumentAsEvaluationDocument( + companyDoc, evaluationFolderPath, evaluationId, userId); + EvaluationDocumentRequest entry = buildEvaluationDocumentEntry(companyDoc, saved); + if (entry == null) { + throw new CustomValidationException(Status.VALIDATION_ERROR, + Translator.toLocale(GepafinConstant.UPLOAD_ERROR_S3)); + } + + ApplicationEvaluationEntity evaluation = evaluationOpt.get(); + List merged = new ArrayList<>(parseEvaluationDocumentJson(evaluation.getEvaluationDocument())); + merged.add(entry); + evaluation.setEvaluationDocument(Utils.convertObjectToJson(merged)); + applicationEvaluationRepository.save(evaluation); + + return callDao.convertToDocumentResponseBean(saved); + } + + private EvaluationDocumentRequest buildEvaluationDocumentEntry(CompanyDocumentEntity companyDoc, DocumentEntity saved) { + if (saved == null || saved.getId() == null) { + return null; + } + EvaluationDocumentRequest entry = new EvaluationDocumentRequest(); + SecureRandom random = new SecureRandom(); + entry.setFieldId(NanoIdUtils.randomNanoId(random, NanoIdUtils.DEFAULT_ALPHABET, 10)); + entry.setNameValue(instructorDocumentDisplayName(companyDoc)); + entry.setFileValue(String.valueOf(saved.getId())); + entry.setValid(null); + return entry; + } + + /** + * For instructor uploads: copies each saved company document into the evaluation S3 folder, persists + * {@link DocumentEntity} rows (source EVALUATION), and merges entries into the existing {@code EVALUATION_DOCUMENT} + * column ({@link ApplicationEvaluationEntity#getEvaluationDocument()} JSON array of {@link EvaluationDocumentRequest}), + * appending to any data already stored there. + */ + private void copyInstructorCompanyDocumentsToEvaluationDocuments(ApplicationEntity application, + List instructorCompanyDocuments, Long userId) { + if (instructorCompanyDocuments == null || instructorCompanyDocuments.isEmpty()) { + return; + } + Long evaluationId = application.getApplicationEvaluationId(); + if (evaluationId == null || evaluationId <= 0) { + log.debug("Skip evaluation document copy for applicationId={}: no applicationEvaluationId set", application.getId()); + return; + } + Optional evaluationOpt = applicationEvaluationRepository.findByIdAndIsDeletedFalse(evaluationId); + if (evaluationOpt.isEmpty()) { + log.warn("Skip evaluation document copy: applicationEvaluationId={} not found or deleted", evaluationId); + return; + } + + Long callId = application.getCall().getId(); + Long applicationId = application.getId(); + String evaluationFolderPath = s3ConfigBean.generateDocumentPath( + DocumentSourceTypeEnum.EVALUATION, callId, applicationId, evaluationId, 0L, 0L); + + List newEntries = new ArrayList<>(); + for (CompanyDocumentEntity companyDoc : instructorCompanyDocuments) { + DocumentEntity saved = persistInstructorCompanyDocumentAsEvaluationDocument( + companyDoc, evaluationFolderPath, evaluationId, userId); + EvaluationDocumentRequest entry = buildEvaluationDocumentEntry(companyDoc, saved); + if (entry != null) { + newEntries.add(entry); + } + } + if (newEntries.isEmpty()) { + return; + } + + ApplicationEvaluationEntity evaluation = evaluationOpt.get(); + List merged = new ArrayList<>(parseEvaluationDocumentJson(evaluation.getEvaluationDocument())); + merged.addAll(newEntries); + evaluation.setEvaluationDocument(Utils.convertObjectToJson(merged)); + applicationEvaluationRepository.save(evaluation); + } + + private static List parseEvaluationDocumentJson(String json) { + if (StringUtils.isBlank(json)) { + return new ArrayList<>(); + } + List parsed = Utils.convertJsonToList(json, + new TypeReference>() {}); + return parsed != null ? new ArrayList<>(parsed) : new ArrayList<>(); + } + + + private static String instructorDocumentDisplayName(CompanyDocumentEntity companyDoc) { + if (companyDoc == null) { + return ""; + } + if (StringUtils.isNotBlank(companyDoc.getName())) { + return companyDoc.getName().trim(); + } + return StringUtils.defaultString(companyDoc.getFileName()); + } + + private DocumentEntity persistInstructorCompanyDocumentAsEvaluationDocument(CompanyDocumentEntity companyDocumentEntity, + String evaluationDocumentFolderPath, Long evaluationId, Long userId) { String companyDocumentPath = companyDocumentEntity.getFilePath(); - String documentPath = s3ConfigBean.generateDocumentPath(DocumentSourceTypeEnum.APPLICATION,applicationEntity.getCall().getId(),applicationId,0L,0L,0L); - - log.info("Original Paths - oldPath: {}, newPath: {}", companyDocumentPath, documentPath); + log.info("Instructor evaluation copy: companyDocumentId={}, oldPath={}, evaluationFolder={}", + companyDocumentEntity.getId(), companyDocumentPath, evaluationDocumentFolderPath); UploadFileOnAmazonS3Response response; try { - response = amazonS3ServiceImpl.copyFile(companyDocumentEntity.getName(), companyDocumentPath, documentPath); + response = amazonS3ServiceImpl.copyFile(companyDocumentEntity.getName(), companyDocumentPath, evaluationDocumentFolderPath); } catch (Exception e) { - log.error("Error occurred while uploading file from Amazon S3: {} for application ID '{}' and company Document ID '{}' ", e,applicationId,companyDocumentId); + log.error("Error copying instructor company document id={} to evaluation folder: {}", + companyDocumentEntity.getId(), e.getMessage(), e); throw new CustomValidationException(Status.VALIDATION_ERROR, Translator.toLocale(GepafinConstant.UPLOAD_ERROR_S3)); } @@ -297,27 +549,27 @@ public class CompanyDocumentDao { DocumentEntity entity = new DocumentEntity(); entity.setFilePath(response.getFilePath()); entity.setFileName(response.getFileName()); - entity.setSource(DocumentSourceTypeEnum.APPLICATION.getValue()); - entity.setType(documentTypeEnum.getValue()); - entity.setSourceId(applicationId); + entity.setSource(DocumentSourceTypeEnum.EVALUATION.getValue()); + entity.setType(DocumentTypeEnum.DOCUMENT.getValue()); + entity.setSourceId(evaluationId); entity.setUploadedBy(userId); - documentRepository.save(entity); - /** This code is responsible for adding a version history log for the "inserting data" operation. **/ loggingUtil.addVersionHistory( VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.INSERT).oldData(null).newData(entity).build()); - - DocumentResponseBean responseBean = callDao.convertToDocumentResponseBean(entity); - return responseBean; + return entity; } public List getAllCompanyDocument(UserEntity user , Long companyId, CompanyDocumentTypeEnum typeEnum){ log.info("Fetching all company documents for Company ID '{}', User ID '{}', Type '{}'", companyId, user.getId(), typeEnum); - if(Boolean.TRUE.equals(validator.checkIsBeneficiary())) { - validator.validateUserWithCompany(request, companyId); + CompanyEntity companyEntity = companyService.validateCompany(companyId); + if (Boolean.TRUE.equals(validator.checkIsBeneficiary())) { + if (typeEnum == CompanyDocumentTypeEnum.PERSONAL_DOCUMENT || typeEnum == null) { + validator.validateUserWithCompany(request, companyId); + } else { + validator.validateHubId(request, companyEntity.getHub().getId()); + } } - companyService.validateCompany(companyId); Specification spec = filterCompanyDocuments(companyId, user.getId(), typeEnum); @@ -345,6 +597,9 @@ public class CompanyDocumentDao { // Case 1: Fetch only COMPANY_DOCUMENT type documents for the given company predicate = builder.and(predicate, builder.equal(root.get("type"), CompanyDocumentTypeEnum.COMPANY_DOCUMENT.getValue())); + } else if (typeEnum == CompanyDocumentTypeEnum.APPLICATION_DOCUMENT) { + predicate = builder.and(predicate, builder.equal(root.get("type"), CompanyDocumentTypeEnum.APPLICATION_DOCUMENT.getValue())); + } else if (typeEnum == CompanyDocumentTypeEnum.PERSONAL_DOCUMENT) { // Case 2: Fetch only PERSONAL_DOCUMENT type documents for the logged-in user predicate = builder.and( @@ -353,20 +608,28 @@ public class CompanyDocumentDao { builder.equal(root.get("userWithCompany").get("userId"), userId) ); } - }else { - // Case 3: If typeEnum is null, fetch all documents for the company and personal documents for the user - Predicate companyPredicate = builder.equal(root.get("companyId"), companyId); - Predicate personalPredicate = builder.and( - builder.equal(root.get("type"), CompanyDocumentTypeEnum.PERSONAL_DOCUMENT.getValue()), - builder.equal(root.get("userWithCompany").get("userId"), userId) + } else { + + Predicate companyAndApplicationDocs = root.get("type").in( + CompanyDocumentTypeEnum.COMPANY_DOCUMENT.getValue(), + CompanyDocumentTypeEnum.APPLICATION_DOCUMENT.getValue() ); - predicate = builder.and(predicate, builder.or(companyPredicate, personalPredicate)); + predicate = builder.and(predicate, companyAndApplicationDocs); } return predicate; }; } - - + public List listApplicationCompanyDocumentsForEvaluation(Long applicationId, Long companyId) { + if (applicationId == null || companyId == null) { + return List.of(); + } + List entities = companyDocumentRepository.findByApplicationIdAndCompanyIdAndTypeAndStatusNot( + applicationId, + companyId, + CompanyDocumentTypeEnum.APPLICATION_DOCUMENT.getValue(), + CompanyDocumentStatusEnum.EXPIRED.getValue()); + return entities.stream().map(this::convertToCompanyDocumentResponseBean).collect(Collectors.toList()); + } } diff --git a/src/main/java/net/gepafin/tendermanagement/dao/NotificationDao.java b/src/main/java/net/gepafin/tendermanagement/dao/NotificationDao.java index 57236418..2f923c3d 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/NotificationDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/NotificationDao.java @@ -36,6 +36,7 @@ import org.springframework.stereotype.Component; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; @@ -423,15 +424,40 @@ public class NotificationDao { } public void sendNotificationToBeneficiaryForDocumentExpiration(CompanyDocumentEntity companyDocumentEntity, NotificationTypeEnum notificationTypeEnum) { - - CompanyEntity companyEntity = companyService.validateCompany(companyDocumentEntity.getCompanyId()); + Long companyId = companyDocumentEntity.getCompanyId(); + if (companyId == null) { + log.warn("Skipping document expiration notification: companyId is null for document id {}", companyDocumentEntity.getId()); + return; + } + CompanyEntity companyEntity = companyService.validateCompany(companyId); + List linkedUsers = userWithCompanyRepository.findByCompanyIdAndIsDeletedFalse(companyId); + if (linkedUsers == null || linkedUsers.isEmpty()) { + log.warn("Skipping document expiration notification: no users linked to company {} for document id {}", companyId, companyDocumentEntity.getId()); + return; + } Map placeHolders = new HashMap<>(); placeHolders.put("{{file_name}}", companyDocumentEntity.getFileName()); placeHolders.put("{{company_name}}", companyEntity.getCompanyName()); placeHolders.put("{{expiration_date}}", companyDocumentEntity.getExpirationDate().toString()); - NotificationReq notificationReq = createNotificationReq(notificationTypeEnum.getValue(), placeHolders, companyDocumentEntity.getUserWithCompany().getUserId(), companyDocumentEntity.getUserWithCompany(), - listOf(companyDocumentEntity.getCompanyId())); - sendNotification(notificationReq); + Map oneRowPerUser = new LinkedHashMap<>(); + for (UserWithCompanyEntity uwc : linkedUsers) { + if (uwc.getUserId() != null) { + oneRowPerUser.putIfAbsent(uwc.getUserId(), uwc); + } + } + if (oneRowPerUser.isEmpty()) { + log.warn("Skipping document expiration notification: no user ids on USER_WITH_COMPANY for company {}, document id {}", companyId, companyDocumentEntity.getId()); + return; + } + for (UserWithCompanyEntity uwc : oneRowPerUser.values()) { + NotificationReq notificationReq = createNotificationReq( + notificationTypeEnum.getValue(), + placeHolders, + uwc.getUserId(), + uwc, + listOf(companyId)); + sendNotification(notificationReq); + } } public PageableResponseBean> getNotificationsByUserIdAndCompanyIdByPagination(Long userId, Long companyId, NotificationRequestBean notificationRequestBean) { 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/ApplicationView.java b/src/main/java/net/gepafin/tendermanagement/entities/ApplicationView.java index 54593c2e..2a2d11fa 100644 --- a/src/main/java/net/gepafin/tendermanagement/entities/ApplicationView.java +++ b/src/main/java/net/gepafin/tendermanagement/entities/ApplicationView.java @@ -91,5 +91,7 @@ public class ApplicationView implements Serializable { @Column(name = "CREATED_DATE") private LocalDateTime createdDate; + @Column(name = "NDG") + private String ndg; } 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/entities/CompanyDocumentEntity.java b/src/main/java/net/gepafin/tendermanagement/entities/CompanyDocumentEntity.java index 1a08fffc..22ebd420 100644 --- a/src/main/java/net/gepafin/tendermanagement/entities/CompanyDocumentEntity.java +++ b/src/main/java/net/gepafin/tendermanagement/entities/CompanyDocumentEntity.java @@ -44,5 +44,7 @@ public class CompanyDocumentEntity extends BaseEntity { @JoinColumn(name = "DOCUMENT_CATEGORY_ID") private DocumentCategoryEntity categoryEntity; + @Column(name = "APPLICATION_ID") + private Long applicationId; } 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/ApplicationStatusForEvaluation.java b/src/main/java/net/gepafin/tendermanagement/enums/ApplicationStatusForEvaluation.java index 1a03e846..3055d749 100644 --- a/src/main/java/net/gepafin/tendermanagement/enums/ApplicationStatusForEvaluation.java +++ b/src/main/java/net/gepafin/tendermanagement/enums/ApplicationStatusForEvaluation.java @@ -8,7 +8,9 @@ public enum ApplicationStatusForEvaluation { ADMISSIBLE("ADMISSIBLE"), TECHNICAL_EVALUATION("TECHNICAL_EVALUATION"), AWAITING_TECHNICAL_EVALUATION("AWAITING_TECHNICAL_EVALUATION"), - TECHNICAL_EVALUATION_REJECTED("TECHNICAL_EVALUATION_REJECTED"); + TECHNICAL_EVALUATION_REJECTED("TECHNICAL_EVALUATION_REJECTED"), + DELETED("DELETED"), + DELETED_CONFIRMED("DELETED_CONFIRMED");; 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 d686a347..0796f22a 100644 --- a/src/main/java/net/gepafin/tendermanagement/enums/ApplicationStatusTypeEnum.java +++ b/src/main/java/net/gepafin/tendermanagement/enums/ApplicationStatusTypeEnum.java @@ -20,7 +20,9 @@ public enum ApplicationStatusTypeEnum { TECHNICAL_EVALUATION_REJECTED("TECHNICAL_EVALUATION_REJECTED"), AWAITING_TECHNICAL_EVALUATION("AWAITING_TECHNICAL_EVALUATION"), AWAITING_CONTRACT("AWAITING_CONTRACT"), - CONTRACT_SIGNED("CONTRACT_SIGNED"); + CONTRACT_SIGNED("CONTRACT_SIGNED"), + DELETED("DELETED"), + DELETED_CONFIRMED("DELETED_CONFIRMED"); private String 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/CompanyDocumentTypeEnum.java b/src/main/java/net/gepafin/tendermanagement/enums/CompanyDocumentTypeEnum.java index 9b163da0..973d17c4 100644 --- a/src/main/java/net/gepafin/tendermanagement/enums/CompanyDocumentTypeEnum.java +++ b/src/main/java/net/gepafin/tendermanagement/enums/CompanyDocumentTypeEnum.java @@ -2,7 +2,8 @@ package net.gepafin.tendermanagement.enums; public enum CompanyDocumentTypeEnum { COMPANY_DOCUMENT("COMPANY_DOCUMENT"), - PERSONAL_DOCUMENT("PERSONAL_DOCUMENT"); + PERSONAL_DOCUMENT("PERSONAL_DOCUMENT"), + APPLICATION_DOCUMENT("APPLICATION_DOCUMENT"); 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 9dc8b16d..6069386d 100644 --- a/src/main/java/net/gepafin/tendermanagement/enums/UserActionContextEnum.java +++ b/src/main/java/net/gepafin/tendermanagement/enums/UserActionContextEnum.java @@ -204,6 +204,7 @@ public enum UserActionContextEnum { GET_COMPANY_DOCUMENT("GET_COMPANY_DOCUMENT"), DELETE_COMPANY_DOCUMENT("DELETE_COMPANY_DOCUMENT"), UPLOAD_COMPANY_DOCUMENT("UPLOAD_COMPANY_DOCUMENT"), + UPLOAD_COMPANY_APPLICATION_DOCUMENT("UPLOAD_COMPANY_APPLICATION_DOCUMENT"), UPLOAD_COMPANY_PERSONAL_DOCUMENT("UPLOAD_COMPANY_PERSONAL_DOCUMENT"), GET_ALL_COMPANY_DOCUMENT("GET_ALL_COMPANY_DOCUMENT"), UPDATE_COMPANY_DOCUMENT("UPDATE_COMPANY_DOCUMENT"), @@ -235,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/ApplicationEvaluationFormResponse.java b/src/main/java/net/gepafin/tendermanagement/model/response/ApplicationEvaluationFormResponse.java index 3903daed..4e117c6f 100644 --- a/src/main/java/net/gepafin/tendermanagement/model/response/ApplicationEvaluationFormResponse.java +++ b/src/main/java/net/gepafin/tendermanagement/model/response/ApplicationEvaluationFormResponse.java @@ -24,6 +24,7 @@ public class ApplicationEvaluationFormResponse { private ApplicationEvaluationFormResponseBean applicationEvaluationFormResponse; private List files; private List evaluationDocument; + private List applicationCompanyDocuments; private List amendmentDetails; private LocalDateTime createdDate; private LocalDateTime updatedDate; 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 559edcc7..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; @@ -52,4 +53,10 @@ public class ApplicationResponse{ private EvaluationVersionEnum evaluationVersion; + 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/model/response/CompanyDocumentResponseBean.java b/src/main/java/net/gepafin/tendermanagement/model/response/CompanyDocumentResponseBean.java index 73c13fdc..47aa3d00 100644 --- a/src/main/java/net/gepafin/tendermanagement/model/response/CompanyDocumentResponseBean.java +++ b/src/main/java/net/gepafin/tendermanagement/model/response/CompanyDocumentResponseBean.java @@ -26,6 +26,8 @@ public class CompanyDocumentResponseBean extends BaseBean { private Long userWithCompanyId; + private Long applicationId; + private DocumentCategoryResponse category; } 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/repositories/CompanyDocumentRepository.java b/src/main/java/net/gepafin/tendermanagement/repositories/CompanyDocumentRepository.java index c37b993a..39731a4e 100644 --- a/src/main/java/net/gepafin/tendermanagement/repositories/CompanyDocumentRepository.java +++ b/src/main/java/net/gepafin/tendermanagement/repositories/CompanyDocumentRepository.java @@ -37,5 +37,7 @@ public interface CompanyDocumentRepository extends JpaRepository findByIdInAndIsDeletedFalseAndStatusNot(List ids, String status); + List findByApplicationIdAndCompanyIdAndTypeAndStatusNot( + Long applicationId, Long companyId, String type, String status); } diff --git a/src/main/java/net/gepafin/tendermanagement/repositories/UserWithCompanyRepository.java b/src/main/java/net/gepafin/tendermanagement/repositories/UserWithCompanyRepository.java index 13a197f1..357c3b0e 100644 --- a/src/main/java/net/gepafin/tendermanagement/repositories/UserWithCompanyRepository.java +++ b/src/main/java/net/gepafin/tendermanagement/repositories/UserWithCompanyRepository.java @@ -20,4 +20,6 @@ public interface UserWithCompanyRepository extends JpaRepository findByCompanyIdAndIsDeletedFalse(Long companyId); + } diff --git a/src/main/java/net/gepafin/tendermanagement/scheduler/CompanyDocumentExpirationScheduler.java b/src/main/java/net/gepafin/tendermanagement/scheduler/CompanyDocumentExpirationScheduler.java index e5a40408..6cd8b592 100644 --- a/src/main/java/net/gepafin/tendermanagement/scheduler/CompanyDocumentExpirationScheduler.java +++ b/src/main/java/net/gepafin/tendermanagement/scheduler/CompanyDocumentExpirationScheduler.java @@ -38,7 +38,7 @@ public class CompanyDocumentExpirationScheduler { @Autowired private ExpirationConfigRepository expirationConfigRepository; - private static final Logger log = LoggerFactory.getLogger(ExpirationScheduler.class); + private static final Logger log = LoggerFactory.getLogger(CompanyDocumentExpirationScheduler.class); @Scheduled(cron = "0 0 4 * * ?") public void processDocumentExpiration(){ diff --git a/src/main/java/net/gepafin/tendermanagement/service/ApplicationService.java b/src/main/java/net/gepafin/tendermanagement/service/ApplicationService.java index a1206ee3..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; @@ -34,6 +35,8 @@ public interface ApplicationService { public ApplicationResponse updateApplicationStatus(HttpServletRequest request, Long applicationId, ApplicationStatusTypeEnum status); + ApplicationResponse recordApplicationRegistrySegment(HttpServletRequest request, Long applicationId,ApplicationStatusTypeEnum status); + public ApplicationSignedDocumentResponse uploadSignedDocument(HttpServletRequest request, Long applicationId, MultipartFile file); public ApplicationSignedDocumentResponse getSignedDocument(HttpServletRequest request, Long applicationId); @@ -55,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/CompanyDocumentService.java b/src/main/java/net/gepafin/tendermanagement/service/CompanyDocumentService.java index ab640840..69cd7391 100644 --- a/src/main/java/net/gepafin/tendermanagement/service/CompanyDocumentService.java +++ b/src/main/java/net/gepafin/tendermanagement/service/CompanyDocumentService.java @@ -24,6 +24,6 @@ public interface CompanyDocumentService { List getAllCompanyDocument(HttpServletRequest request ,Long companyId , CompanyDocumentTypeEnum typeEnum); - + List uploadInstructorCompanyDocumentToApplication(HttpServletRequest request, List files, Long companyId, Long applicationId, Long documentCategoryId, LocalDateTime expirationDate, String name); } 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 12769ac7..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; @@ -107,6 +108,13 @@ public class ApplicationServiceImpl implements ApplicationService { return applicationDao.updateApplicationStatus(request, applicationId, status); } + @Override + @Transactional(rollbackFor = Exception.class) + public ApplicationResponse recordApplicationRegistrySegment(HttpServletRequest request, Long applicationId,ApplicationStatusTypeEnum status) { + UserEntity userEntity = validator.validateUser(request); + return applicationDao.recordApplicationRegistrySegment(request, applicationId, status); + } + @Override @Transactional(readOnly = true) public List getAllApplications(HttpServletRequest request, Long callId, Long companyId ,List statusList) { @@ -188,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/service/impl/CompanyDocumentServiceImpl.java b/src/main/java/net/gepafin/tendermanagement/service/impl/CompanyDocumentServiceImpl.java index b76a82f0..2281a79a 100644 --- a/src/main/java/net/gepafin/tendermanagement/service/impl/CompanyDocumentServiceImpl.java +++ b/src/main/java/net/gepafin/tendermanagement/service/impl/CompanyDocumentServiceImpl.java @@ -2,6 +2,7 @@ package net.gepafin.tendermanagement.service.impl; import jakarta.servlet.http.HttpServletRequest; import net.gepafin.tendermanagement.dao.CompanyDocumentDao; +import net.gepafin.tendermanagement.entities.CompanyEntity; import net.gepafin.tendermanagement.entities.UserEntity; import net.gepafin.tendermanagement.enums.CompanyDocumentTypeEnum; import net.gepafin.tendermanagement.enums.DocumentTypeEnum; @@ -9,6 +10,7 @@ import net.gepafin.tendermanagement.model.request.CompanyDocumentRequest; import net.gepafin.tendermanagement.model.response.CompanyDocumentResponseBean; import net.gepafin.tendermanagement.model.response.DocumentResponseBean; import net.gepafin.tendermanagement.service.CompanyDocumentService; +import net.gepafin.tendermanagement.service.CompanyService; import net.gepafin.tendermanagement.util.Utils; import net.gepafin.tendermanagement.util.Validator; import org.springframework.beans.factory.annotation.Autowired; @@ -28,6 +30,9 @@ public class CompanyDocumentServiceImpl implements CompanyDocumentService { @Autowired private CompanyDocumentDao companyDocumentDao; + @Autowired + private CompanyService companyService; + @Override public List uploadFileForCompany(HttpServletRequest request, List files, Long companyId, Long documentCategoryId , CompanyDocumentTypeEnum documentSourceTypeEnum, LocalDateTime expirationDate,String name) { Map userInfo = validator.getUserInfoFromToken(request); @@ -37,6 +42,17 @@ public class CompanyDocumentServiceImpl implements CompanyDocumentService { return companyDocumentDao.uploadFileForCompany(userId,files,companyId,documentCategoryId,documentSourceTypeEnum,expirationDate,name); } + @Override + public List uploadInstructorCompanyDocumentToApplication(HttpServletRequest request, List files, Long companyId, Long applicationId, Long documentCategoryId, LocalDateTime expirationDate, String name) { + Map userInfo = validator.getUserInfoFromToken(request); + Long userId = validator.getUserId(userInfo); + files.forEach(Utils::validateFileType); + validator.validateUser(request); + CompanyEntity company = companyService.validateCompany(companyId); + validator.validateHubId(request, company.getHub().getId()); + return companyDocumentDao.uploadInstructorCompanyDocumentToApplication(userId, files, companyId, applicationId, documentCategoryId, expirationDate, name); + } + @Override public CompanyDocumentResponseBean updateCompanyDocument(HttpServletRequest request, Long companyDocumentId, CompanyDocumentRequest companyDocumentRequest) { validator.validateUser(request); diff --git a/src/main/java/net/gepafin/tendermanagement/util/Validator.java b/src/main/java/net/gepafin/tendermanagement/util/Validator.java index 895e63d9..e946c170 100644 --- a/src/main/java/net/gepafin/tendermanagement/util/Validator.java +++ b/src/main/java/net/gepafin/tendermanagement/util/Validator.java @@ -197,6 +197,26 @@ public class Validator { } return false; } + + public Boolean checkIsDirector() { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + if (authentication != null && authentication.isAuthenticated()) { + for (GrantedAuthority authority : authentication.getAuthorities()) { + if (RoleStatusEnum.ROLE_DIRECTOR.getValue().equals(authority.getAuthority())) { + return true; + } + } + } + return false; + } + + /** Super admin (any hub) or director (hub must match application — enforced separately). */ + public void validateSuperAdminOrDirector() { + if (Boolean.TRUE.equals(checkIsSuperAdmin()) || Boolean.TRUE.equals(checkIsDirector())) { + return; + } + throw new ForbiddenAccessException(Status.FORBIDDEN, Translator.toLocale(GepafinConstant.PERMISSION_DENIED)); + } public Boolean checkIsConfidi() { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if (authentication != null && authentication.isAuthenticated()) { diff --git a/src/main/java/net/gepafin/tendermanagement/web/rest/api/ApplicationAmendmentRequestApi.java b/src/main/java/net/gepafin/tendermanagement/web/rest/api/ApplicationAmendmentRequestApi.java index 033f9fd6..98ca1a1b 100644 --- a/src/main/java/net/gepafin/tendermanagement/web/rest/api/ApplicationAmendmentRequestApi.java +++ b/src/main/java/net/gepafin/tendermanagement/web/rest/api/ApplicationAmendmentRequestApi.java @@ -206,7 +206,7 @@ public interface ApplicationAmendmentRequestApi { @PostMapping(value = "/user/{userId}/pagination", produces = { "application/json" }) ResponseEntity>>> getApplicationAmendmentByPaginnation(HttpServletRequest request, @Parameter(description = "The user id", required = true) @PathVariable("userId") Long userId, @RequestBody ApplicationAmendmentPaginationRequestBean userActionPaginationRequest); - @Operation(summary = "Api to create special Amendment", + @Operation(summary = "Api to create special Amendment new", responses = { @ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "404", description = "Not Found", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, examples = { diff --git a/src/main/java/net/gepafin/tendermanagement/web/rest/api/ApplicationApi.java b/src/main/java/net/gepafin/tendermanagement/web/rest/api/ApplicationApi.java index 989088d5..6dfbf1fd 100644 --- a/src/main/java/net/gepafin/tendermanagement/web/rest/api/ApplicationApi.java +++ b/src/main/java/net/gepafin/tendermanagement/web/rest/api/ApplicationApi.java @@ -132,6 +132,21 @@ public interface ApplicationApi { @Parameter(description = "The application id", required = true) @PathVariable("applicationId") Long applicationId, @Parameter(description = "status", required = true)@RequestParam(value = "status", required = true) ApplicationStatusTypeEnum status); + @Operation(summary = "Api to update application external status", + responses = { + @ApiResponse(responseCode = "200", description = "OK"), + @ApiResponse(responseCode = "404", description = "Not Found", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, examples = { + @ExampleObject(value = ErrorConstants.NOTFOUND_ERROR_EXAMPLE) })), + @ApiResponse(responseCode = "401", description = "Unauthorized", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, examples = { + @ExampleObject(value = ErrorConstants.UNAUTHORIZED_ERROR_EXAMPLE) })), + @ApiResponse(responseCode = "400", description = "Bad Request", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, examples = { + @ExampleObject(value = ErrorConstants.BADREQUEST_ERROR_EXAMPLE) })) }) + @PatchMapping(value = "/{applicationId}/external/status", produces = { "application/json" }) + @PreAuthorize("hasRole('ROLE_SUPER_ADMIN') || hasRole('ROLE_DIRECTOR')") + ResponseEntity> updateApplicationSegment(HttpServletRequest request, + @Parameter(description = "The application id", required = true) @PathVariable("applicationId") Long applicationId, + @Parameter(description = "Application status", required = true) @RequestParam ApplicationStatusTypeEnum status); + @Operation(summary = "API to generate PDF for an application", responses = { @ApiResponse(responseCode = "200", description = "OK", content = @Content(mediaType = "application/pdf")), @@ -234,7 +249,7 @@ public interface ApplicationApi { @ExampleObject(value = ErrorConstants.BADREQUEST_ERROR_EXAMPLE)})) }) @GetMapping(value = "/call/{callId}/csv") - @PreAuthorize("hasRole('ROLE_SUPER_ADMIN')") + @PreAuthorize("hasRole('ROLE_SUPER_ADMIN')|| hasRole('ROLE_INSTRUCTOR_MANAGER')") public ResponseEntity exportCsv( HttpServletRequest request, @Parameter(description = "The call id", required = true) @PathVariable(value = "callId", required = true) Long callId); diff --git a/src/main/java/net/gepafin/tendermanagement/web/rest/api/CompanyDocumentApi.java b/src/main/java/net/gepafin/tendermanagement/web/rest/api/CompanyDocumentApi.java index fdd53635..4b8656ca 100644 --- a/src/main/java/net/gepafin/tendermanagement/web/rest/api/CompanyDocumentApi.java +++ b/src/main/java/net/gepafin/tendermanagement/web/rest/api/CompanyDocumentApi.java @@ -19,6 +19,7 @@ import org.springframework.format.annotation.DateTimeFormat; import org.springframework.http.HttpStatus; 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.*; import org.springframework.web.multipart.MultipartFile; @@ -48,6 +49,27 @@ public interface CompanyDocumentApi { return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED); } + @PreAuthorize("hasRole('ROLE_SUPER_ADMIN') || hasRole('ROLE_INSTRUCTOR_MANAGER') || hasRole('ROLE_PRE_INSTRUCTOR')") + @Operation(summary = "API to upload company document for an application (Only for instructor)", + responses = { + @ApiResponse(responseCode = "200", description = "OK"), + @ApiResponse(responseCode = "404", description = "Not Found", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, examples = { + @ExampleObject(value = ErrorConstants.NOTFOUND_ERROR_EXAMPLE) })), + @ApiResponse(responseCode = "401", description = "Unauthorized", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, examples = { + @ExampleObject(value = ErrorConstants.UNAUTHORIZED_ERROR_EXAMPLE) })), + @ApiResponse(responseCode = "400", description = "Bad Request", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, examples = { + @ExampleObject(value = ErrorConstants.BADREQUEST_ERROR_EXAMPLE) }))}) + @PostMapping(value = "company/{companyId}/application/{applicationId}/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + default ResponseEntity>> uploadInstructorCompanyDocumentToApplication(HttpServletRequest httpServletRequest, + @Parameter(description = "Company Id", required = true) @PathVariable("companyId") Long companyId, + @Parameter(description = "Application Id", required = true) @PathVariable("applicationId") Long applicationId, + @Parameter(description = "Document category id", required = true) @RequestParam(value = "documentCategoryId") Long documentCategoryId, + @Parameter(description = "Display name", required = true) @RequestParam(value = "name") String name, + @Parameter(description = "Expiration date (ISO-8601)", required = true) @RequestParam("expirationDate") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime expirationDate, + @RequestParam("file") List files) { + return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED); + } + @Operation(summary = "Api to update company document", responses = { @ApiResponse(responseCode = "200", description = "OK"), 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/ApplicationApiController.java b/src/main/java/net/gepafin/tendermanagement/web/rest/api/impl/ApplicationApiController.java index c5817500..3e850280 100644 --- a/src/main/java/net/gepafin/tendermanagement/web/rest/api/impl/ApplicationApiController.java +++ b/src/main/java/net/gepafin/tendermanagement/web/rest/api/impl/ApplicationApiController.java @@ -22,6 +22,8 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import jakarta.validation.Valid; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; @@ -141,6 +143,18 @@ public class ApplicationApiController implements ApplicationApi { .body(new Response<>(applicationResponse, Status.SUCCESS, Translator.toLocale(GepafinConstant.APPLICATION_STATUS_UPDATED_SUCCESSFULLY))); } + @Override + public ResponseEntity> updateApplicationSegment(HttpServletRequest request, Long applicationId, + ApplicationStatusTypeEnum status) { + + loggingUtil.logUserAction( + UserActionRequest.builder().request(request).actionType(UserActionLogsEnum.UPDATE).actionContext(UserActionContextEnum.UPDATE_APPLICATION_STATUS).build()); + + ApplicationResponse applicationResponse = applicationService.recordApplicationRegistrySegment(request, applicationId, status); + return ResponseEntity.status(HttpStatus.OK) + .body(new Response<>(applicationResponse, Status.SUCCESS, Translator.toLocale(GepafinConstant.APPLICATION_STATUS_UPDATED_SUCCESSFULLY))); + } + @Override public ResponseEntity generateApplicationPdf(HttpServletRequest request, Long applicationId) { diff --git a/src/main/java/net/gepafin/tendermanagement/web/rest/api/impl/CompanyDocumentApiControlller.java b/src/main/java/net/gepafin/tendermanagement/web/rest/api/impl/CompanyDocumentApiControlller.java index 543ec40d..191d7a31 100644 --- a/src/main/java/net/gepafin/tendermanagement/web/rest/api/impl/CompanyDocumentApiControlller.java +++ b/src/main/java/net/gepafin/tendermanagement/web/rest/api/impl/CompanyDocumentApiControlller.java @@ -57,6 +57,14 @@ public class CompanyDocumentApiControlller implements CompanyDocumentApi { } } + @Override + public ResponseEntity>> uploadInstructorCompanyDocumentToApplication(HttpServletRequest request, Long companyId, Long applicationId, Long documentCategoryId, String name, LocalDateTime expirationDate, List files) { + loggingUtil.logUserAction(UserActionRequest.builder().request(request).actionType(UserActionLogsEnum.UPLOAD).actionContext(UserActionContextEnum.UPLOAD_COMPANY_DOCUMENT_TO_APPLICATION).build()); + List responseBeans = companyDocumentService.uploadInstructorCompanyDocumentToApplication(request, files, companyId, applicationId, documentCategoryId, expirationDate, name); + return ResponseEntity.status(HttpStatus.CREATED) + .body(new Response<>(responseBeans, Status.SUCCESS, Translator.toLocale(GepafinConstant.FILES_UPLOADED_MSG))); + } + @Override public ResponseEntity> updateCompanyDocument(HttpServletRequest httpServletRequest, Long companyDocumentId, CompanyDocumentRequest companyDocumentRequest) { 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/application-local.properties b/src/main/resources/application-local.properties index bcae6352..e1abc344 100644 --- a/src/main/resources/application-local.properties +++ b/src/main/resources/application-local.properties @@ -1,5 +1,5 @@ # DataSource Configuration -spring.datasource.url=jdbc:postgresql://localhost:5432/gepafin_local +spring.datasource.url=jdbc:postgresql://localhost:5432/postgres spring.datasource.username=postgres spring.datasource.password=root spring.datasource.driver-class-name=org.postgresql.Driver diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index f3798939..9469ec99 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -77,6 +77,9 @@ spring.rabbitmq.connection-timeout=120000 app.bandi.login.url.suffix=/loginadmin app.confidi.login.url.suffix=/confidi +excel.fill.api.url=https://excel-gepafin-dev-be.bflows.ai/excel/fill +excel.fill.api.token=a3f8c2d1e4b5a6f7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1 + #sviluppumbria protocol codAoo=SVILUMBRIA-01 sviluppumbria.username=protocollatoresvilumbria 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 9c50da89..1722b34b 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 @@ -3205,7 +3205,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + role_type='ROLE_GEPAFIN_OPERATOR' + + + + + role_type='ROLE_BENEFICIARY' + + + + + role_type='ROLE_PRE_INSTRUCTOR' + + + + + role_type='ROLE_INSTRUCTOR_MANAGER' + + + + + role_type='ROLE_CONFIDI' + + + + + role_type='ROLE_DIRECTOR' + + + + + role_type='ROLE_SUPER_ADMIN' + + + 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/db/dump/insert_form_field_25_03_2026.sql b/src/main/resources/db/dump/insert_form_field_25_03_2026.sql new file mode 100644 index 00000000..17f8a507 --- /dev/null +++ b/src/main/resources/db/dump/insert_form_field_25_03_2026.sql @@ -0,0 +1,12 @@ +INSERT INTO FORM_FIELD (SORT_ORDER, NAME, LABEL, DESCRIPTION, SETTINGS, VALIDATORS, CREATED_DATE, UPDATED_DATE) +VALUES +( + 24, + 'spreadsheet', + 'Foglio di Calcolo', + 'Modello di foglio di calcolo con variabili dinamiche', + '[{name: "label",value: "Foglio di Calcolo"},{name: "template",value: {}}]', + '{}', + CURRENT_TIMESTAMP, + CURRENT_TIMESTAMP +); \ No newline at end of file diff --git a/src/main/resources/db/dump/update_application_view_23_03_2026.sql b/src/main/resources/db/dump/update_application_view_23_03_2026.sql new file mode 100644 index 00000000..3d2e97d8 --- /dev/null +++ b/src/main/resources/db/dump/update_application_view_23_03_2026.sql @@ -0,0 +1,86 @@ +DROP VIEW IF EXISTS gepafin_schema.application_view ; + + +CREATE OR REPLACE VIEW application_view AS + +SELECT + a.id, + a.status, + a.submission_date, + a.comments, + a.amount_requested, + a.amount_accepted, + a.date_accepted, + a.date_rejected, + a.is_deleted, + a.hub_id, + a.user_id, + a.ndg, + + a.evaluation_version AS evaluation_version, + a.updated_date AS modified_date, + a.created_date AS created_date, + + + + -- Call Details + a.call_id AS call_id, + cl.name AS call_title, + cl.end_date AS call_end_date, + cl.end_time AS call_end_time, + + -- Company Details + a.COMPANY_ID AS company_id, + c.company_name AS company_name, + + -- Protocol Details + p.protocol_number AS protocol_number, + + + -- Assigned User Details from ASSIGNED_APPLICATION and GEPAFIN_USER + COALESCE(aa.user_id, NULL) AS assigned_user_id, + COALESCE( + NULLIF( + TRIM(CONCAT( + COALESCE(u.first_name, ''), ' ', + COALESCE(u.last_name, '') + )), + '' + ), + '' + ) AS assigned_user_name, + +-- User with Company Details (From Application's User) + COALESCE(uwc.id, NULL) AS user_with_company_id + + +FROM gepafin_schema.APPLICATION a + +-- Join Call Entity +LEFT JOIN gepafin_schema.CALL cl + ON a.CALL_ID = cl.id + +-- Join Company Entity (Ensuring it is not deleted) +LEFT JOIN gepafin_schema.COMPANY c + ON a.COMPANY_ID = c.id + +-- Join Protocol Entity +LEFT JOIN gepafin_schema.PROTOCOL p + ON a.PROTOCOL_NUMBER = p.id + +-- Join Assigned Application Entity (Ensuring it is not deleted) +LEFT JOIN gepafin_schema.assigned_applications aa + ON a.id = aa.APPLICATION_ID + AND (aa.IS_DELETED IS FALSE OR aa.IS_DELETED IS NULL) + + -- Join User Entity (Get First & Last Name Combined) +LEFT JOIN gepafin_schema.GEPAFIN_USER u + ON aa.user_id = u.id + + -- Get User With Company ID (From Application's User & Company) + LEFT JOIN gepafin_schema.USER_WITH_COMPANY uwc + ON a.user_id = uwc.user_id + AND a.COMPANY_ID = uwc.company_id + AND uwc.is_deleted = FALSE -- Ensuring the user is active + +WHERE a.IS_DELETED IS FALSE OR a.IS_DELETED IS NULL; \ No newline at end of file diff --git a/src/main/resources/db/dump/update_system_email_template_09_04_2026.sql b/src/main/resources/db/dump/update_system_email_template_09_04_2026.sql new file mode 100644 index 00000000..d8550728 --- /dev/null +++ b/src/main/resources/db/dump/update_system_email_template_09_04_2026.sql @@ -0,0 +1,202 @@ +-- 1 +UPDATE gepafin_schema.system_email_template +SET template_name = 'Application submission template to beneficiary and company', + "type" = 'APPLICATION_SUBMISSION_TO_USER_AND_COMPANY', + html_content = ' + +
+

Buongiorno,

+

+ Si comunica che, in riferimento alla domanda di concessione di + Finanziamento agevolato a valere sul Fondo prestiti + {{call_name}} di cui all''oggetto, la stessa è stata + regolarmente acquisita ed è stata registrata con Protocollo n. + {{protocol_number}} del {{date}} alle + {{time}}. +

+

Distinti Saluti,

+

+ {{email_signature}} +

+
+ + ', + subject = 'BANDO {{call_name}} - Domanda di concessione di finanziamento agevolato {{company_name}}' +WHERE email_scenario = 'APPLICATION_SUBMITTED' + AND type = 'APPLICATION_SUBMISSION_TO_USER_AND_COMPANY' + AND hub_id IS NULL; + +-- 2 +UPDATE gepafin_schema.system_email_template +SET template_name='Application submission template to gepafin', + "type"='APPLICATION_SUBMISSION_TO_GEPAFIN', + html_content=' + +
+

+ In riferimento alla domanda di concessione di Finanziamento agevolato a valere sul Fondo prestiti + {{call_name}} di cui all''oggetto, la stessa è stata regolarmente acquisita ed è stata + registrata con Protocollo n. {{protocol_number}} del {{date}} alle {{time}}. +

+

Distinti Saluti,

+

+ {{email_signature}} +

+
+ + ', + subject='BANDO {{call_name}} - Domanda di concessione di finanziamento agevolato {{company_name}}' +WHERE email_scenario='APPLICATION_SUBMITTED' + AND type='APPLICATION_SUBMISSION_TO_GEPAFIN' + AND hub_id IS NULL; + +-- 3 +UPDATE gepafin_schema.system_email_template +SET html_content=' + +
+

RICHIESTA INTEGRAZIONE DOCUMENTALE

+

Buongiorno,

+

In riferimento alla domanda di concessione di Finanziamento agevolato a valere sul Fondo prestiti + {{call_name}} di cui al Protocollo n. {{protocol_number}} del + {{protocol_date}} e {{protocol_time}}, alla luce dell''attività istruttoria svolta, + segnaliamo quanto segue: +

+
    + {{form_dataInput}} +
+

{{note}}

+

Vi invitiamo a fornire quanto sopra richiesto integrando la documentazione sia caricandola all''interno dello sportello + online {{platform_link}} che inviandola a mezzo PEC all''indirizzo + bandi.gepafin@legalmail.it entro e non oltre {{response_days}} giorni dal ricevimento della presente comunicazione, + precisando che, in caso di mancata ricezione nei termini indicati, saremo costretti a non prendere in considerazione la Vostra richiesta di finanziamento. +

+

Vi informiamo che per la ricezione della PEC farà fede la ricevuta di avvenuta consegna che attesterà il buon esito + dell''invio. La documentazione trasmessa e le informazioni fornite saranno processate dall''istruttore assegnatario della pratica. +

+

Distinti Saluti,

+

{{email_signature}}

+
+ + ' +WHERE email_scenario='APPLICATION_AMENDMENT_REQUESTED' + AND hub_id IS NULL; + +-- 4 +UPDATE gepafin_schema.system_email_template +SET subject='BANDO {{call_name}} - Domanda di finanziamento non ammessa {{company_name}}' +WHERE email_scenario='APPLICATION_AMENDMENT_EXPIRED'; + +-- 5 +UPDATE gepafin_schema.system_email_template +SET subject='BANDO {{call_name}} – Esito positivo istruttoria di ammissibilità {{company_name}}' +WHERE email_scenario='APPLICATION_ADMISSIBLE'; + +-- 6 +UPDATE gepafin_schema.system_email_template +SET html_content=' +
+

Buongiorno,

+

Si comunica che, in riferimento alla domanda a valere sul bando “{{call_name}}” di cui al + Protocollo n. {{protocol_number}} del {{protocol_date}} alle {{protocol_time}}, +

+

+ {{tipo_inammissibilita}}.

+

Le motivazioni sono le seguenti: {{form_text}}

+

Vi ricordiamo che i Beneficiari che hanno presentato richieste valutate non ammissibili entro 10 giorni dalla data di ricevimento della presente potranno finoltrare richiesta di chiarimenti e/o osservazioni alla scrivente Società ai sensi e per gli effetti dell''art.10 bis della L.241/1990 e s.m.i.

+

Distinti Saluti,

+

{{email_signature}}

+
+ + ' +WHERE email_scenario='APPLICATION_REJECTED' + AND hub_id IS NULL; + + +-- 7 +UPDATE gepafin_schema.system_email_template +SET html_content=' + +
+

PROMEMORIA PER LA PRESENTAZIONE DELL''Soccorso Istruttorio

+

Buongiorno,

+

Questo è un promemoria per completare la presentazione dell''Soccorso Istruttorio entro il termine specificato. Di seguito i dettagli:

+
    +
  • Amendment ID: {{amendment_id}}
  • +
  • Data di Scadenza: {{amendment_due_date}}
  • +
+

Si prega di assicurarsi che l''Soccorso Istruttorio venga presentato entro la data di scadenza per evitare ritardi. Inviare l''Soccorso Istruttorio tramite la piattaforma online {{platform_link}}

+

Distinti saluti,

+

{{email_signature}}

+
+ + ' +WHERE email_scenario='APPLICATION_AMENDMENT_REMINDER'; + +-- 8 +UPDATE gepafin_schema.system_email_template +SET template_name='Welcome Email for New Confidi User' +WHERE email_scenario='USER_CREATION' AND type='USER_ONBOARDING_CONFIDI'; + +-- 9 +UPDATE gepafin_schema.system_email_template +SET template_name='Password Reset Link Email (Italian)' +WHERE email_scenario='PASSWORD_RESET_REQUEST'; + +-- 10 (hub specific) +UPDATE gepafin_schema.system_email_template +SET html_content=' + +
+

RICHIESTA INTEGRAZIONE DOCUMENTALE

+

Buongiorno,

+

In riferimento alla domanda di concessione di Finanziamento agevolato a valere sul Bando + "{{call_name}}" di cui al Protocollo n. {{protocol_number}} del + {{protocol_date}} e {{protocol_time}}, alla luce dell''attività istruttoria svolta, + segnaliamo quanto segue: +

+ {{note}} +

Vi invitiamo a fornire quanto sopra richiesto integrando la documentazione caricandola all''interno dello sportello + online {{platform_link}} entro e non oltre {{response_days}} giorni dal ricevimento della presente comunicazione, + precisando che, in caso di mancata ricezione nei termini indicati, saremo costretti a non prendere in considerazione la Vostra richiesta di finanziamento. +

+

La documentazione trasmessa e le informazioni fornite saranno processate dall''istruttore assegnatario della pratica. +

+

Distinti Saluti,

+

{{email_signature}}

+
+ + ' +WHERE email_scenario='APPLICATION_AMENDMENT_REQUESTED' AND hub_id=2; + +-- 11 (hub specific reject) +UPDATE gepafin_schema.system_email_template +SET subject='BANDO {{call_name}} – Esito negativo istruttoria di ammissibilità {{company_name}}' +WHERE email_scenario='APPLICATION_REJECTED' AND hub_id=2; + +-- 12 +UPDATE gepafin_schema.system_email_template +SET template_name='Welcome Email for New Bandi User' +WHERE email_scenario='USER_CREATION' AND type='USER_ONBOARDING_BANDI'; + +-- 13 +UPDATE gepafin_schema.system_email_template +SET template_name='Application submission failure notification' +WHERE email_scenario='APPLICATION_SUBMISSION_FAILURE'; + +-- 14 +UPDATE gepafin_schema.system_email_template +SET subject='BANDO – "{{call_name}}" – Esito negativo della valutazione tecnica – {{company_name}}' +WHERE email_scenario='APPLICATION_TECHNICAL_EVALUATION_REJECTED'; + +-- 15 +UPDATE gepafin_schema.system_email_template +SET subject='Comunicazione esito valutazione tecnica ed economico-finanziaria – Avviso {{call_name}} ' +WHERE email_scenario='SPECIAL_APPLICATION_AMENDMENT_REQUESTED'; + +-- 16 +UPDATE gepafin_schema.system_email_template +SET subject='Comunicazione esito valutazione tecnica ed economico-finanziaria – Avviso {{call_name}} ' +WHERE email_scenario='SPECIAL_APPLICATION_AMENDMENT_REQUESTED_BLUE_TONGUE'; + + diff --git a/src/main/resources/db/dump/update_system_email_template_special_amendment_16_03_2026.sql b/src/main/resources/db/dump/update_system_email_template_special_amendment_16_03_2026.sql new file mode 100644 index 00000000..03cd457d --- /dev/null +++ b/src/main/resources/db/dump/update_system_email_template_special_amendment_16_03_2026.sql @@ -0,0 +1,7 @@ +UPDATE gepafin_schema.system_email_template +SET subject='Comunicazione esito valutazione tecnica ed economico-finanziaria– Avviso {{call_name}} ' +WHERE email_scenario='SPECIAL_APPLICATION_AMENDMENT_REQUESTED'; + +UPDATE gepafin_schema.system_email_template +SET subject='Comunicazione esito valutazione tecnica ed economico-finanziaria– Avviso {{call_name}} ' +WHERE email_scenario='SPECIAL_APPLICATION_AMENDMENT_REQUESTED_BLUE_TONGUE'; \ No newline at end of file diff --git a/src/main/resources/message_en.properties b/src/main/resources/message_en.properties index b223a21d..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. @@ -230,6 +236,8 @@ call.not.started.yet = The call has not started yet. Please wait until the speci call.already.ended = The call has already ended. You cannot submit the application after the deadline. status.updated.successfully=Status updated successfully. application.status.updated.successfully = Application status updated successfully. +application.status.transition.restricted=This status change is not available through this operation. +application.registry.segment.invalid=Invalid segment value. application.already.in.provided.status=Application is already in provided status. delegation.not.found=Delegation not found. user.company.relation.not.found=User with the specified company relation not found. @@ -389,6 +397,7 @@ company.document.fetched.successfully = Company Document fetched successfully. company.document.updated.successfully = Company Document Updated successfully. category.cannot.be.deleted = Category cannot be deleted as it is associated with company documents. company.document.copied.successfully = Company Document Copied successfully. +company.document.copy.evaluation.requires.application.evaluation=Copy to evaluation requires an application evaluation linked to this application. invalid.expiration.date = Invalid Expiration Date diff --git a/src/main/resources/message_it.properties b/src/main/resources/message_it.properties index bcff6a8f..6214d5db 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 @@ -374,6 +380,7 @@ document.category.not.found = Categoria documento non trovata. document.category.get.success = Categoria del documento recuperata correttamente. document.category.success =Categoria documento creata correttamente. company.document.copied.successfully = Documento aziendale copiato correttamente. +company.document.copy.evaluation.requires.application.evaluation=La copia nella valutazione richiede una valutazione dell'applicazione collegata a questa domanda. error.moving.file.to.deleted.folder = Si è verificato un errore durante lo spostamento del file aziendale nella cartella eliminata.ss company.document.fetched.successfully = Documento aziendale recuperato con successo. @@ -424,5 +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. -provide.valid.vat.number=Inserisci un numero di partita IVA valido per procedere con NDG. \ No newline at end of file +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.registry.segment.invalid=Valore del segmento non valido.