Merge pull request #393 from Kitzanos/develop

Sync Master with Develop (09/04/2026)
This commit is contained in:
Rinaldo
2026-04-09 14:42:24 +02:00
committed by GitHub
56 changed files with 1925 additions and 57 deletions

View File

@@ -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";

View File

@@ -1164,8 +1164,12 @@ public class ApplicationAmendmentRequestDao {
applicationEvaluationRepository.save(existingApplicationAmendment.getApplicationEvaluationEntity());
log.info("Updated ApplicationEvaluation status to OPEN for ID: {}", existingApplicationEvaluationEntity.getId());
if(Boolean.FALSE.equals(existingApplicationAmendment.getType().equals(ApplicationAmendmentRequestTypeEnum.SPECIAL.getValue()))){
application.setStatus(application.getPreviousStatus());
String previousApplicationStatus = application.getPreviousStatus();
if (previousApplicationStatus != null) {
application.setStatus(previousApplicationStatus);
} else if (ApplicationAmendmentRequestTypeEnum.SPECIAL.getValue().equals(existingApplicationAmendment.getType())) {
application.setStatus(ApplicationStatusTypeEnum.ADMISSIBLE.getValue());
log.warn("Special amendment close: previousStatus was null for applicationId={}, defaulting to ADMISSIBLE", application.getId());
}
applicationRepository.save(application);
log.info("Updated Application status to previous state for Application ID: {}", application.getId());
@@ -1222,8 +1226,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. **/
@@ -1910,6 +1928,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<ApplicationAmendmentRequestEntity> 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);

View File

@@ -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<ApplicationRankingActionTypeEnum> rankingActionTypes) {
CallEntity call = callRepository.findById(callId)
.orElseThrow(() -> new ResourceNotFoundException(Status.NOT_FOUND,
Translator.toLocale(GepafinConstant.CALL_NOT_FOUND)));
Specification<ApplicationRankingView> spec = (root, query, criteriaBuilder) -> {
List<jakarta.persistence.criteria.Predicate> predicates = new ArrayList<>();
predicates.add(criteriaBuilder.equal(root.get("callId"), callId));
if (rankingActionTypes != null && !rankingActionTypes.isEmpty()) {
List<String> 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<ApplicationRankingView> 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<ApplicationAmendmentRequestEntity> 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;
}

View File

@@ -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<String, String> 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<String, String> 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()}).
* <p>
* 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<ContentResponseBean> 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<ApplicationFormFieldRequestBean> spreadsheetUpdates = new ArrayList<>();
for (ContentResponseBean contentResponseBean : contentResponseBeans) {
if (!GepafinConstant.SPREADSHEET.equals(contentResponseBean.getName())) {
continue;
}
String fieldId = contentResponseBean.getId();
Optional<ApplicationEvaluationFormFieldEntity> 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<String, Object> requestBody = new LinkedHashMap<>();
requestBody.put("call_id", callId);
requestBody.put("application_id", applicationId);
requestBody.put(GepafinConstant.TEMPLATE, spreadsheetField);
try {
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<Object> 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());

View File

@@ -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<LocalDateTime> 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);

View File

