Merge pull request #316 from Kitzanos/ranking-extraction-prod

Cherry-pick (Extract ranking)
This commit is contained in:
Rinaldo
2025-06-30 20:15:16 +02:00
committed by GitHub
17 changed files with 219 additions and 17 deletions

View File

@@ -56,13 +56,11 @@ import org.springframework.web.multipart.MultipartFile;
import jakarta.servlet.http.HttpServletRequest;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.*;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.sql.Timestamp;
import java.text.MessageFormat;
import java.text.SimpleDateFormat;
@@ -217,6 +215,9 @@ public class ApplicationDao {
@Autowired
private ApplicationEvaluationDao applicationEvaluationDao;
@Autowired
private EvaluationCriteriaRepository evaluationCriteriaRepository;
public final Random random = new Random();
public ApplicationResponseBean createApplication(HttpServletRequest request, ApplicationRequestBean applicationRequestBean, Long formId, Long applicationId) {
@@ -2419,4 +2420,78 @@ public class ApplicationDao {
emailNotificationDao.sendMail(hub.getId(), subject, body, List.of(GepafinConstant.RINALDO_EMAIL),emailLogRequest);
}
public byte[] downloadRankingCsv(Long callId) {
CallEntity callEntity = callService.validateCall(callId);
BigDecimal scoreList = BigDecimal.ZERO;
List<EvaluationCriteriaEntity> evaluationCriteriaEntities =
evaluationCriteriaRepository.findByCallIdAndIsDeletedFalse(callId);
List<String> headers = Arrays.asList(
"ApplicationID",
"VatNumber",
"Company Name",
"Protocol",
"Requested Amount",
"Status",
"Total Score",
"Individual Scores"
);
for (EvaluationCriteriaEntity evaluationCriteria : evaluationCriteriaEntities) {
scoreList = scoreList.add(evaluationCriteria.getScore());
}
List<ApplicationEntity> applications =
applicationRepository.findByCallIdAndIsDeletedFalseAndStatusIn(callId,List.of(ApplicationStatusForEvaluation.APPROVED.getValue(),ApplicationStatusForEvaluation.ADMISSIBLE.getValue(),ApplicationStatusForEvaluation.TECHNICAL_EVALUATION.getValue()));
// Collect all rows with totalScore for sorting
List<List<Object>> rows = new ArrayList<>();
for (ApplicationEntity app : applications) {
CompanyEntity company = companyService.validateCompany(app.getCompanyId());
String name = company.getCompanyName();
String vat = company.getVatNumber();
Long applicationId = app.getId();
ProtocolEntity protocolEntity = app.getProtocol();
Long protocol = (protocolEntity != null) ? protocolEntity.getProtocolNumber() : 0L;
BigDecimal requestedAmount = app.getAmountRequested();
String status = app.getStatus();
ApplicationEvaluationEntity applicationEvaluationEntity =
applicationEvaluationRepository.findByApplicationId(app.getId());
BigDecimal totalScore = applicationEvaluationDao.calculateTotalScore(
applicationEvaluationEntity.getCriteria()
);
rows.add(Arrays.asList(
applicationId,
vat,
name,
protocol,
requestedAmount,
status,
scoreList,
totalScore
));
}
// 5. Write the CSV using Commons CSV, with headers:
ByteArrayOutputStream out = new ByteArrayOutputStream();
try (OutputStreamWriter writer = new OutputStreamWriter(out, StandardCharsets.UTF_8);
CSVPrinter csvPrinter = new CSVPrinter(writer,
CSVFormat.DEFAULT.withHeader(headers.toArray(new String[0])))) {
for (List<Object> row : rows) {
csvPrinter.printRecord(row);
}
csvPrinter.flush();
} catch (IOException e) {
throw new RuntimeException("Error while generating CSV", e);
}
return out.toByteArray();
}
}

View File

@@ -1910,7 +1910,8 @@ public class ApplicationEvaluationDao {
Optional<ApplicationEvaluationEntity> existingEntityOptional = applicationEvaluationRepository.findByAssignedApplicationsEntity_IdAndIsDeletedFalse(
assignedApplicationsEntity.getId());
ApplicationEvaluationEntity entity;
UserEntity user=userService.validateUser(application.getUserId());
HubEntity hub=user.getHub();
EmailSendResponse emailSendResponse = new EmailSendResponse();
if (existingEntityOptional.isPresent()) {
ApplicationEvaluationEntity existingEntity = existingEntityOptional.get();
@@ -1931,15 +1932,21 @@ public class ApplicationEvaluationDao {
}
}
if(newStatus.equals(ApplicationStatusForEvaluation.TECHNICAL_EVALUATION) && Boolean.TRUE.equals(application.getStatus().equals(ApplicationStatusTypeEnum.ADMISSIBLE.getValue()))){
if(newStatus.equals(ApplicationStatusForEvaluation.TECHNICAL_EVALUATION)){
log.info("Processing technical evaluation for applicationId: {}", application.getId());
processTechnicalEvaluation(application.getId(), application, newStatus);
}
if((newStatus.equals(ApplicationStatusForEvaluation.APPROVED) || newStatus.equals(ApplicationStatusForEvaluation.REJECTED)) && application.getStatus().equals(ApplicationStatusTypeEnum.EVALUATION.getValue())) {
if((newStatus.equals(ApplicationStatusForEvaluation.APPROVED) || newStatus.equals(ApplicationStatusForEvaluation.REJECTED))) {
application.setStatus(newStatus.getValue());
log.info("Application status updated to {} for applicationId: {}", newStatus, application.getId());
}
if(newStatus.equals(ApplicationStatusForEvaluation.TECHNICAL_EVALUATION_REJECTED)) {
application.setStatus(newStatus.getValue());
log.info("Application status updated to {} for applicationId: {}", newStatus, application.getId());
emailNotificationDao.sendMailForApplicationTechnicalEvaluationRejected(application,hub,existingEntity);
}
application = applicationRepository.save(application);
/** This code is responsible for adding a version history log for the "Update Application" operation. **/
@@ -2573,7 +2580,7 @@ public class ApplicationEvaluationDao {
}
}
private BigDecimal calculateTotalScore(String criteriaJson){
public BigDecimal calculateTotalScore(String criteriaJson){
try {
ObjectMapper objectMapper = new ObjectMapper();
// Convert JSON string to List of Maps

View File

@@ -73,6 +73,9 @@ public class EmailNotificationDao {
@Value("${rinaldo_email}")
private String rinaldoEmail;
@Autowired
private SystemEmailTemplatesDao systemEmailTemplatesDao;
private void sendEmail(ApplicationEntity applicationEntity, SystemEmailTemplatesEntity.SystemEmailTemplatesEntityTypeEnum templateType, Map<String, String> bodyPlaceholders,
List<String> additionalRecipients, Long amendmentId) {
@@ -355,4 +358,30 @@ public class EmailNotificationDao {
throw new IllegalArgumentException("Failed to parse email configuration JSON", e);
}
}
public void sendMailForApplicationTechnicalEvaluationRejected(ApplicationEntity applicationEntity,HubEntity hub,ApplicationEvaluationEntity applicationEvaluationEntity) {
Map<String, String> bodyPlaceholders = prepareEmailPlaceholdersForTechnicalEvaluationRejected(applicationEntity,hub,applicationEvaluationEntity);
sendEmail(applicationEntity, SystemEmailTemplatesEntity.SystemEmailTemplatesEntityTypeEnum.INADMISSIBILITY_NOTIFICATION_DUE_TO_TECHNICAL_EVALUATION_FAILURE, bodyPlaceholders, null,
null);
}
public Map<String, String> prepareEmailPlaceholdersForTechnicalEvaluationRejected(ApplicationEntity applicationEntity,HubEntity hub,ApplicationEvaluationEntity applicationEvaluationEntity) {
Map<String, String> bodyPlaceholders = new HashMap<>();
bodyPlaceholders.put("{{call_name}}", applicationEntity.getCall().getName());
String protocolNumber = applicationEntity.getProtocol().getExternalProtocolNumber();
if (protocolNumber == null) {
protocolNumber = String.valueOf(applicationEntity.getProtocol().getProtocolNumber());
}
bodyPlaceholders.put("{{protocol_number}}", protocolNumber);
String protocolDate = DateTimeUtil.formatLocalDateTime(applicationEntity.getProtocol().getCreatedDate(), GepafinConstant.DD_MM_YYYY);
if (applicationEntity.getProtocol().getExternalProtocolDate() != null) {
protocolDate = DateTimeUtil.formatLocalDateTime(applicationEntity.getProtocol().getExternalProtocolDate(), GepafinConstant.DD_MM_YYYY);
}
bodyPlaceholders.put("{{protocol_date}}", protocolDate);
bodyPlaceholders.put("{{protocol_time}}", DateTimeUtil.parseLocalTimeToString(applicationEntity.getProtocol().getTime(), GepafinConstant.HH_MM_SS));
bodyPlaceholders.put("{{email_signature}}", hub.getEmailSignature());
bodyPlaceholders.put("{{platform_link}}", hub.getDomainName());
bodyPlaceholders.put("{{form_text}}", applicationEvaluationEntity.getMotivation());
return bodyPlaceholders;
}
}

View File

@@ -116,7 +116,7 @@ public class SystemEmailTemplatesDao {
return htmlContent;
}
private String replaceEmailSignature(HubEntity hub, String htmlContent, Map<String, String> languageMap) {
public String replaceEmailSignature(HubEntity hub, String htmlContent, Map<String, String> languageMap) {
String emailSignature = defaultEmailSignature;
if(hub != null && Boolean.FALSE.equals(StringUtils.isEmpty(hub.getEmailSignature()))){
emailSignature = hub.getEmailSignature();
@@ -124,7 +124,7 @@ public class SystemEmailTemplatesDao {
return htmlContent.replace("{{email_signature}}", emailSignature);
}
private String replacePlatformLinkPlaceholder(HubEntity hub, String htmlContent, Map<String, String> languageMap) {
public String replacePlatformLinkPlaceholder(HubEntity hub, String htmlContent, Map<String, String> languageMap) {
String platformLink = feBaseUrl;
if(hub != null && Boolean.FALSE.equals(StringUtils.isEmpty(hub.getDomainName()))){
platformLink = hub.getDomainName();

View File

@@ -55,7 +55,8 @@ public class SystemEmailTemplatesEntity extends BaseEntity {
USER_ONBOARDING_BANDI("USER_ONBOARDING_BANDI"),
PASSWORD_RESET("PASSWORD_RESET"),
INADMISSIBILITY_TEMPLATE("INADMISSIBILITY_NOTIFICATION"),
APPLICATION_SUBMISSION_FAILURE_NOTIFICATION("APPLICATION_SUBMISSION_FAILURE_NOTIFICATION");
APPLICATION_SUBMISSION_FAILURE_NOTIFICATION("APPLICATION_SUBMISSION_FAILURE_NOTIFICATION"),
INADMISSIBILITY_NOTIFICATION_DUE_TO_TECHNICAL_EVALUATION_FAILURE("INADMISSIBILITY_NOTIFICATION_DUE_TO_TECHNICAL_EVALUATION_FAILURE");
private String value;
SystemEmailTemplatesEntityTypeEnum(String value) {

View File

@@ -6,7 +6,8 @@ public enum ApplicationStatusForEvaluation {
APPROVED("APPROVED"),
REJECTED("REJECTED"),
ADMISSIBLE("ADMISSIBLE"),
TECHNICAL_EVALUATION("TECHNICAL_EVALUATION");
TECHNICAL_EVALUATION("TECHNICAL_EVALUATION"),
TECHNICAL_EVALUATION_REJECTED("TECHNICAL_EVALUATION_REJECTED");
private String value;

View File

@@ -16,7 +16,8 @@ public enum ApplicationStatusTypeEnum {
APPOINTMENT("APPOINTMENT"),
NDG("NDG"),
ADMISSIBLE("ADMISSIBLE"),
TECHNICAL_EVALUATION("TECHNICAL_EVALUATION");
TECHNICAL_EVALUATION("TECHNICAL_EVALUATION"),
TECHNICAL_EVALUATION_REJECTED("TECHNICAL_EVALUATION_REJECTED");
private String value;

View File

@@ -12,7 +12,8 @@ public enum EmailScenarioTypeEnum {
USER_CREATION("USER_CREATION"),
PASSWORD_RESET_REQUEST("PASSWORD_RESET_REQUEST"),
APPLICATION_REJECTED("APPLICATION_REJECTED"),
APPLICATION_SUBMISSION_FAILURE("APPLICATION_SUBMISSION_FAILURE");
APPLICATION_SUBMISSION_FAILURE("APPLICATION_SUBMISSION_FAILURE"),
APPLICATION_TECHNICAL_EVALUATION_REJECTED("APPLICATION_TECHNICAL_EVALUATION_REJECTED");
private final String value;

View File

@@ -47,6 +47,8 @@ public enum UserActionContextEnum {
GET_NEXT_PREVIOUS_FORM("GET_NEXT_PREVIOUS_FORM"),
DOWNLOAD_APPLICATION_DOC_ZIP("DOWNLOAD_APPLICATION_DOC_ZIP"),
READMIT_APPLICATION("READMIT_APPLICATION"),
DOWNLOAD_CSV_BY_CALL_ID("DOWNLOAD_CSV_BY_CALL_ID"),
DOWNLOAD_CSV_AS_PER_RANKING("DOWNLOAD_CSV_AS_PER_RANKING"),
/** FAQ action context **/
CREATE_FAQ("CREATE_FAQ"),

View File

@@ -181,4 +181,7 @@ public interface ApplicationRepository extends JpaRepository<ApplicationEntity,
ApplicationEntity findByIdAndStatusAndIsDeletedFalse( Long id, String status);
public List<ApplicationEntity> findByCallIdAndIsDeletedFalseAndStatusIn(Long callId,List<String> status);
}

View File

@@ -18,4 +18,6 @@ public interface EvaluationCriteriaRepository extends JpaRepository<EvaluationCr
List<EvaluationCriteriaEntity> findByCallIdAndLookupDataTypeAndIsDeletedFalse(Long callId, String type);
// List<EvaluationCriteriaEntity> findByCallId(Long callId);
List<EvaluationCriteriaEntity> findByCallIdAndIsDeletedFalse(Long callId);
}

View File

@@ -52,4 +52,7 @@ public interface ApplicationService {
public ApplicationResponse readmitApplication(HttpServletRequest request, Long applicationId);
public byte[] downloadRankingCsv(HttpServletRequest request, Long callId);
}

View File

@@ -176,4 +176,10 @@ public class ApplicationServiceImpl implements ApplicationService {
UserEntity userEntity = validator.validateUser(request);
return applicationDao.readmitApplication(request, applicationId);
}
@Override
public byte[] downloadRankingCsv(HttpServletRequest request, Long callId) {
UserEntity userEntity = validator.validateUser(request);
return applicationDao.downloadRankingCsv(callId);
}
}

View File

@@ -252,6 +252,21 @@ public interface ApplicationApi {
ResponseEntity<Response<ApplicationResponse>> readmitApplication(HttpServletRequest request,
@Parameter(description = "The application id", required = true) @PathVariable("applicationId") Long applicationId);
@Operation(summary = "Api to download application data as a CSV file as per ranking",
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)}))
})
@GetMapping(value = "/call/{callId}/ranking-csv")
@PreAuthorize("hasRole('ROLE_SUPER_ADMIN') || hasRole('ROLE_INSTRUCTOR_MANAGER')")
public ResponseEntity<byte[]> downloadRankingCsv(
HttpServletRequest request, @Parameter(description = "The call id", required = true) @PathVariable(value = "callId", required = true) Long callId);
}

View File

@@ -27,6 +27,8 @@ import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import org.slf4j.Logger;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.List;
@@ -236,6 +238,10 @@ public class ApplicationApiController implements ApplicationApi {
}
@Override
public ResponseEntity<byte[]> exportCsv(HttpServletRequest request, Long callId) {
loggingUtil.logUserAction(
UserActionRequest.builder().request(request).actionType(UserActionLogsEnum.DOWNLOAD).actionContext(UserActionContextEnum.DOWNLOAD_CSV_BY_CALL_ID).build());
byte[] csvBytes =applicationService.exportCsv(request,callId);
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=applications.csv")
@@ -254,4 +260,19 @@ public class ApplicationApiController implements ApplicationApi {
return ResponseEntity.status(HttpStatus.OK)
.body(new Response<>(applicationResponse, Status.SUCCESS, Translator.toLocale(GepafinConstant.READMIT_APPLICATION_SUCCESS)));
}
@Override
public ResponseEntity<byte[]> downloadRankingCsv(HttpServletRequest request, Long callId) {
loggingUtil.logUserAction(
UserActionRequest.builder().request(request).actionType(UserActionLogsEnum.DOWNLOAD).actionContext(UserActionContextEnum.DOWNLOAD_CSV_AS_PER_RANKING).build());
byte[] csvBytes =applicationService.downloadRankingCsv(request,callId);
String dateString = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));
String fileName = "call_" + callId + "_" + dateString + ".csv";
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + fileName)
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.body(csvBytes);
}
}

View File

@@ -2980,4 +2980,8 @@
</addColumn>
</changeSet>
<changeSet id="27-06-2025_RK_155914" author="Rajesh Khore">
<sqlFile dbms="postgresql"
path="db/dump/insert_system_email_template_technical_evaluation_rejected.sql"/>
</changeSet>
</databaseChangeLog>

View File

@@ -0,0 +1,31 @@
INSERT INTO gepafin_schema.system_email_template
(template_name, "type", html_content, subject, "json", "system", is_deleted, created_date, updated_date, email_scenario)
VALUES
(
'Application Technical Evaluation Rejected Template',
'INADMISSIBILITY_NOTIFICATION_DUE_TO_TECHNICAL_EVALUATION_FAILURE',
'<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>,
a stessa è stata sottoposta a valutazione tecnica ed economico finanziaria
con esito negativo</p>
<p>Le motivazioni sono le seguenti: <strong>{{form_text}}</strong></p>
<p>Vi ricordiamo che i Beneficiari, in caso di mancato accoglimento della Domanda di Finanziamento agevolato, entro 10 giorni dalla data di ricevimento della presente potranno formulare ricorso al Gestore tramite
modello disponibile nello sportello online
<a href="{{platform_link}}">{{platform_link}}</a>, e sul sito internet home - ND Credit Repair , nella sezione dedicata ai Bandi e Avvisi pubblici.</p>
<p>Distinti Saluti,</p>
<p><strong>{{email_signature}}</strong></p>
</div>
</body>
</html>',
'BANDO "{{call_name}}" Esito negativo della valutazione tecnica {{company_name}}',
null,
true,
false,
CURRENT_TIMESTAMP,
CURRENT_TIMESTAMP,
'APPLICATION_TECHNICAL_EVALUATION_REJECTED'
);