@@ -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<CompanyDocumentResponseBean> uploadInstructorCompanyDocumentToApplication(Long userId, List<MultipartFile> 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<UserWithCompanyEntity> 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<CompanyDocumentEntity> 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<CompanyDocumentResponseBean> uploadFileForCompany(Long userId, List<MultipartFile> 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<ApplicationEvaluationEntity> 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<EvaluationDocumentRequest> 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<CompanyDocumentEntity> 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<ApplicationEvaluationEntity> 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<EvaluationDocumentRequest> 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<EvaluationDocumentRequest> merged = new ArrayList<>(parseEvaluationDocumentJson(evaluation.getEvaluationDocument()));
merged.addAll(newEntries);
evaluation.setEvaluationDocument(Utils.convertObjectToJson(merged));
applicationEvaluationRepository.save(evaluation);
}
private static List<EvaluationDocumentRequest> parseEvaluationDocumentJson(String json) {
if (StringUtils.isBlank(json)) {
return new ArrayList<>();
}
List<EvaluationDocumentRequest> parsed = Utils.convertJsonToList(json,
new TypeReference<List<EvaluationDocumentRequest>>() {});
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<CompanyDocumentResponseBean> 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<CompanyDocumentEntity> 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<CompanyDocumentResponseBean> listApplicationCompanyDocumentsForEvaluation(Long applicationId, Long companyId) {
if (applicationId == null || companyId == null) {
return List.of();
}
List<CompanyDocumentEntity> entities = companyDocumentRepository.findByApplicationIdAndCompanyIdAndTypeAndStatusNot(
applicationId,
companyId,
CompanyDocumentTypeEnum.APPLICATION_DOCUMENT.getValue(),
CompanyDocumentStatusEnum.EXPIRED.getValue());
return entities.stream().map(this::convertToCompanyDocumentResponseBean).collect(Collectors.toList());
}
}

View File

@@ -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<UserWithCompanyEntity> 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<String, String> 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<Long, UserWithCompanyEntity> 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<List<NotificationResponse>> getNotificationsByUserIdAndCompanyIdByPagination(Long userId, Long companyId, NotificationRequestBean notificationRequestBean) {

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -91,5 +91,7 @@ public class ApplicationView implements Serializable {
@Column(name = "CREATED_DATE")
private LocalDateTime createdDate;
@Column(name = "NDG")
private String ndg;
}

View File

@@ -102,5 +102,8 @@ public class CallEntity extends BaseEntity {
@Column(name = "allow_multiple_applications")
private Boolean allowMultipleApplications;
@Column(name = "ranking_type")
private String rankingType;
}

View File

@@ -44,5 +44,7 @@ public class CompanyDocumentEntity extends BaseEntity {
@JoinColumn(name = "DOCUMENT_CATEGORY_ID")
private DocumentCategoryEntity categoryEntity;
@Column(name = "APPLICATION_ID")
private Long applicationId;
}

View File

@@ -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;
}
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;
}
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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<FaqReq> faq;
private EvaluationVersionEnum evaluationVersion;
private CallRankingTypeEnum rankingType;
}

View File

@@ -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;
}

View File

@@ -24,6 +24,7 @@ public class ApplicationEvaluationFormResponse {
private ApplicationEvaluationFormResponseBean applicationEvaluationFormResponse;
private List<FieldResponse> files;
private List<EvaluationDocumentResponse> evaluationDocument;
private List<CompanyDocumentResponseBean> applicationCompanyDocuments;
private List<AmendmentDocumentResponseBean> amendmentDetails;
private LocalDateTime createdDate;
private LocalDateTime updatedDate;

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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<ApplicationRankingResponse> applications;
}

View File

@@ -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;
}

View File

@@ -26,6 +26,8 @@ public class CompanyDocumentResponseBean extends BaseBean {
private Long userWithCompanyId;
private Long applicationId;
private DocumentCategoryResponse category;
}

View File

@@ -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<ApplicationRankingView, Long>,
JpaSpecificationExecutor<ApplicationRankingView> {
}

View File

@@ -183,5 +183,6 @@ public interface ApplicationRepository extends JpaRepository<ApplicationEntity,
public List<ApplicationEntity> findByCallIdAndIsDeletedFalseAndStatusIn(Long callId,List<String> status);
boolean existsByCallIdAndManualRankingAndIsDeletedFalseAndIdNot(Long callId, Long manualRanking, Long id);
}

View File

@@ -37,5 +37,7 @@ public interface CompanyDocumentRepository extends JpaRepository<CompanyDocument
List<CompanyDocumentEntity> findByIdInAndIsDeletedFalseAndStatusNot(List<Long> ids, String status);
List<CompanyDocumentEntity> findByApplicationIdAndCompanyIdAndTypeAndStatusNot(
Long applicationId, Long companyId, String type, String status);
}

View File

@@ -20,4 +20,6 @@ public interface UserWithCompanyRepository extends JpaRepository<UserWithCompany
@Query("SELECT u FROM UserWithCompanyEntity u WHERE u.userId = :userId AND u.companyId = :companyId AND u.isDeleted = false")
UserWithCompanyEntity findByUserIdAndCompanyIdAndIsDeletedFalseForNotification(Long userId, Long companyId);
List<UserWithCompanyEntity> findByCompanyIdAndIsDeletedFalse(Long companyId);
}

View File

@@ -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(){

View File

@@ -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<Long> companyDocumentIds);
ApplicationResponse updateApplicationRankingAction(HttpServletRequest request, Long applicationId,
ApplicationRankingActionTypeEnum rankingActionType, Long manualRanking);
CallRankingSummaryResponse getApplicationRanking(HttpServletRequest request, Long callId,
List<ApplicationRankingActionTypeEnum> rankingActionTypes);
}

View File

@@ -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);
}

View File

@@ -24,6 +24,6 @@ public interface CompanyDocumentService {
List<CompanyDocumentResponseBean> getAllCompanyDocument(HttpServletRequest request ,Long companyId , CompanyDocumentTypeEnum typeEnum);
List<CompanyDocumentResponseBean> uploadInstructorCompanyDocumentToApplication(HttpServletRequest request, List<MultipartFile> files, Long companyId, Long applicationId, Long documentCategoryId, LocalDateTime expirationDate, String name);
}

View File

@@ -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<ApplicationResponse> getAllApplications(HttpServletRequest request, Long callId, Long companyId ,List<ApplicationStatusTypeEnum> 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<ApplicationRankingActionTypeEnum> rankingActionTypes) {
validator.validateUser(request);
validator.validateSuperAdminOrDirector();
return applicationDao.getApplicationRanking(callId, rankingActionTypes);
}
}

View File

@@ -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);
}
}

View File

@@ -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<CompanyDocumentResponseBean> uploadFileForCompany(HttpServletRequest request, List<MultipartFile> files, Long companyId, Long documentCategoryId , CompanyDocumentTypeEnum documentSourceTypeEnum, LocalDateTime expirationDate,String name) {
Map<String, Object> 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<CompanyDocumentResponseBean> uploadInstructorCompanyDocumentToApplication(HttpServletRequest request, List<MultipartFile> files, Long companyId, Long applicationId, Long documentCategoryId, LocalDateTime expirationDate, String name) {
Map<String, Object> 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);

View File

@@ -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()) {

View File

@@ -206,7 +206,7 @@ public interface ApplicationAmendmentRequestApi {
@PostMapping(value = "/user/{userId}/pagination", produces = { "application/json" })
ResponseEntity<Response<PageableResponseBean<List<ApplicationAmendmentRequestViewResponse>>>> 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 = {

View File

@@ -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<Response<ApplicationResponse>> 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<byte[]> exportCsv(
HttpServletRequest request, @Parameter(description = "The call id", required = true) @PathVariable(value = "callId", required = true) Long callId);

View File

@@ -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<Response<List<CompanyDocumentResponseBean>>> 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<MultipartFile> files) {
return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
}
@Operation(summary = "Api to update company document",
responses = {
@ApiResponse(responseCode = "200", description = "OK"),

View File

@@ -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<Response<CallResponse>> 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<Response<ApplicationResponse>> 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<Response<CallRankingSummaryResponse>> 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<ApplicationRankingActionTypeEnum> rankingActionTypes);
}

View File

@@ -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<Response<ApplicationResponse>> 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<byte[]> generateApplicationPdf(HttpServletRequest request, Long applicationId) {

View File

@@ -57,6 +57,14 @@ public class CompanyDocumentApiControlller implements CompanyDocumentApi {
}
}
@Override
public ResponseEntity<Response<List<CompanyDocumentResponseBean>>> uploadInstructorCompanyDocumentToApplication(HttpServletRequest request, Long companyId, Long applicationId, Long documentCategoryId, String name, LocalDateTime expirationDate, List<MultipartFile> files) {
loggingUtil.logUserAction(UserActionRequest.builder().request(request).actionType(UserActionLogsEnum.UPLOAD).actionContext(UserActionContextEnum.UPLOAD_COMPANY_DOCUMENT_TO_APPLICATION).build());
List<CompanyDocumentResponseBean> 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<Response<CompanyDocumentResponseBean>> updateCompanyDocument(HttpServletRequest httpServletRequest, Long companyDocumentId, CompanyDocumentRequest companyDocumentRequest) {

View File

@@ -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<Response<CallResponse>> 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<Response<ApplicationResponse>> 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<Response<CallRankingSummaryResponse>> getApplicationRanking(HttpServletRequest request,
Long callId, List<ApplicationRankingActionTypeEnum> 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)));
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -3205,7 +3205,81 @@
</addColumn>
</changeSet>
<changeSet id="16-03-2026_RK_153512" author="Rajesh Khore">
<sqlFile dbms="postgresql" path="db/dump/update_system_email_template_special_amendment_16_03_2026.sql"/>
</changeSet>
<changeSet id="23-03-2026_NK_174724" author="Rajesh Khore">
<sqlFile dbms="postgresql" path="db/dump/update_application_view_23_03_2026.sql"/>
</changeSet>
<changeSet id="24-03-2026_RK_191723" author="Rajesh Khore">
<addColumn tableName="company_document">
<column name="application_id" type="INTEGER"/>
</addColumn>
<dropNotNullConstraint tableName="company_document" columnName="user_with_company_id" columnDataType="INTEGER"/>
</changeSet>
<changeSet id="25-03-2026_NK_170536" author="Rajesh Khore">
<sqlFile dbms="postgresql" path="db/dump/insert_form_field_25_03_2026.sql"/>
</changeSet>
<changeSet id="07-04-2026_ranking_columns" author="GEPAFIN">
<addColumn tableName="call">
<column name="ranking_type" type="VARCHAR(64)"/>
</addColumn>
<addColumn tableName="application">
<column name="ranking_action_type" type="VARCHAR(64)"/>
<column name="manual_ranking" type="BIGINT"/>
</addColumn>
</changeSet>
<changeSet id="07-04-2026_RK_145623" author="Rajesh Khore">
<sqlFile path="db/dump/create_application_ranking_view_07_04_2026.sql"/>
</changeSet>
<changeSet id="09-04-2026_RK_115923" author="Rajesh Khore">
<sqlFile dbms="postgresql" path="db/dump/update_email_config_for_mailgun_09_04_2026.sql" />
</changeSet>
<changeSet id="09-04-2026_RK_143457" author="Rajesh Khore">
<sqlFile dbms="postgresql" path="db/dump/update_system_email_template_09_04_2026.sql" />
</changeSet>
<changeSet id="09-04-2026_RK_174653" author="Rajesh Khore">
<update tableName="role">
<column name="permissions" value="MANAGE_SUBSEQUENT_PHASES"/>
<where>role_type='ROLE_GEPAFIN_OPERATOR'</where>
</update>
<update tableName="role">
<column name="permissions" value="VIEW_CALLS,APPLY_CALLS"/>
<where>role_type='ROLE_BENEFICIARY'</where>
</update>
<update tableName="role">
<column name="permissions" value="EVALUATE_APPLICATIONS,MANAGE_PRELIMINARY_RELIEF"/>
<where>role_type='ROLE_PRE_INSTRUCTOR'</where>
</update>
<update tableName="role">
<column name="permissions" value="EVALUATE_APPLICATIONS,MANAGE_PRELIMINARY_RELIEF,ASSIGED_APPLICATION"/>
<where>role_type='ROLE_INSTRUCTOR_MANAGER'</where>
</update>
<update tableName="role">
<column name="permissions" value="VIEW_CONFIDI_CALLS,APPLY_CONFIDI_CALLS"/>
<where>role_type='ROLE_CONFIDI'</where>
</update>
<update tableName="role">
<column name="permissions" value="ROOT_MANAGE_APPL_VIEW_DELETED,ROOT_MANAGE_APPL_DELETE_CONFIRM"/>
<where>role_type='ROLE_DIRECTOR'</where>
</update>
<update tableName="role">
<column name="permissions" value="MANAGE_TENDERS,MANAGE_USERS,ROOT_MANAGE_NDG,ROOT_MANAGE_APPL_STATUS,ROOT_MANAGE_AMENDMENT_REOPEN,ROOT_MANAGE_AMENDMENT_EXTEND,ROOT_MANAGE_APPL_VIEW_DELETED,ROOT_MANAGE_APPL_DELETE,ROOT_MANAGE_PEC_SEND,ROOT_MANAGE_VIEW_LOG,ROOT_MANAGE_EMAIL_TEMPLATES,ROOT_MANAGE_CALL_COPY"/>
<where>role_type='ROLE_SUPER_ADMIN'</where>
</update>
</changeSet>
</databaseChangeLog>

View File

@@ -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;

View File

@@ -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
);

View File

@@ -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;

View File

@@ -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 = '<html>
<body style="font-family: Arial, sans-serif; color: #333; line-height: 1.6;">
<div style="padding: 20px; border: 1px solid #ddd; border-radius: 8px; max-width: 600px; margin: auto;">
<p>Buongiorno,</p>
<p>
Si comunica che, in riferimento alla domanda di concessione di
Finanziamento agevolato a valere sul Fondo prestiti
<strong>{{call_name}}</strong> di cui all''oggetto, la stessa è stata
regolarmente acquisita ed è stata registrata con Protocollo n.
<strong>{{protocol_number}}</strong> del <strong>{{date}}</strong> alle
<strong>{{time}}</strong>.
</p>
<p>Distinti Saluti,</p>
<p>
<strong>{{email_signature}}</strong>
</p>
</div>
</body>
</html>',
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='<html>
<body style="font-family: Arial, sans-serif; color: #333; line-height: 1.6;">
<div style="padding: 20px; border: 1px solid #ddd; border-radius: 8px; max-width: 600px; margin: auto;">
<p>
In riferimento alla domanda di concessione di Finanziamento agevolato a valere sul Fondo prestiti
<strong>{{call_name}}</strong> di cui all''oggetto, la stessa è stata regolarmente acquisita ed è stata
registrata con Protocollo n. <strong>{{protocol_number}}</strong> del <strong>{{date}}</strong> alle <strong>{{time}}</strong>.
</p>
<p>Distinti Saluti,</p>
<p>
<strong>{{email_signature}}</strong>
</p>
</div>
</body>
</html>',
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='<html>
<body style="font-family: Arial, sans-serif; color: #000; line-height: 1.6;">
<div style="padding: 20px; border: 1px solid #ddd; border-radius: 8px; max-width: 600px; margin: auto;">
<p><strong>RICHIESTA INTEGRAZIONE DOCUMENTALE</strong></p>
<p>Buongiorno,</p>
<p>In riferimento alla domanda di concessione di Finanziamento agevolato a valere sul Fondo prestiti
<strong>{{call_name}}</strong> di cui al <strong>Protocollo n. {{protocol_number}} del
{{protocol_date}} e {{protocol_time}}</strong>, alla luce dell''attività istruttoria svolta,
segnaliamo quanto segue:
</p>
<ul>
{{form_dataInput}}
</ul>
<p>{{note}}</p>
<p>Vi invitiamo a fornire quanto sopra richiesto integrando la documentazione sia caricandola all''interno dello sportello
online <a href="{{platform_link}}">{{platform_link}}</a> che inviandola a mezzo PEC all''indirizzo
bandi.gepafin@legalmail.it entro e <strong>non oltre {{response_days}} giorni</strong> 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.
</p>
<p>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.
</p>
<p>Distinti Saluti,</p>
<p><strong>{{email_signature}}</strong></p>
</div>
</body>
</html>'
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='<html> <body style="font-family: Arial, sans-serif; color: #000; line-height: 1.6;">
<div style="padding: 20px; border: 1px solid #ddd; border-radius: 8px; max-width: 600px; margin: auto;">
<p>Buongiorno,</p>
<p>Si comunica che, in riferimento alla domanda a valere sul bando “<strong>{{call_name}}</strong>” di cui al
<strong>Protocollo n. {{protocol_number}} del {{protocol_date}} alle {{protocol_time}}</strong>,
</p>
<p>
{{tipo_inammissibilita}}.</p>
<p>Le motivazioni sono le seguenti: <strong>{{form_text}}</strong></p>
<p>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.</p>
<p>Distinti Saluti,</p>
<p><strong>{{email_signature}}</strong></p>
</div>
</body>
</html>'
WHERE email_scenario='APPLICATION_REJECTED'
AND hub_id IS NULL;
-- 7
UPDATE gepafin_schema.system_email_template
SET html_content='<html>
<body style="font-family: Arial, sans-serif; color: #000; line-height: 1.6;">
<div style="padding: 20px; border: 1px solid #ddd; border-radius: 8px; max-width: 600px; margin: auto;">
<p><strong>PROMEMORIA PER LA PRESENTAZIONE DELL''Soccorso Istruttorio</strong></p>
<p>Buongiorno,</p>
<p>Questo è un promemoria per completare la presentazione dell''Soccorso Istruttorio entro il termine specificato. Di seguito i dettagli:</p>
<ul>
<li><strong>Amendment ID:</strong> {{amendment_id}}</li>
<li><strong>Data di Scadenza:</strong> {{amendment_due_date}}</li>
</ul>
<p>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 <a href="{{platform_link}}">{{platform_link}}</a> </p>
<p>Distinti saluti,</p>
<p><strong>{{email_signature}}</strong></p>
</div>
</body>
</html>'
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='<html>
<body style="font-family: Arial, sans-serif; color: #000; line-height: 1.6;">
<div style="padding: 20px; border: 1px solid #ddd; border-radius: 8px; max-width: 600px; margin: auto;">
<p><strong>RICHIESTA INTEGRAZIONE DOCUMENTALE</strong></p>
<p>Buongiorno,</p>
<p>In riferimento alla domanda di concessione di Finanziamento agevolato a valere sul Bando
"<strong>{{call_name}}</strong>" di cui al <strong>Protocollo n. {{protocol_number}} del
{{protocol_date}} e {{protocol_time}}</strong>, alla luce dell''attività istruttoria svolta,
segnaliamo quanto segue:
</p>
{{note}}
<p>Vi invitiamo a fornire quanto sopra richiesto integrando la documentazione caricandola all''interno dello sportello
online <a href="{{platform_link}}">{{platform_link}}</a> entro e <strong>non oltre {{response_days}} giorni</strong> 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.
</p>
<p>La documentazione trasmessa e le informazioni fornite saranno processate dall''istruttore assegnatario della pratica.
</p>
<p>Distinti Saluti,</p>
<p><strong>{{email_signature}}</strong></p>
</div>
</body>
</html>'
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';

View File

@@ -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';

View File

@@ -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

View File

@@ -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 <20> consentita solo se il bando <20> 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 <EFBFBD> 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.
vat.or.tax.code.required=<EFBFBD> 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 <20> disponibile tramite questa operazione.
application.registry.segment.invalid=Valore del segmento non valido.