From 57c767cea6c01fadeaf038be3ec0510509751e34 Mon Sep 17 00:00:00 2001 From: rajesh Date: Tue, 18 Nov 2025 15:01:45 +0530 Subject: [PATCH 01/42] Created new endpoint to upload company documents in application --- .../constants/GepafinConstant.java | 3 +- .../tendermanagement/dao/ApplicationDao.java | 61 +++++++++++++++++++ .../dao/ApplicationEvaluationDao.java | 10 +++ .../tendermanagement/dao/AppointmentDao.java | 4 +- .../dao/CompanyDocumentDao.java | 2 +- .../entities/ApplicationEntity.java | 3 + .../enums/UserActionContextEnum.java | 3 +- .../CompanyDocumentRepository.java | 2 +- .../service/ApplicationService.java | 2 +- .../service/impl/ApplicationServiceImpl.java | 6 ++ .../web/rest/api/ApplicationApi.java | 13 ++++ .../api/impl/ApplicationApiController.java | 11 ++++ .../db/changelog/db.changelog-1.0.0.xml | 6 ++ src/main/resources/message_en.properties | 3 + src/main/resources/message_it.properties | 2 + 15 files changed, 124 insertions(+), 7 deletions(-) diff --git a/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java b/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java index cf544480..8ff0c836 100644 --- a/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java +++ b/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java @@ -634,7 +634,8 @@ public class GepafinConstant { public static final String MAIL_SENT_SUCCESSFULLY="mail.send.successfully"; public static final String EMAIL_LOG_FETCHED="email.log.fetched"; public static final String APPLICATION_AMENDMENT_APPROPIATE_STATUS="amendment.appropiate.status"; - + public static final String UPLOAD_COMPANY_DOCUMENT_TO_APPLICATION_MSG="upload.company.document.to.application"; + public static final String COMPANY_DOCUMENT_NOT_FOUND_WITH_IDS="company.document.not.found.with.ids"; } diff --git a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java index d193c953..aa65357a 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java @@ -232,6 +232,15 @@ public class ApplicationDao { @Autowired private ApplicationContractRepository applicationContractRepository; + @Autowired + private CompanyDocumentRepository companyDocumentRepository; + + @Autowired + private AppointmentDao appointmentDao; + + @Autowired + private DocumentDao documentDao; + public final Random random = new Random(); public ApplicationResponseBean createApplication(HttpServletRequest request, ApplicationRequestBean applicationRequestBean, Long formId, Long applicationId) { @@ -2630,4 +2639,56 @@ public class ApplicationDao { } return Collections.emptyList(); } + public void uploadCompanyDocumentsToApplication( Long applicationId,List companyDocumentIds,UserEntity user) { + ApplicationEntity applicationEntity=validateApplication(applicationId); + ApplicationEntity oldApplication = Utils.getClonedEntityForData(applicationEntity); + List companyDocumentEntities=validateCompanyDocuments(companyDocumentIds); + List multipartFiles=new ArrayList<>(); + for(CompanyDocumentEntity companyDocumentEntity:companyDocumentEntities) { + try { + File localFile = appointmentDao.downloadFileFromS3(companyDocumentEntity.getFilePath()); + MultipartFile multipartFile = appointmentDao.convertFileToMultipartFile(localFile); + multipartFiles.add(multipartFile); + } catch (Exception e) { + throw new RuntimeException(e); + } + List documentResponseBeans=documentDao.uploadFiles(user.getId(),multipartFiles,applicationId,DocumentSourceTypeEnum.APPLICATION,DocumentTypeEnum.DOCUMENT); + List initialDocumentIds = documentResponseBeans.stream() + .map(DocumentResponseBean::getId) + .collect(Collectors.toList()); + String initialDocumentId = initialDocumentIds.stream() + .map(String::valueOf) + .collect(Collectors.joining(",")); + applicationEntity.setCompanyDocument(initialDocumentId); + applicationRepository.save(applicationEntity); + loggingUtil.addVersionHistory(VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldApplication).newData(applicationEntity).build()); + } + } + + public List validateCompanyDocuments(List ids) { + + List documents = + companyDocumentRepository.findByIdInAndIsDeletedFalseAndStatus(ids,CompanyDocumentStatusEnum.VALID.getValue()); + + Set foundIds = documents.stream() + .map(CompanyDocumentEntity::getId) + .collect(Collectors.toSet()); + + List missingIds = ids.stream() + .filter(id -> !foundIds.contains(id)) + .toList(); + + if (!missingIds.isEmpty()) { + log.warn("Company Document(s) not found with IDs {}", missingIds); + throw new ResourceNotFoundException( + Status.NOT_FOUND, + MessageFormat.format( + Translator.toLocale(GepafinConstant.COMPANY_DOCUMENT_NOT_FOUND_WITH_IDS), + missingIds + )); + } + + return documents; + } + } diff --git a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationEvaluationDao.java b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationEvaluationDao.java index 121cd15e..a07cb582 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationEvaluationDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationEvaluationDao.java @@ -630,6 +630,16 @@ public class ApplicationEvaluationDao { processedFieldIds.add(fieldResponse.getId()); }); + List companyDocuments=applicationAmendmentRequestDao.getDocumentResponseBean(applicationFormEntities.get(0).getApplication().getCompanyDocument()); + + for(DocumentResponseBean documentResponseBean:companyDocuments) { + FieldResponse companyFieldResponse = new FieldResponse(); + companyFieldResponse.setId("COMPANY"); + companyFieldResponse.setValid(Boolean.TRUE); + companyFieldResponse.setLabel(documentResponseBean.getName()); + companyFieldResponse.setFileDetail(List.of(documentResponseBean)); + validFieldResponses.add(companyFieldResponse); + } response.setFiles(validFieldResponses); } diff --git a/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java b/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java index 5b6bf8c1..aa81aab5 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java @@ -1355,13 +1355,13 @@ public class AppointmentDao { return input; } - public static MultipartFile convertFileToMultipartFile(File file) throws IOException { + public MultipartFile convertFileToMultipartFile(File file) throws IOException { FileInputStream input = new FileInputStream(file); return new MockMultipartFile(file.getName(), file.getName(), MediaType.APPLICATION_OCTET_STREAM_VALUE, input); } - private File downloadFileFromS3(String fileUrl) throws Exception { + public File downloadFileFromS3(String fileUrl) throws Exception { String key = amazonS3Service.extractS3KeyFromUrl(fileUrl); String fileName = extractFileName(key); String folderPath = key.substring(0, key.lastIndexOf("/")); diff --git a/src/main/java/net/gepafin/tendermanagement/dao/CompanyDocumentDao.java b/src/main/java/net/gepafin/tendermanagement/dao/CompanyDocumentDao.java index c034a985..687917df 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/CompanyDocumentDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/CompanyDocumentDao.java @@ -354,7 +354,7 @@ public class CompanyDocumentDao { builder.equal(root.get("userWithCompany").get("userId"), userId) ); predicate = builder.and(predicate, builder.or(companyPredicate, personalPredicate)); - + predicate = builder.equal(root.get("status"), CompanyDocumentStatusEnum.VALID.getValue()); return predicate; }; } diff --git a/src/main/java/net/gepafin/tendermanagement/entities/ApplicationEntity.java b/src/main/java/net/gepafin/tendermanagement/entities/ApplicationEntity.java index 8b8c64d7..7670d520 100644 --- a/src/main/java/net/gepafin/tendermanagement/entities/ApplicationEntity.java +++ b/src/main/java/net/gepafin/tendermanagement/entities/ApplicationEntity.java @@ -87,4 +87,7 @@ public class ApplicationEntity extends BaseEntity { @Column(name = "REJECTED_DOCUMENT") private String rejectedDocument; + + @Column(name = "COMPANY_DOCUMENT") + private String companyDocument; } \ No newline at end of file diff --git a/src/main/java/net/gepafin/tendermanagement/enums/UserActionContextEnum.java b/src/main/java/net/gepafin/tendermanagement/enums/UserActionContextEnum.java index 9401834e..9092c769 100644 --- a/src/main/java/net/gepafin/tendermanagement/enums/UserActionContextEnum.java +++ b/src/main/java/net/gepafin/tendermanagement/enums/UserActionContextEnum.java @@ -232,7 +232,8 @@ public enum UserActionContextEnum { FETCH_APPLICATION_CONTRACT_BY_BENEFICIARY_USER_ID("FETCH_APPLICATION_CONTRACT_BY_BENEFICIARY_USER_ID"), SEND_PEC_MAIL("SEND_PEC_MAIL"), FETCH_EMAIL_LOG("FETCH_EMAIL_LOG"), - FETCH_ALL_EMAIL_LOG("FETCH_ALL_EMAIL_LOG"); + FETCH_ALL_EMAIL_LOG("FETCH_ALL_EMAIL_LOG"), + UPLOAD_COMPANY_DOCUMENT_TO_APPLICATION("UPLOAD_COMPANY_DOCUMENT_TO_APPLICATION"); private final String value; diff --git a/src/main/java/net/gepafin/tendermanagement/repositories/CompanyDocumentRepository.java b/src/main/java/net/gepafin/tendermanagement/repositories/CompanyDocumentRepository.java index 30a49410..47e0a14f 100644 --- a/src/main/java/net/gepafin/tendermanagement/repositories/CompanyDocumentRepository.java +++ b/src/main/java/net/gepafin/tendermanagement/repositories/CompanyDocumentRepository.java @@ -35,7 +35,7 @@ public interface CompanyDocumentRepository extends JpaRepository findByCategoryEntityId(Long categoryId); - + List findByIdInAndIsDeletedFalseAndStatus(List ids,String status); } diff --git a/src/main/java/net/gepafin/tendermanagement/service/ApplicationService.java b/src/main/java/net/gepafin/tendermanagement/service/ApplicationService.java index 9cc2ea22..a1206ee3 100644 --- a/src/main/java/net/gepafin/tendermanagement/service/ApplicationService.java +++ b/src/main/java/net/gepafin/tendermanagement/service/ApplicationService.java @@ -54,5 +54,5 @@ public interface ApplicationService { public byte[] downloadRankingCsv(HttpServletRequest request, Long callId); - + public void uploadCompanyDocumentsToApplication(HttpServletRequest request, Long applicationId, List companyDocumentIds); } diff --git a/src/main/java/net/gepafin/tendermanagement/service/impl/ApplicationServiceImpl.java b/src/main/java/net/gepafin/tendermanagement/service/impl/ApplicationServiceImpl.java index e311340e..12769ac7 100644 --- a/src/main/java/net/gepafin/tendermanagement/service/impl/ApplicationServiceImpl.java +++ b/src/main/java/net/gepafin/tendermanagement/service/impl/ApplicationServiceImpl.java @@ -182,4 +182,10 @@ public class ApplicationServiceImpl implements ApplicationService { UserEntity userEntity = validator.validateUser(request); return applicationDao.downloadRankingCsv(callId,userEntity); } + + @Override + public void uploadCompanyDocumentsToApplication(HttpServletRequest request, Long applicationId, List companyDocumentIds) { + UserEntity userEntity = validator.validateUser(request); + applicationDao.uploadCompanyDocumentsToApplication(applicationId,companyDocumentIds,userEntity); + } } diff --git a/src/main/java/net/gepafin/tendermanagement/web/rest/api/ApplicationApi.java b/src/main/java/net/gepafin/tendermanagement/web/rest/api/ApplicationApi.java index b64b3951..989088d5 100644 --- a/src/main/java/net/gepafin/tendermanagement/web/rest/api/ApplicationApi.java +++ b/src/main/java/net/gepafin/tendermanagement/web/rest/api/ApplicationApi.java @@ -267,6 +267,19 @@ public interface ApplicationApi { public ResponseEntity downloadRankingCsv( HttpServletRequest request, @Parameter(description = "The call id", required = true) @PathVariable(value = "callId", required = true) Long callId); + @Operation(summary = "Api to upload company documents in application", + 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 = "/{applicationId}/companyDocuments") + public ResponseEntity> uploadCompanyDocumentsToApplication( + HttpServletRequest request,@Parameter(description = "The application id", required = true) @PathVariable(value = "applicationId", required = true) Long applicationId, @Parameter(description = "The company document id", required = true) @RequestParam("companyDocumentIds") List companyDocumentIds); } diff --git a/src/main/java/net/gepafin/tendermanagement/web/rest/api/impl/ApplicationApiController.java b/src/main/java/net/gepafin/tendermanagement/web/rest/api/impl/ApplicationApiController.java index 76946420..c5817500 100644 --- a/src/main/java/net/gepafin/tendermanagement/web/rest/api/impl/ApplicationApiController.java +++ b/src/main/java/net/gepafin/tendermanagement/web/rest/api/impl/ApplicationApiController.java @@ -275,4 +275,15 @@ public class ApplicationApiController implements ApplicationApi { .contentType(MediaType.APPLICATION_OCTET_STREAM) .body(csvBytes); } + + @Override + public ResponseEntity> uploadCompanyDocumentsToApplication(HttpServletRequest request, Long applicationId,List companyDocumentIds) { + loggingUtil.logUserAction( + UserActionRequest.builder().request(request).actionType(UserActionLogsEnum.UPDATE).actionContext(UserActionContextEnum.UPLOAD_COMPANY_DOCUMENT_TO_APPLICATION).build()); + + applicationService.uploadCompanyDocumentsToApplication(request, applicationId,companyDocumentIds); + + return ResponseEntity.status(HttpStatus.OK).body(new Response<>(null, Status.SUCCESS, Translator.toLocale(GepafinConstant.UPLOAD_COMPANY_DOCUMENT_TO_APPLICATION_MSG))); + + } } diff --git a/src/main/resources/db/changelog/db.changelog-1.0.0.xml b/src/main/resources/db/changelog/db.changelog-1.0.0.xml index 233604e3..651f00f0 100644 --- a/src/main/resources/db/changelog/db.changelog-1.0.0.xml +++ b/src/main/resources/db/changelog/db.changelog-1.0.0.xml @@ -3170,4 +3170,10 @@ + + + + + + diff --git a/src/main/resources/message_en.properties b/src/main/resources/message_en.properties index 825dbc63..58dece6b 100644 --- a/src/main/resources/message_en.properties +++ b/src/main/resources/message_en.properties @@ -427,4 +427,7 @@ subject.body.required=Subject and body is required to create contract. mail.send.successfully=Mail sent succesfully. email.log.fetched=Email log fetched successfully. amendment.appropiate.status=Application amendment is not in appropiate status for this operation. +upload.company.document.to.application=Uploaded company document to application successfully. +company.document.not.found.with.ids=Company document not found. Missing IDs: {0} + diff --git a/src/main/resources/message_it.properties b/src/main/resources/message_it.properties index 0b059428..78cbb8fd 100644 --- a/src/main/resources/message_it.properties +++ b/src/main/resources/message_it.properties @@ -418,3 +418,5 @@ subject.body.required=Per creare un contratto sono necessari oggetto e corpo. mail.send.successfully=Email inviata con successo. email.log.fetched=Registro email recuperato correttamente. amendment.appropiate.status=L'emendamento dell'applicazione non č in stato appropriato per questa operazione. +upload.company.document.to.application=Documento aziendale caricato correttamente nell'applicazione. +company.document.not.found.with.ids=Documento aziendale non trovato. ID mancanti: {0} \ No newline at end of file From 6f6a56b2814f83eea2cb5e914b9db720a7292a1c Mon Sep 17 00:00:00 2001 From: rajesh Date: Tue, 18 Nov 2025 16:59:43 +0530 Subject: [PATCH 02/42] Fixed issue in company document get endpoint --- .../dao/CompanyDocumentDao.java | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/main/java/net/gepafin/tendermanagement/dao/CompanyDocumentDao.java b/src/main/java/net/gepafin/tendermanagement/dao/CompanyDocumentDao.java index 687917df..ca266679 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/CompanyDocumentDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/CompanyDocumentDao.java @@ -333,6 +333,11 @@ public class CompanyDocumentDao { predicate = builder.and(predicate, builder.isFalse(root.get("isDeleted"))); + predicate = builder.and( + predicate, + builder.notEqual(root.get("status"), CompanyDocumentStatusEnum.EXPIRED.getValue()) + ); + if (typeEnum != null) { if (typeEnum == CompanyDocumentTypeEnum.COMPANY_DOCUMENT) { // Case 1: Fetch only COMPANY_DOCUMENT type documents for the given company @@ -346,15 +351,15 @@ 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) + ); + predicate = builder.and(predicate, builder.or(companyPredicate, personalPredicate)); } - // 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) - ); - predicate = builder.and(predicate, builder.or(companyPredicate, personalPredicate)); - predicate = builder.equal(root.get("status"), CompanyDocumentStatusEnum.VALID.getValue()); return predicate; }; } From 3da924c3d6f3094b2926f1087f2d45e9ba423d5f Mon Sep 17 00:00:00 2001 From: rajesh Date: Tue, 18 Nov 2025 19:37:03 +0530 Subject: [PATCH 03/42] Disabled evaluation scheduler and increased evaluation duration --- .../gepafin/tendermanagement/dao/ApplicationEvaluationDao.java | 2 +- .../scheduler/ApplicationEvaluationScheduler.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationEvaluationDao.java b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationEvaluationDao.java index a07cb582..4379fbc8 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationEvaluationDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationEvaluationDao.java @@ -170,7 +170,7 @@ public class ApplicationEvaluationDao { Long hubId = application.getHubId(); HubEntity hub = hubService.valdateHub(hubId); - Long initialDays = (hub != null) ? hub.getEvaluationExpirationDays() : 30L; + Long initialDays = (hub != null) ? hub.getEvaluationExpirationDays() : 365L; entity.setApplicationId(application.getId()); entity.setAssignedApplicationsEntity(assignedApplications); diff --git a/src/main/java/net/gepafin/tendermanagement/scheduler/ApplicationEvaluationScheduler.java b/src/main/java/net/gepafin/tendermanagement/scheduler/ApplicationEvaluationScheduler.java index 413efe37..dd32315f 100644 --- a/src/main/java/net/gepafin/tendermanagement/scheduler/ApplicationEvaluationScheduler.java +++ b/src/main/java/net/gepafin/tendermanagement/scheduler/ApplicationEvaluationScheduler.java @@ -45,7 +45,7 @@ public class ApplicationEvaluationScheduler { private static final Logger log = LoggerFactory.getLogger(ApplicationEvaluationScheduler.class); - @Scheduled(cron = "0 0 2 * * ?") // Runs daily at midnight +// @Scheduled(cron = "0 0 2 * * ?") // Runs daily at midnight public void updateExpiredEvaluations() { log.info("Starting the Application Evaluation Expiration scheduler..."); try { From a87779deac5a4d9b4ac0e1e8b2572894fbe1611f Mon Sep 17 00:00:00 2001 From: rajesh Date: Wed, 19 Nov 2025 12:19:49 +0530 Subject: [PATCH 04/42] Added companyId in evaluation response --- .../gepafin/tendermanagement/dao/ApplicationEvaluationDao.java | 3 ++- .../model/response/ApplicationEvaluationFormResponse.java | 1 + .../model/response/ApplicationEvaluationResponse.java | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationEvaluationDao.java b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationEvaluationDao.java index 4379fbc8..cb052189 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationEvaluationDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationEvaluationDao.java @@ -205,7 +205,7 @@ public class ApplicationEvaluationDao { CompanyEntity company=companyService.validateCompany(entity.getAssignedApplicationsEntity().getApplication().getCompanyId()); setAmendmentDetails(entity,response); - + response.setCompanyId(company.getId()); response.setCompanyVatNumber(company.getVatNumber()); response.setCompanyCodiceAteco(company.getCodiceAteco()); setCriteriaResponses(entity, response, evaluationCriterias); @@ -2434,6 +2434,7 @@ public class ApplicationEvaluationDao { if (evaluationFormEntity != null) { response.setApplicationEvaluationFormResponse(convertEvaluationFormToResponse(evaluationFormEntity, evaluationEntity)); } + response.setCompanyId(company.getId()); response.setCompanyVatNumber(company.getVatNumber()); response.setCompanyCodiceAteco(company.getCodiceAteco()); response.setSignedDocument(getApplicationSignedDocument(evaluationEntity)); diff --git a/src/main/java/net/gepafin/tendermanagement/model/response/ApplicationEvaluationFormResponse.java b/src/main/java/net/gepafin/tendermanagement/model/response/ApplicationEvaluationFormResponse.java index 2e18339a..3903daed 100644 --- a/src/main/java/net/gepafin/tendermanagement/model/response/ApplicationEvaluationFormResponse.java +++ b/src/main/java/net/gepafin/tendermanagement/model/response/ApplicationEvaluationFormResponse.java @@ -15,6 +15,7 @@ public class ApplicationEvaluationFormResponse { private Long id; private Long applicationId; + private Long companyId; private ApplicationStatusTypeEnum applicationStatus; private Long assignedApplicationId; private String note; diff --git a/src/main/java/net/gepafin/tendermanagement/model/response/ApplicationEvaluationResponse.java b/src/main/java/net/gepafin/tendermanagement/model/response/ApplicationEvaluationResponse.java index 742edae9..d05f5816 100644 --- a/src/main/java/net/gepafin/tendermanagement/model/response/ApplicationEvaluationResponse.java +++ b/src/main/java/net/gepafin/tendermanagement/model/response/ApplicationEvaluationResponse.java @@ -15,6 +15,7 @@ public class ApplicationEvaluationResponse { private Long id; private Long applicationId; + private Long companyId; private ApplicationStatusTypeEnum applicationStatus; private Long assignedApplicationId; private String note; From 87816c9ce09e81d582d4ce4347c73715ca7b4638 Mon Sep 17 00:00:00 2001 From: rajesh Date: Wed, 19 Nov 2025 13:06:44 +0530 Subject: [PATCH 05/42] Updated code for company document --- .../java/net/gepafin/tendermanagement/dao/ApplicationDao.java | 2 +- .../repositories/CompanyDocumentRepository.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java index aa65357a..d7a5661a 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java @@ -2668,7 +2668,7 @@ public class ApplicationDao { public List validateCompanyDocuments(List ids) { List documents = - companyDocumentRepository.findByIdInAndIsDeletedFalseAndStatus(ids,CompanyDocumentStatusEnum.VALID.getValue()); + companyDocumentRepository.findByIdInAndIsDeletedFalseAndStatusNot(ids,CompanyDocumentStatusEnum.EXPIRED.getValue()); Set foundIds = documents.stream() .map(CompanyDocumentEntity::getId) diff --git a/src/main/java/net/gepafin/tendermanagement/repositories/CompanyDocumentRepository.java b/src/main/java/net/gepafin/tendermanagement/repositories/CompanyDocumentRepository.java index 47e0a14f..c37b993a 100644 --- a/src/main/java/net/gepafin/tendermanagement/repositories/CompanyDocumentRepository.java +++ b/src/main/java/net/gepafin/tendermanagement/repositories/CompanyDocumentRepository.java @@ -35,7 +35,7 @@ public interface CompanyDocumentRepository extends JpaRepository findByCategoryEntityId(Long categoryId); - List findByIdInAndIsDeletedFalseAndStatus(List ids,String status); + List findByIdInAndIsDeletedFalseAndStatusNot(List ids, String status); } From 362072e2094fe1c2ad6ba9e5b6cf86103328e1e4 Mon Sep 17 00:00:00 2001 From: rajesh Date: Wed, 19 Nov 2025 15:07:43 +0530 Subject: [PATCH 06/42] Allowed permission to instructor for accessing company document API --- src/main/java/net/gepafin/tendermanagement/util/Validator.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/net/gepafin/tendermanagement/util/Validator.java b/src/main/java/net/gepafin/tendermanagement/util/Validator.java index a68dc1bd..895e63d9 100644 --- a/src/main/java/net/gepafin/tendermanagement/util/Validator.java +++ b/src/main/java/net/gepafin/tendermanagement/util/Validator.java @@ -92,6 +92,8 @@ public class Validator { return companyEntity; } else if (checkIsInstructorManager()) { return companyEntity; + }else if (checkIsPreInstructor()) { + return companyEntity; } Map userInfo = tokenProvider.getUserInfoAndUserIdFromToken(request); companyService.validateUserWithCompny(getUserId(userInfo), companyId); From 745a1ec2c955ee7f599391b39bc866d46ee4a6bf Mon Sep 17 00:00:00 2001 From: rajesh Date: Wed, 19 Nov 2025 16:34:43 +0530 Subject: [PATCH 07/42] Fixed issue of email --- .../net/gepafin/tendermanagement/dao/EmailNotificationDao.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/net/gepafin/tendermanagement/dao/EmailNotificationDao.java b/src/main/java/net/gepafin/tendermanagement/dao/EmailNotificationDao.java index 4bf00449..117c3b4a 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/EmailNotificationDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/EmailNotificationDao.java @@ -257,7 +257,7 @@ public class EmailNotificationDao { //SMTP emailLogRequest.setAttachments(attachmentRequests); EmailLogEntity emailLogEntity=emailLogDao.createEmailLog(emailLogRequest,urls); - sendMail(null, subject, body, List.of(rinaldoEmail), emailLogEntity); + sendMail(applicationEntity.getHubId(), subject, body, List.of(rinaldoEmail), emailLogEntity); } if (applicationEvaluationEntity.isPresent()) { Long preInstructorId = applicationEvaluationEntity.get().getUserId(); // Assuming UserEntity has an email field From a4efa8f3c481a686c70db8643431d49f099ec80f Mon Sep 17 00:00:00 2001 From: rajesh Date: Thu, 20 Nov 2025 15:09:20 +0530 Subject: [PATCH 08/42] Enabled company document for director --- .../net/gepafin/tendermanagement/dao/CompanyDocumentDao.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/net/gepafin/tendermanagement/dao/CompanyDocumentDao.java b/src/main/java/net/gepafin/tendermanagement/dao/CompanyDocumentDao.java index ca266679..2eba35f5 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/CompanyDocumentDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/CompanyDocumentDao.java @@ -314,7 +314,9 @@ public class CompanyDocumentDao { public List getAllCompanyDocument(UserEntity user , Long companyId, CompanyDocumentTypeEnum typeEnum){ log.info("Fetching all company documents for Company ID '{}', User ID '{}', Type '{}'", companyId, user.getId(), typeEnum); - validator.validateUserWithCompany(request, companyId); + if(Boolean.TRUE.equals(validator.checkIsBeneficiary())) { + validator.validateUserWithCompany(request, companyId); + } companyService.validateCompany(companyId); Specification spec = filterCompanyDocuments(companyId, user.getId(), typeEnum); From 06e9783e51b7e4d5ea399226f4014435645493c8 Mon Sep 17 00:00:00 2001 From: rajesh Date: Wed, 28 Jan 2026 20:30:25 +0530 Subject: [PATCH 09/42] Updated password of NDG appointment --- src/main/resources/application-local.properties | 4 ++-- src/main/resources/application-production.properties | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/resources/application-local.properties b/src/main/resources/application-local.properties index 9cd21534..b61da230 100644 --- a/src/main/resources/application-local.properties +++ b/src/main/resources/application-local.properties @@ -1,5 +1,5 @@ # DataSource Configuration -spring.datasource.url=jdbc:postgresql://localhost:5432/gepafin_local +spring.datasource.url=jdbc:postgresql://localhost:5432/postgres spring.datasource.username=postgres spring.datasource.password=root spring.datasource.driver-class-name=org.postgresql.Driver @@ -17,7 +17,7 @@ default.hub.uuid=p4lk3bcx1RStqTaIVVbXs appointment.base.url=https://demo.galileonetwork.it/gateway/rest appointment.portal.user=UtenzaAPIPortal@621 -appointment.portal.password=u13nzaAP1P0rtal! +appointment.portal.password=Sardegna2025! appointment.portal.source=GEPAFINPORTAL appointment.portal.context=GEPAFINPORTAL diff --git a/src/main/resources/application-production.properties b/src/main/resources/application-production.properties index b159155c..a38014af 100644 --- a/src/main/resources/application-production.properties +++ b/src/main/resources/application-production.properties @@ -26,7 +26,7 @@ default.hub.uuid=p4lk3bcx1RStqTaIVVbXs #Login to Odessa, Appointment Creation, Upload document Configuration appointment.base.url=https://prd.galileonetwork.it/gateway/rest appointment.portal.user=UtenzaAPIPortal@621 -appointment.portal.password=Valeria2016! +appointment.portal.password=Gepafin2025! appointment.portal.source=GEPAFINPORTAL appointment.portal.context=GEPAFINPORTAL From b93e1da75f47e89eb8528f661cffc569646c81ff Mon Sep 17 00:00:00 2001 From: rajesh Date: Tue, 3 Feb 2026 15:24:26 +0530 Subject: [PATCH 10/42] Removed condition of APPOINTMENT status --- .../gepafin/tendermanagement/dao/ApplicationEvaluationDao.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationEvaluationDao.java b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationEvaluationDao.java index cb052189..db30b057 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationEvaluationDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationEvaluationDao.java @@ -1976,7 +1976,8 @@ public class ApplicationEvaluationDao { List responses = new ArrayList<>(); - if(newStatus.equals(ApplicationStatusForEvaluation.ADMISSIBLE) && Boolean.TRUE.equals(application.getStatus().equals(ApplicationStatusTypeEnum.APPOINTMENT.getValue()))){ +// if(newStatus.equals(ApplicationStatusForEvaluation.ADMISSIBLE) && Boolean.TRUE.equals(application.getStatus().equals(ApplicationStatusTypeEnum.APPOINTMENT.getValue()))){ + if(newStatus.equals(ApplicationStatusForEvaluation.ADMISSIBLE)){ application.setStatus(newStatus.getValue()); log.info("Status updated to ADMISSIBLE for applicationId: " + application.getId()); emailNotificationDao.sendAdmissibilityNotificationEmailForAdmissibleApplication(application); From 822917a16c2130f6fc9a4b364269b56c613ff366 Mon Sep 17 00:00:00 2001 From: rajesh Date: Fri, 20 Feb 2026 16:43:20 +0530 Subject: [PATCH 11/42] Fixed document issue in mail sending API --- .../net/gepafin/tendermanagement/util/S3DocxProcessor.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/net/gepafin/tendermanagement/util/S3DocxProcessor.java b/src/main/java/net/gepafin/tendermanagement/util/S3DocxProcessor.java index 86037d8f..999f44b8 100644 --- a/src/main/java/net/gepafin/tendermanagement/util/S3DocxProcessor.java +++ b/src/main/java/net/gepafin/tendermanagement/util/S3DocxProcessor.java @@ -50,9 +50,9 @@ public class S3DocxProcessor { try (S3Object s3Object = s3Client.getObject(bucket, key); InputStream originalStream = new BufferedInputStream(s3Object.getObjectContent())) { byte[] updatedBytes=null; - if (isDocxFile(originalStream)) { - log.warn("Skipping non-DOCX file from S3: bucket={}, key={}", bucket, key); - updatedBytes = replacePlaceholders(originalStream, replacements); + if(Boolean.FALSE.equals(key.endsWith(".zip")) && (replacements!=null && Boolean.FALSE.equals(replacements.isEmpty())) && isDocxFile(originalStream)) { + log.warn("Skipping non-DOCX file from S3: bucket={}, key={}", bucket, key); + updatedBytes = replacePlaceholders(originalStream, replacements); }else { // non-DOCX → just copy raw stream updatedBytes = originalStream.readAllBytes(); From ccef97b0accb1750fcb3fbf63cbe9d07cbdc6824 Mon Sep 17 00:00:00 2001 From: rajesh Date: Mon, 23 Feb 2026 12:53:06 +0530 Subject: [PATCH 12/42] Implemented new email sending flow for type BLUE TONGUE --- .../constants/GepafinConstant.java | 1 + .../dao/ApplicationAmendmentRequestDao.java | 7 ++++ .../dao/EmailNotificationDao.java | 8 +++- .../ApplicationAmendmentRequestEntity.java | 7 ++++ .../entities/SystemEmailTemplatesEntity.java | 3 +- .../enums/AmendmentDocumentTypeEnum.java | 4 +- .../enums/EmailScenarioTypeEnum.java | 1 + .../ApplicationAmendmentSpecialRequest.java | 4 ++ ...ApplicationAmendmentRequestRepository.java | 2 + .../service/impl/PecEmailService.java | 40 +++++++++++++------ .../db/changelog/db.changelog-1.0.0.xml | 21 ++++++++++ ...ecial_amendment_blue_tongue_23_02_2026.sql | 11 +++++ src/main/resources/message_en.properties | 3 +- src/main/resources/message_it.properties | 1 + 14 files changed, 96 insertions(+), 17 deletions(-) create mode 100644 src/main/resources/db/dump/insert_system_email_template_for_special_amendment_blue_tongue_23_02_2026.sql diff --git a/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java b/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java index 4d0a3e04..74b4e221 100644 --- a/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java +++ b/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java @@ -636,6 +636,7 @@ public class GepafinConstant { public static final String APPLICATION_AMENDMENT_APPROPIATE_STATUS="amendment.appropiate.status"; public static final String UPLOAD_COMPANY_DOCUMENT_TO_APPLICATION_MSG="upload.company.document.to.application"; public static final String COMPANY_DOCUMENT_NOT_FOUND_WITH_IDS="company.document.not.found.with.ids"; + public static final String REQUIRED_AMOUNT_FIELD_NOT_PROVIDED = "amount.field.not.provided"; } diff --git a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationAmendmentRequestDao.java b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationAmendmentRequestDao.java index d8243429..bb9a2b29 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationAmendmentRequestDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationAmendmentRequestDao.java @@ -1859,6 +1859,11 @@ public class ApplicationAmendmentRequestDao { ApplicationEvaluationEntity oldApplicationEvaluationEntity = Utils.getClonedEntityForData(applicationEvaluationEntity); ApplicationEntity applicationEntity=applicationDao.validateApplication(applicationEvaluationEntity.getApplicationId()); ApplicationEntity oldApplicationEntity = Utils.getClonedEntityForData(applicationEntity); + if(Boolean.TRUE.equals(applicationAmendmentRequest.getAmendmentDocumentType().equals(AmendmentDocumentTypeEnum.BLUE_TONGUE))) { + if(applicationAmendmentRequest.getBlueTongueField1()==null || applicationAmendmentRequest.getBlueTongueField2()==null){ + throw new CustomValidationException(Status.VALIDATION_ERROR,Translator.toLocale(GepafinConstant.REQUIRED_AMOUNT_FIELD_NOT_PROVIDED)); + } + } if(Boolean.FALSE.equals(applicationEntity.getStatus().equals(ApplicationStatusTypeEnum.ADMISSIBLE.getValue()))) { throw new CustomValidationException(Status.VALIDATION_ERROR,Translator.toLocale(GepafinConstant.INVALID_APPLICATION_STATUS)); @@ -1922,6 +1927,8 @@ public class ApplicationAmendmentRequestDao { protocolDao.saveProtocolEntity(protocolEntity); applicationAmendmentRequestEntity.setProtocol(protocolEntity); applicationAmendmentRequestEntity.setAmendmentDocumentType(applicationAmendmentRequest.getAmendmentDocumentType().getValue()); + applicationAmendmentRequestEntity.setBlueTongueField1(applicationAmendmentRequest.getBlueTongueField1()); + applicationAmendmentRequestEntity.setBlueTongueField2(applicationAmendmentRequest.getBlueTongueField2()); ApplicationAmendmentRequestEntity applicationAmendment = saveApplicationAmendmentRequestEntity(applicationAmendmentRequestEntity, null, VersionActionTypeEnum.INSERT); log.info("Amendment request saved with ID={}", applicationAmendment.getId()); diff --git a/src/main/java/net/gepafin/tendermanagement/dao/EmailNotificationDao.java b/src/main/java/net/gepafin/tendermanagement/dao/EmailNotificationDao.java index 117c3b4a..7cfb73cc 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/EmailNotificationDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/EmailNotificationDao.java @@ -145,7 +145,7 @@ public class EmailNotificationDao { List attachmentRequests =new ArrayList<>(); List urls=new ArrayList<>(); List documentEntities=new ArrayList<>(); - if(systemEmailTemplateResponse.getEmailScenario().equals(EmailScenarioTypeEnum.SPECIAL_APPLICATION_AMENDMENT_REQUESTED)) { + if(systemEmailTemplateResponse.getEmailScenario().equals(EmailScenarioTypeEnum.SPECIAL_APPLICATION_AMENDMENT_REQUESTED) || systemEmailTemplateResponse.getEmailScenario().equals(EmailScenarioTypeEnum.SPECIAL_APPLICATION_AMENDMENT_REQUESTED_BLUE_TONGUE)) { if(Boolean.TRUE.equals(AmendmentDocumentTypeEnum.ALTRE_GARANZIE.getValue().equals(applicationAmendmentRequest.getAmendmentDocumentType()))){ documentEntities=documentRepository.findBySourceInAndIsDeletedFalse(List.of(applicationAmendmentRequest.getAmendmentDocumentType(),"MODELLO_AUTOCERTIFICAZIONE","MODELLO_PRIVACY")); }else { @@ -517,7 +517,11 @@ public class EmailNotificationDao { public void sendMailforSpecialAmendment(ApplicationAmendmentRequestEntity applicationAmendmentRequestEntity,ApplicationEntity applicationEntity) { Map bodyPlaceholders = prepareEmailPlaceholders(applicationEntity, applicationAmendmentRequestEntity); - sendEmail(applicationEntity, SystemEmailTemplatesEntity.SystemEmailTemplatesEntityTypeEnum.SPECIAL_APPLICATION_AMENDMENT_REQUESTED, bodyPlaceholders, null, + SystemEmailTemplatesEntity.SystemEmailTemplatesEntityTypeEnum systemEmailTemplatesEntityTypeEnum= SystemEmailTemplatesEntity.SystemEmailTemplatesEntityTypeEnum.SPECIAL_APPLICATION_AMENDMENT_REQUESTED; + if(applicationAmendmentRequestEntity.getAmendmentDocumentType().equals(AmendmentDocumentTypeEnum.BLUE_TONGUE.getValue())){ + systemEmailTemplatesEntityTypeEnum= SystemEmailTemplatesEntity.SystemEmailTemplatesEntityTypeEnum.SPECIAL_APPLICATION_AMENDMENT_REQUESTED_BLUE_TONGUE; + } + sendEmail(applicationEntity,systemEmailTemplatesEntityTypeEnum, bodyPlaceholders, null, applicationAmendmentRequestEntity.getId(),null); } public void sendEmailForApplicationContracted(ApplicationEntity applicationEntity,ApplicationContractEntity applicationContractEntity,UserEntity user) { diff --git a/src/main/java/net/gepafin/tendermanagement/entities/ApplicationAmendmentRequestEntity.java b/src/main/java/net/gepafin/tendermanagement/entities/ApplicationAmendmentRequestEntity.java index 1ccd4eed..af7f6f24 100644 --- a/src/main/java/net/gepafin/tendermanagement/entities/ApplicationAmendmentRequestEntity.java +++ b/src/main/java/net/gepafin/tendermanagement/entities/ApplicationAmendmentRequestEntity.java @@ -5,6 +5,7 @@ import lombok.Data; import org.hibernate.annotations.Where; import net.gepafin.tendermanagement.model.response.EmailSendResponse; +import java.math.BigDecimal; import java.time.LocalDateTime; import java.util.List; @@ -77,4 +78,10 @@ public class ApplicationAmendmentRequestEntity extends BaseEntity { @Column(name = "AMENDMENT_INITIAL_DOCUMENT") private String amendmentInitialDocument; + @Column(name = "BLUE_TONGUE_FIELD_1") + private BigDecimal blueTongueField1; + + @Column(name = "BLUE_TONGUE_FIELD_2") + private BigDecimal blueTongueField2; + } diff --git a/src/main/java/net/gepafin/tendermanagement/entities/SystemEmailTemplatesEntity.java b/src/main/java/net/gepafin/tendermanagement/entities/SystemEmailTemplatesEntity.java index 0cc3c6fb..4d425442 100644 --- a/src/main/java/net/gepafin/tendermanagement/entities/SystemEmailTemplatesEntity.java +++ b/src/main/java/net/gepafin/tendermanagement/entities/SystemEmailTemplatesEntity.java @@ -57,7 +57,8 @@ public class SystemEmailTemplatesEntity extends BaseEntity { INADMISSIBILITY_TEMPLATE("INADMISSIBILITY_NOTIFICATION"), APPLICATION_SUBMISSION_FAILURE_NOTIFICATION("APPLICATION_SUBMISSION_FAILURE_NOTIFICATION"), INADMISSIBILITY_NOTIFICATION_DUE_TO_TECHNICAL_EVALUATION_FAILURE("INADMISSIBILITY_NOTIFICATION_DUE_TO_TECHNICAL_EVALUATION_FAILURE"), - SPECIAL_APPLICATION_AMENDMENT_REQUESTED("SPECIAL_APPLICATION_AMENDMENT_REQUESTED"); + SPECIAL_APPLICATION_AMENDMENT_REQUESTED("SPECIAL_APPLICATION_AMENDMENT_REQUESTED"), + SPECIAL_APPLICATION_AMENDMENT_REQUESTED_BLUE_TONGUE("SPECIAL_APPLICATION_AMENDMENT_REQUESTED_BLUE_TONGUE"); private String value; SystemEmailTemplatesEntityTypeEnum(String value) { diff --git a/src/main/java/net/gepafin/tendermanagement/enums/AmendmentDocumentTypeEnum.java b/src/main/java/net/gepafin/tendermanagement/enums/AmendmentDocumentTypeEnum.java index e1e97b63..8dd53ab8 100644 --- a/src/main/java/net/gepafin/tendermanagement/enums/AmendmentDocumentTypeEnum.java +++ b/src/main/java/net/gepafin/tendermanagement/enums/AmendmentDocumentTypeEnum.java @@ -10,7 +10,9 @@ public enum AmendmentDocumentTypeEnum { MCC_START_UP("MCC_START_UP"), - ALTRE_GARANZIE("ALTRE_GARANZIE"); + ALTRE_GARANZIE("ALTRE_GARANZIE"), + + BLUE_TONGUE("BLUE_TONGUE"); private final String value; diff --git a/src/main/java/net/gepafin/tendermanagement/enums/EmailScenarioTypeEnum.java b/src/main/java/net/gepafin/tendermanagement/enums/EmailScenarioTypeEnum.java index fe8d64b2..563e91e0 100644 --- a/src/main/java/net/gepafin/tendermanagement/enums/EmailScenarioTypeEnum.java +++ b/src/main/java/net/gepafin/tendermanagement/enums/EmailScenarioTypeEnum.java @@ -15,6 +15,7 @@ public enum EmailScenarioTypeEnum { APPLICATION_SUBMISSION_FAILURE("APPLICATION_SUBMISSION_FAILURE"), APPLICATION_TECHNICAL_EVALUATION_REJECTED("APPLICATION_TECHNICAL_EVALUATION_REJECTED"), SPECIAL_APPLICATION_AMENDMENT_REQUESTED("SPECIAL_APPLICATION_AMENDMENT_REQUESTED"), + SPECIAL_APPLICATION_AMENDMENT_REQUESTED_BLUE_TONGUE("SPECIAL_APPLICATION_AMENDMENT_REQUESTED_BLUE_TONGUE"), APPLICATION_CONTRACT_CREATED("APPLICATION_CONTRACT_CREATED"); private final String value; diff --git a/src/main/java/net/gepafin/tendermanagement/model/request/ApplicationAmendmentSpecialRequest.java b/src/main/java/net/gepafin/tendermanagement/model/request/ApplicationAmendmentSpecialRequest.java index 3a971a7c..673a5afa 100644 --- a/src/main/java/net/gepafin/tendermanagement/model/request/ApplicationAmendmentSpecialRequest.java +++ b/src/main/java/net/gepafin/tendermanagement/model/request/ApplicationAmendmentSpecialRequest.java @@ -13,4 +13,8 @@ public class ApplicationAmendmentSpecialRequest { private AmendmentDocumentTypeEnum amendmentDocumentType; private String pec; + + private BigDecimal blueTongueField1; + + private BigDecimal blueTongueField2; } diff --git a/src/main/java/net/gepafin/tendermanagement/repositories/ApplicationAmendmentRequestRepository.java b/src/main/java/net/gepafin/tendermanagement/repositories/ApplicationAmendmentRequestRepository.java index dc03dd43..f0f0aca4 100644 --- a/src/main/java/net/gepafin/tendermanagement/repositories/ApplicationAmendmentRequestRepository.java +++ b/src/main/java/net/gepafin/tendermanagement/repositories/ApplicationAmendmentRequestRepository.java @@ -156,4 +156,6 @@ public interface ApplicationAmendmentRequestRepository extends JpaRepository statusList); + ApplicationAmendmentRequestEntity findByApplicationIdAndIsDeletedFalseAndAmendmentDocumentTypeAndType(Long applicationId,String amendmentDocumentType, String type); + } diff --git a/src/main/java/net/gepafin/tendermanagement/service/impl/PecEmailService.java b/src/main/java/net/gepafin/tendermanagement/service/impl/PecEmailService.java index 7f29d522..be6aeb02 100644 --- a/src/main/java/net/gepafin/tendermanagement/service/impl/PecEmailService.java +++ b/src/main/java/net/gepafin/tendermanagement/service/impl/PecEmailService.java @@ -10,14 +10,13 @@ import net.gepafin.tendermanagement.constants.GepafinConstant; import net.gepafin.tendermanagement.dao.ApplicationDao; import net.gepafin.tendermanagement.dao.EmailLogDao; import net.gepafin.tendermanagement.dao.NotificationDao; +import net.gepafin.tendermanagement.entities.ApplicationAmendmentRequestEntity; import net.gepafin.tendermanagement.entities.ApplicationEntity; import net.gepafin.tendermanagement.entities.CompanyEntity; import net.gepafin.tendermanagement.entities.EmailLogEntity; -import net.gepafin.tendermanagement.enums.EmailScenarioTypeEnum; -import net.gepafin.tendermanagement.enums.EmailServiceTypeEnum; -import net.gepafin.tendermanagement.enums.NotificationTypeEnum; -import net.gepafin.tendermanagement.enums.StatusTypeEnum; +import net.gepafin.tendermanagement.enums.*; import net.gepafin.tendermanagement.model.request.*; +import net.gepafin.tendermanagement.repositories.ApplicationAmendmentRequestRepository; import net.gepafin.tendermanagement.repositories.EmailLogRepository; import net.gepafin.tendermanagement.service.CompanyService; import net.gepafin.tendermanagement.util.DateTimeUtil; @@ -68,6 +67,9 @@ public class PecEmailService implements EmailService { @Autowired private EmailLogRepository emailLogRepository; + @Autowired + private ApplicationAmendmentRequestRepository applicationAmendmentRequestRepository; + @Override public void sendEmail(String subject, String body, List recipientEmails, EmailConfig emailConfig, EmailLogEntity emailLogEntity, Boolean isSendEmail) { @@ -80,7 +82,7 @@ public class PecEmailService implements EmailService { S3DocxProcessor processor = new S3DocxProcessor(s3Client); List urls = Utils.convertJsonStringToList(emailLogEntity.getAttachments(), String.class); - if(emailLogEntity.getEmailType().equals(EmailScenarioTypeEnum.SPECIAL_APPLICATION_AMENDMENT_REQUESTED.getValue())) { + if(emailLogEntity.getEmailType().equals(EmailScenarioTypeEnum.SPECIAL_APPLICATION_AMENDMENT_REQUESTED.getValue()) || emailLogEntity.getEmailType().equals(EmailScenarioTypeEnum.SPECIAL_APPLICATION_AMENDMENT_REQUESTED_BLUE_TONGUE.getValue()) ) { ApplicationEntity applicationEntity = applicationDao.validateApplication(emailLogEntity.getApplicationId()); CompanyEntity company = companyService.validateCompany(applicationEntity.getCompanyId()); String amount = Utils.convertToItalianFormat(String.valueOf(applicationEntity.getAmountAccepted())); @@ -88,13 +90,27 @@ public class PecEmailService implements EmailService { if (protocolNumber == null) { protocolNumber = String.valueOf(applicationEntity.getProtocol().getProtocolNumber()); } - replacements = Map.of( - "{call_name}", applicationEntity.getCall().getName(), - "{amount_accepted}", amount, - "{pec}", "bandi.gepafin@legalmail.it", - "{company_name}", company.getCompanyName(), - "{protocol_number}", protocolNumber - ); + + replacements.put("{call_name}", applicationEntity.getCall().getName()); + replacements.put("{amount_accepted}", amount); + replacements.put("{pec}", "bandi.gepafin@legalmail.it"); + replacements.put("{company_name}", company.getCompanyName()); + replacements.put("{protocol_number}", protocolNumber); + + if (emailLogEntity.getEmailType().equals(EmailScenarioTypeEnum.SPECIAL_APPLICATION_AMENDMENT_REQUESTED_BLUE_TONGUE.getValue())){ + ApplicationAmendmentRequestEntity applicationAmendmentRequestEntity=applicationAmendmentRequestRepository.findByApplicationIdAndIsDeletedFalseAndAmendmentDocumentTypeAndType(applicationEntity.getId(), AmendmentDocumentTypeEnum.BLUE_TONGUE.getValue(),ApplicationAmendmentRequestTypeEnum.SPECIAL.getValue()); + if(applicationAmendmentRequestEntity!=null){ + if (applicationAmendmentRequestEntity.getBlueTongueField1() != null) { + replacements.put("{blue_tongue_field_1}", + Utils.convertToItalianFormat(applicationAmendmentRequestEntity.getBlueTongueField1().toString())); + } + + if (applicationAmendmentRequestEntity.getBlueTongueField2() != null) { + replacements.put("{blue_tongue_field_2}", + Utils.convertToItalianFormat(applicationAmendmentRequestEntity.getBlueTongueField2().toString())); + } + } + } } if (urls!=null && Boolean.FALSE.equals(urls.isEmpty())) { Map processedFiles = null; diff --git a/src/main/resources/db/changelog/db.changelog-1.0.0.xml b/src/main/resources/db/changelog/db.changelog-1.0.0.xml index 9c5aa1de..32c8690d 100644 --- a/src/main/resources/db/changelog/db.changelog-1.0.0.xml +++ b/src/main/resources/db/changelog/db.changelog-1.0.0.xml @@ -3173,4 +3173,25 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/db/dump/insert_system_email_template_for_special_amendment_blue_tongue_23_02_2026.sql b/src/main/resources/db/dump/insert_system_email_template_for_special_amendment_blue_tongue_23_02_2026.sql new file mode 100644 index 00000000..11bdcc02 --- /dev/null +++ b/src/main/resources/db/dump/insert_system_email_template_for_special_amendment_blue_tongue_23_02_2026.sql @@ -0,0 +1,11 @@ +INSERT INTO gepafin_schema.system_email_template +( template_name, "type", html_content, subject, "json", "system", is_deleted, created_date, updated_date, email_scenario, hub_id) +VALUES( 'Special Amendment Creation Email', 'SPECIAL_APPLICATION_AMENDMENT_REQUESTED_BLUE_TONGUE', ' + +
+

Buongiorno, vi invitiamo a prendere visione del documento allegato con cui vi informiamo dell avvenuta delibera a valere sull Avviso in oggetto.

+

Distinti Saluti,

+

{{email_signature}}

+
+ +', 'Comunicazione esito valutazione tecnica ed economico-finanziaria– Avviso {{call_name}} ', NULL, true, false, '2026-02-22 20:00:00.000', '2026-02-22 20:00:00.000', 'SPECIAL_APPLICATION_AMENDMENT_REQUESTED_BLUE_TONGUE', NULL); \ No newline at end of file diff --git a/src/main/resources/message_en.properties b/src/main/resources/message_en.properties index 7e930753..d21b41c1 100644 --- a/src/main/resources/message_en.properties +++ b/src/main/resources/message_en.properties @@ -428,7 +428,8 @@ mail.send.successfully=Mail sent succesfully. email.log.fetched=Email log fetched successfully. amendment.appropiate.status=Application amendment is not in appropiate status for this operation. upload.company.document.to.application=Uploaded company document to application successfully. -company.document.not.found.with.ids=Company document not found. Missing IDs: {0} +company.document.not.found.with.ids=Company document not found. Missing IDs: {0}. +amount.field.not.provided= Please provide the required amount fields. diff --git a/src/main/resources/message_it.properties b/src/main/resources/message_it.properties index d88b196c..3595be62 100644 --- a/src/main/resources/message_it.properties +++ b/src/main/resources/message_it.properties @@ -420,3 +420,4 @@ email.log.fetched=Registro email recuperato correttamente. amendment.appropiate.status=L'emendamento dell'applicazione non ďż˝ in stato appropriato per questa operazione. 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. From 74446109c01faebb112b86fa4a1fe4cedc25923a Mon Sep 17 00:00:00 2001 From: rajesh Date: Mon, 23 Feb 2026 14:34:31 +0530 Subject: [PATCH 13/42] Updated code --- .../tendermanagement/dao/ApplicationAmendmentRequestDao.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationAmendmentRequestDao.java b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationAmendmentRequestDao.java index d8243429..63a9b941 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationAmendmentRequestDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationAmendmentRequestDao.java @@ -1931,6 +1931,7 @@ public class ApplicationAmendmentRequestDao { ApplicationAmendmentRequestResponse applicationAmendmentRequestResponse = convertEntityToResponse(applicationAmendmentRequestEntity,false); if (!Boolean.TRUE.equals(emailSendResponse.getIsEmailSend())){ + log.info("Sending mail for the special amendment for amendment ID = {}", applicationAmendment.getId()); saveEmailSendResponse(emailSendResponse, applicationAmendmentRequestEntity); applicationAmendmentRequestResponse.setEmailSendResponse(responses); } From fdc8507669396747a4f29242828fe4f0f04901fa Mon Sep 17 00:00:00 2001 From: rajesh Date: Mon, 23 Feb 2026 14:36:00 +0530 Subject: [PATCH 14/42] Updated code --- .../web/rest/api/ApplicationAmendmentRequestApi.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/net/gepafin/tendermanagement/web/rest/api/ApplicationAmendmentRequestApi.java b/src/main/java/net/gepafin/tendermanagement/web/rest/api/ApplicationAmendmentRequestApi.java index 033f9fd6..98ca1a1b 100644 --- a/src/main/java/net/gepafin/tendermanagement/web/rest/api/ApplicationAmendmentRequestApi.java +++ b/src/main/java/net/gepafin/tendermanagement/web/rest/api/ApplicationAmendmentRequestApi.java @@ -206,7 +206,7 @@ public interface ApplicationAmendmentRequestApi { @PostMapping(value = "/user/{userId}/pagination", produces = { "application/json" }) ResponseEntity>>> getApplicationAmendmentByPaginnation(HttpServletRequest request, @Parameter(description = "The user id", required = true) @PathVariable("userId") Long userId, @RequestBody ApplicationAmendmentPaginationRequestBean userActionPaginationRequest); - @Operation(summary = "Api to create special Amendment", + @Operation(summary = "Api to create special Amendment new", responses = { @ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "404", description = "Not Found", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, examples = { From 35606f3f53a1635fed0f4d5c4883d768eab2fb31 Mon Sep 17 00:00:00 2001 From: rajesh Date: Mon, 23 Feb 2026 16:26:00 +0530 Subject: [PATCH 15/42] Updated application status setting flow --- .../gepafin/tendermanagement/dao/ApplicationEvaluationDao.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationEvaluationDao.java b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationEvaluationDao.java index db30b057..7bd70aa9 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationEvaluationDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationEvaluationDao.java @@ -2653,7 +2653,6 @@ public class ApplicationEvaluationDao { if (criteriaJson != null && applicationEntity.getCall().getThreshold()!=null ){ BigDecimal totalScore = calculateTotalScore(evaluationEntity.getCriteria()); if (totalScore.compareTo(new BigDecimal(applicationEntity.getCall().getThreshold())) >= 0) { - applicationEntity.setStatus(status.getValue()); log.info("Status updated to AWAITING_TECHNICAL_EVALUATION for applicationId: {}", applicationId); } else{ @@ -2661,6 +2660,7 @@ public class ApplicationEvaluationDao { throw new CustomValidationException(Status.BAD_REQUEST,Translator.toLocale(GepafinConstant.INSUFFICIENT_SCORE_MESSAGE)); } } + applicationEntity.setStatus(status.getValue()); } public BigDecimal calculateTotalScore(String criteriaJson){ From f3c4e631b30f8b7fda707d13779d1da3830b9b6a Mon Sep 17 00:00:00 2001 From: rajesh Date: Tue, 24 Feb 2026 11:38:46 +0530 Subject: [PATCH 16/42] Removed calculation logic from the evaluation form --- .../gepafin/tendermanagement/dao/ApplicationEvaluationDao.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationEvaluationDao.java b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationEvaluationDao.java index 7bd70aa9..d5edecb7 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationEvaluationDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationEvaluationDao.java @@ -2277,7 +2277,7 @@ public class ApplicationEvaluationDao { } Utils.setIfUpdated(applicationEvaluationFormFieldEntity::getFieldId, applicationEvaluationFormFieldEntity::setFieldId, applicationFormFieldRequestBean.getFieldId()); List contentBeans = Utils.convertJsonStringToList(evaluationFormEntity.getContent(), ContentResponseBean.class); - calculationProcessForFormula(applicationEvaluationFormEntity,contentBeans,applicationFormFieldRequestBean,fieldValidator); +// calculationProcessForFormula(applicationEvaluationFormEntity,contentBeans,applicationFormFieldRequestBean,fieldValidator); if (applicationFormFieldRequestBean.getFieldValue() != null) { applicationEvaluationFormFieldEntity.setFieldValue(Utils.convertObjectToJsonString(applicationFormFieldRequestBean.getFieldValue())); } else { From 9ae2926b881986bf52ddf1d7f178ad0fba13bad0 Mon Sep 17 00:00:00 2001 From: rajesh Date: Mon, 2 Mar 2026 17:55:55 +0530 Subject: [PATCH 17/42] Applied null check --- .../tendermanagement/dao/AppointmentDao.java | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java b/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java index aa81aab5..ecff43b2 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java @@ -534,7 +534,7 @@ public class AppointmentDao { log.info("Scheduler shut down for applicationId: {}", applicationId); } - private void processNdgGeneration(ApplicationEntity application, CompanyEntity company, HubEntity hub) { + private void processNdgGeneration(ApplicationEntity application, CompanyEntity company, HubEntity hub) { // Validate application, company, and hub Long applicationId = application.getId(); log.info("Starting NDG generation process for applicationId: {}", applicationId); @@ -560,10 +560,12 @@ public class AppointmentDao { }else { ndgResponse = retrieveNdgByVatNumber(company.getVatNumber(), authorizationToken, hub, application,company); } - if (isNdgValid(ndgResponse.getNdg())) { - saveNdg(application, company, ndgResponse.getNdg()); - log.info("NDG successfully generated for applicationId: {}", applicationId); - } else { + if(ndgResponse!=null) { + if (isNdgValid(ndgResponse.getNdg())) { + saveNdg(application, company, ndgResponse.getNdg()); + log.info("NDG successfully generated for applicationId: {}", applicationId); + } + }else { log.info("Polling for NDG for applicationId: {}", applicationId); handleNdgPolling(application, company, hub, authorizationToken); } @@ -743,6 +745,9 @@ public class AppointmentDao { String responseJson = getNdgFromExternalService(vatNumber, authorizationToken); // Parse and return the NDG response AppointmentLoginResponse loginResponse=parseNdgResponse(responseJson); + if(loginResponse==null){ + return null; + } ObjectMapper objectMapper = new ObjectMapper(); JsonNode rootNode = null; try { From 7d6c55cf92a2f8323df5e2b18b47c3b8a7f8365d Mon Sep 17 00:00:00 2001 From: rajesh Date: Tue, 3 Mar 2026 12:14:48 +0530 Subject: [PATCH 18/42] Fixed NDG issue --- .../net/gepafin/tendermanagement/dao/AppointmentDao.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java b/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java index ecff43b2..7169eb08 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java @@ -166,6 +166,13 @@ public class AppointmentDao { NdganagEntity ndganagEntity = ndganagRepository.findByVatNumber(company.getVatNumber()); if (ndganagEntity != null && ndganagEntity.getNdg() != null) { ndgResponse.setNdg(ndganagEntity.getNdg()); + company.setNdg(ndganagEntity.getNdg()); + application.setNdg(ndganagEntity.getNdg()); + application.setNdgStatus(GepafinConstant.NDG_GENERATED); + application.setStatus(ApplicationStatusTypeEnum.NDG.getValue()); + + companyRepository.save(company); + applicationRepository.save(application); return ndgResponse; } From 4726cd5104c312b9094203e924be2f3702745690 Mon Sep 17 00:00:00 2001 From: rajesh Date: Thu, 5 Mar 2026 15:42:03 +0530 Subject: [PATCH 19/42] Handled non-existent user login error --- .../gepafin/tendermanagement/constants/GepafinConstant.java | 1 + .../service/impl/AuthenticationService.java | 6 +++--- src/main/resources/message_en.properties | 1 + src/main/resources/message_it.properties | 1 + 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java b/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java index 74b4e221..d312f062 100644 --- a/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java +++ b/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java @@ -9,6 +9,7 @@ public class GepafinConstant { public static final String USER_UPDATED_SUCCESS_MSG = "user.updated.success"; public static final String USER_DELETED_SUCCESS_MSG = "user.deleted.success"; public static final String USER_NOT_FOUND_MSG = "user.not.found"; + public static final String INVALID_USERNAME_AND_PASSWORD = "invalid.username.or.password"; public static final String CREATE_USER_ERROR_MSG = "create_user_error_msg"; public static final String UPDATE_USER_ERROR_MSG = "update_user_error_msg"; public static final String DELETE_USER_ERROR_MSG = "delete_user_error_msg"; diff --git a/src/main/java/net/gepafin/tendermanagement/service/impl/AuthenticationService.java b/src/main/java/net/gepafin/tendermanagement/service/impl/AuthenticationService.java index 01ad155b..ed0e862d 100644 --- a/src/main/java/net/gepafin/tendermanagement/service/impl/AuthenticationService.java +++ b/src/main/java/net/gepafin/tendermanagement/service/impl/AuthenticationService.java @@ -96,7 +96,7 @@ public class AuthenticationService { RoleStatusEnum.ROLE_BENEFICIARY.getValue() ).orElseThrow(() -> new ResourceNotFoundException( Status.NOT_FOUND, - Translator.toLocale(GepafinConstant.USER_NOT_FOUND_MSG) + Translator.toLocale(GepafinConstant.INVALID_USERNAME_AND_PASSWORD) )); String emailWithHubId = loginReq.getEmail()+":"+loginReq.getHubUuid(); @@ -115,7 +115,7 @@ public class AuthenticationService { createSuccessLoginAttempt(loginAttemptEntity); } catch (Exception e) { log.info("Authentication failed for email: {}", loginReq.getEmail()); - loginAttemptEntity.setUserId(user.getId()); +// loginAttemptEntity.setUserId(user.getId()); createFailedLoginAttempt(loginAttemptEntity, e.getMessage()); throw e; } @@ -238,7 +238,7 @@ public class AuthenticationService { return getJWTTokenBean(userEntity, Boolean.TRUE, loginAttempt.getId(),null); } catch (Exception e) { log.info("Authentication login failed for email: {}",e.getMessage()); - loginAttemptEntity.setUserId(userId); +// loginAttemptEntity.setUserId(userId); createFailedLoginAttempt(loginAttemptEntity, e.getMessage()); throw e; } diff --git a/src/main/resources/message_en.properties b/src/main/resources/message_en.properties index d21b41c1..b26ec2c2 100644 --- a/src/main/resources/message_en.properties +++ b/src/main/resources/message_en.properties @@ -2,6 +2,7 @@ user.created.success=User created successfully. user.updated.success=User updated successfully. user.deleted.success=User deleted successfully. user.not.found=User not found. +invalid.username.or.password= Invalid username or password. create_user_error_msg=An error occurred while creating the user. update_user_error_msg=An error occurred while updating the user. delete_user_error_msg=An error occurred while deleting the user. diff --git a/src/main/resources/message_it.properties b/src/main/resources/message_it.properties index 3595be62..041e08a2 100644 --- a/src/main/resources/message_it.properties +++ b/src/main/resources/message_it.properties @@ -2,6 +2,7 @@ user.created.success=Utente creato con successo. user.updated.success=Utente aggiornato con successo. user.deleted.success=Utente eliminato con successo. user.not.found=Utente non trovato. +invalid.username.or.password= Nome utente o password non validi. create_user_error_msg=Si ? verificato un errore durante la creazione dell'utente. update_user_error_msg=Si ? verificato un errore durante l'aggiornamento dell'utente. delete_user_error_msg=Si ? verificato un errore durante l'eliminazione dell'utente. From aec9fa6b5de3181903d9a96532d59c4fb6a97e55 Mon Sep 17 00:00:00 2001 From: rajesh Date: Thu, 5 Mar 2026 16:53:51 +0530 Subject: [PATCH 20/42] Fixed upload document external system issue --- .../java/net/gepafin/tendermanagement/dao/AppointmentDao.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java b/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java index 7169eb08..069d823d 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java @@ -1240,9 +1240,9 @@ public class AppointmentDao { if (documentAttachmentId!=null) { // If the documentAttachmentId is already set, return the response - log.info("Document already uploaded with documentAttachmentId: {}", systemDoc.getDocumentAttachmentId()); + log.info("Document already uploaded with documentAttachmentId: {}", documentAttachmentId); DocumentUploadResponse response = new DocumentUploadResponse(); - response.setDocumentAttachmentId(systemDoc.getDocumentAttachmentId()); + response.setDocumentAttachmentId(documentAttachmentId); return response; } // Check if a thread is already running for this document upload From 01dff4a88aa4165c9a189b37c88b979c658a39a1 Mon Sep 17 00:00:00 2001 From: rajesh Date: Mon, 9 Mar 2026 13:46:20 +0530 Subject: [PATCH 21/42] Added documentAttachmentId in response of evaluation --- .../java/net/gepafin/tendermanagement/dao/ApplicationDao.java | 1 + .../model/response/ApplicationSignedDocumentResponse.java | 1 + 2 files changed, 2 insertions(+) diff --git a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java index d7a5661a..7a351d08 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java @@ -1453,6 +1453,7 @@ public class ApplicationDao { applicationSignedDocumentResponse.setCreatedDate(applicationSignedDocument.getCreatedDate()); applicationSignedDocumentResponse.setUpdatedDate(applicationSignedDocument.getUpdatedDate()); applicationSignedDocumentResponse.setFileHash(applicationSignedDocument.getFileHash()); + applicationSignedDocumentResponse.setDocumentAttachmentId(applicationSignedDocument.getDocumentAttachmentId()); return applicationSignedDocumentResponse; } diff --git a/src/main/java/net/gepafin/tendermanagement/model/response/ApplicationSignedDocumentResponse.java b/src/main/java/net/gepafin/tendermanagement/model/response/ApplicationSignedDocumentResponse.java index 249ed7f8..a9ccc386 100644 --- a/src/main/java/net/gepafin/tendermanagement/model/response/ApplicationSignedDocumentResponse.java +++ b/src/main/java/net/gepafin/tendermanagement/model/response/ApplicationSignedDocumentResponse.java @@ -12,4 +12,5 @@ public class ApplicationSignedDocumentResponse extends BaseBean{ private String filePath; private ApplicationSignedDocumentStatusEnum status; private String fileHash; + private String documentAttachmentId; } From 45687f51d75ae4c0b1b53a8bbe7071e81f385ed7 Mon Sep 17 00:00:00 2001 From: rajesh Date: Tue, 10 Mar 2026 16:50:00 +0530 Subject: [PATCH 22/42] Managed company creation --- .../config/MessageSourceConfig.java | 2 +- .../config/WebSocketConfig.java | 16 ++++- .../constants/GepafinConstant.java | 3 +- .../tendermanagement/dao/AppointmentDao.java | 3 + .../tendermanagement/dao/CompanyDao.java | 65 ++++++++++++++++--- .../entities/CompanyEntity.java | 3 + src/main/resources/application-dev.properties | 4 +- .../resources/application-local.properties | 3 +- .../application-production.properties | 4 +- .../resources/application-testing.properties | 3 +- .../db/changelog/db.changelog-1.0.0.xml | 5 ++ src/main/resources/message_en.properties | 2 + src/main/resources/message_it.properties | 4 +- 13 files changed, 100 insertions(+), 17 deletions(-) diff --git a/src/main/java/net/gepafin/tendermanagement/config/MessageSourceConfig.java b/src/main/java/net/gepafin/tendermanagement/config/MessageSourceConfig.java index 79ef4d11..1159a0aa 100644 --- a/src/main/java/net/gepafin/tendermanagement/config/MessageSourceConfig.java +++ b/src/main/java/net/gepafin/tendermanagement/config/MessageSourceConfig.java @@ -14,7 +14,7 @@ public class MessageSourceConfig { public ResourceBundleMessageSource messageSource() { ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource(); messageSource.setBasenames("message"); - messageSource.setDefaultEncoding("UTF-8"); + messageSource.setDefaultEncoding("ISO-8859-1"); messageSource.setUseCodeAsDefaultMessage(true); return messageSource; } diff --git a/src/main/java/net/gepafin/tendermanagement/config/WebSocketConfig.java b/src/main/java/net/gepafin/tendermanagement/config/WebSocketConfig.java index 0212f720..1b9a9e36 100644 --- a/src/main/java/net/gepafin/tendermanagement/config/WebSocketConfig.java +++ b/src/main/java/net/gepafin/tendermanagement/config/WebSocketConfig.java @@ -23,10 +23,22 @@ public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { @Value("${spring.rabbitmq.password}") private String clientPassword; + @Value("${rabbitmq.enabled:false}") + private boolean rabbitmqEnabled; + @Override public void configureMessageBroker(MessageBrokerRegistry config) { - - config.enableStompBrokerRelay("/topic").setRelayHost(relayHost).setRelayPort(relayPort).setClientLogin(clientUserName).setClientPasscode(clientPassword); + if (rabbitmqEnabled) { + // Use external RabbitMQ broker + config.enableStompBrokerRelay("/topic") + .setRelayHost(relayHost) + .setRelayPort(relayPort) + .setClientLogin(clientUserName) + .setClientPasscode(clientPassword); + } else { + // Use in-memory simple broker + config.enableSimpleBroker("/topic"); + } config.setApplicationDestinationPrefixes("/app"); } diff --git a/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java b/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java index d312f062..46a8eb23 100644 --- a/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java +++ b/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java @@ -199,6 +199,7 @@ public class GepafinConstant { public static final String VALIDATION_ERROR_FILE_EMPTY = "validation.error.file.empty"; public static final String VALIDATION_ERROR_FILE_INVALIDTYPE = "validation.error.file.invalidType"; public static final String UPLOAD_ERROR_S3 = "upload.error.s3"; + public static final String VAT_OR_TAX_CODE_REQUIRED = "vat.or.tax.code.required"; public static final String CALL_NOT_STARTED_YET = "call.not.started.yet"; public static final String CALL_ALREADY_ENDED = "call.already.ended"; @@ -638,7 +639,7 @@ public class GepafinConstant { public static final String UPLOAD_COMPANY_DOCUMENT_TO_APPLICATION_MSG="upload.company.document.to.application"; public static final String COMPANY_DOCUMENT_NOT_FOUND_WITH_IDS="company.document.not.found.with.ids"; public static final String REQUIRED_AMOUNT_FIELD_NOT_PROVIDED = "amount.field.not.provided"; - + public static final String PLEASE_PROVIDE_VALID_VAT_NUMBER="provide.valid.vat.number"; } diff --git a/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java b/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java index 069d823d..0e25e8d4 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java @@ -163,6 +163,9 @@ public class AppointmentDao { NdgResponse ndgResponse = new NdgResponse(); CompanyEntity company = companyService.validateCompany(application.getCompanyId()); + if(Boolean.FALSE.equals(company.getValidVat())){ + throw new CustomValidationException(Status.VALIDATION_ERROR,Translator.toLocale(GepafinConstant.PLEASE_PROVIDE_VALID_VAT_NUMBER)); + } NdganagEntity ndganagEntity = ndganagRepository.findByVatNumber(company.getVatNumber()); if (ndganagEntity != null && ndganagEntity.getNdg() != null) { ndgResponse.setNdg(ndganagEntity.getNdg()); diff --git a/src/main/java/net/gepafin/tendermanagement/dao/CompanyDao.java b/src/main/java/net/gepafin/tendermanagement/dao/CompanyDao.java index 8b59eebe..8c6a8b1b 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/CompanyDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/CompanyDao.java @@ -69,13 +69,26 @@ public class CompanyDao { @Autowired private HttpServletRequest request; + @Autowired + private VatCheckDao vatCheckDao; + private static final String NOT_FOUND_JSON = "{\"data\": \"not found\"}"; public CompanyResponse createCompany(UserEntity userEntity, CompanyRequest companyRequest) { log.info("Initiating company creation by userId: {}", userEntity.getId()); - CompanyEntity existingCompany = companyRepository.findByVatNumberAndHubId(companyRequest.getVatNumber(), userEntity.getHub().getId()); + Boolean validVat=Boolean.FALSE; + if(companyRequest.getVatNumber()!=null){ + VatCheckResponseBean vatCheckResponseBean=vatCheckDao.checkVatNumber(companyRequest.getVatNumber(), userEntity.getHub().getId()); + if(vatCheckResponseBean!=null && Boolean.TRUE.equals(vatCheckResponseBean.getValid())){ + validVat=Boolean.TRUE; + } + } + CompanyEntity existingCompany = null; + if (!StringUtils.isEmpty(companyRequest.getVatNumber())) { + existingCompany = companyRepository.findByVatNumberAndHubId(companyRequest.getVatNumber(), userEntity.getHub().getId()); + } UserWithCompanyEntity userWithCompanyEntity = null; if (existingCompany != null) { UserWithCompanyEntity existingRelation = userWithCompanyRepository.findByUserIdAndCompanyIdAndIsDeletedFalse(userEntity.getId(), existingCompany.getId()).orElse(null); @@ -93,6 +106,7 @@ public class CompanyDao { } else { validateCompany(userEntity, companyRequest); CompanyEntity companyEntity = convertCompanyRequestToCompanyEntity(userEntity, companyRequest); + companyEntity.setValidVat(validVat); CompanyEntity companyData = companyRepository.save(companyEntity); /** This code is responsible for adding a version history log for "creating company" operation. **/ @@ -112,12 +126,16 @@ public class CompanyDao { throw new CustomValidationException(Status.VALIDATION_ERROR, Translator.toLocale(GepafinConstant.INVALID_EMAIL)); } - if (StringUtils.isEmpty(companyRequest.getVatNumber())) { + // At least one identifier required + if (StringUtils.isEmpty(companyRequest.getVatNumber()) + && StringUtils.isEmpty(companyRequest.getCodiceFiscale())) { throw new CustomValidationException(Status.VALIDATION_ERROR, - Translator.toLocale(GepafinConstant.VATNUMBER_MANDATORY)); + Translator.toLocale(GepafinConstant.VAT_OR_TAX_CODE_REQUIRED)); } - if (companyRepository.existsByVatNumberAndHubId(companyRequest.getVatNumber(), userEntity.getHub().getId())) { - throw new CustomValidationException(Status.VALIDATION_ERROR, + // Only check VAT uniqueness if VAT provided + if (!StringUtils.isEmpty(companyRequest.getVatNumber()) + && companyRepository.existsByVatNumberAndHubId(companyRequest.getVatNumber(), userEntity.getHub().getId())) { + throw new CustomValidationException(Status.VALIDATION_ERROR, Translator.toLocale(GepafinConstant.VATNUMBER_ALREADY_EXISTS)); } } @@ -140,7 +158,7 @@ public class CompanyDao { UserWithCompanyEntity userWithCompany = userWithCompanyRepository.save(userWithCompanyEntity); /** This code is responsible for adding a version history log for the "adding user with company" operation. **/ loggingUtil.addVersionHistory(VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.INSERT).oldData(null).newData(userWithCompany).build()); - if (StringUtils.isEmpty(companyEntity.getJson())) { + if (StringUtils.isEmpty(companyEntity.getJson()) && companyRequest.getVatCheckResponse() != null) { companyEntity.setJson(Utils.convertMapIntoJsonString(companyRequest.getVatCheckResponse())); updateCodiceAtecoFieldWithNewJson(companyEntity); companyEntity = companyRepository.save(companyEntity); @@ -161,7 +179,11 @@ public class CompanyDao { private CompanyEntity convertCompanyRequestToCompanyEntity(UserEntity userEntity, CompanyRequest request) { CompanyEntity entity = new CompanyEntity(); entity.setCompanyName(request.getCompanyName()); - entity.setVatNumber(request.getVatNumber()); + if(request.getVatNumber()==null){ + entity.setVatNumber(request.getCodiceFiscale()); + }else { + entity.setVatNumber(request.getVatNumber()); + } entity.setCodiceFiscale(request.getCodiceFiscale()); entity.setAddress(request.getAddress()); entity.setPhoneNumber(request.getPhoneNumber()); @@ -182,7 +204,11 @@ public class CompanyDao { CompanyResponse response = new CompanyResponse(); response.setId(entity.getId()); response.setCompanyName(entity.getCompanyName()); - response.setVatNumber(entity.getVatNumber()); + if(entity.getVatNumber()==null){ + response.setVatNumber(entity.getCodiceFiscale()); + }else { + response.setVatNumber(entity.getVatNumber()); + } response.setCodiceFiscale(entity.getCodiceFiscale()); response.setAddress(entity.getAddress()); response.setPhoneNumber(entity.getPhoneNumber()); @@ -231,6 +257,29 @@ public class CompanyDao { // companyEntity.setVatNumber(companyRequest.getVatNumber()); // // } + + //allow adding VAT later + if(StringUtils.isNotBlank(companyRequest.getVatNumber()) + && StringUtils.isBlank(companyEntity.getVatNumber())) { + CompanyEntity existingCompany = companyRepository.findByVatNumberAndHubId(companyRequest.getVatNumber(), userEntity.getHub().getId()); + if(existingCompany!=null){ + throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.VATNUMBER_ALREADY_EXISTS)); + } + Boolean validVat=Boolean.FALSE; + if(companyRequest.getVatNumber()!=null){ + VatCheckResponseBean vatCheckResponseBean=vatCheckDao.checkVatNumber(companyRequest.getVatNumber(), userEntity.getHub().getId()); + if(vatCheckResponseBean!=null && Boolean.TRUE.equals(vatCheckResponseBean.getValid())){ + validVat=Boolean.TRUE; + } + } + companyEntity.setVatNumber(companyRequest.getVatNumber()); + companyEntity.setValidVat(validVat); + if(companyRequest.getVatCheckResponse() != null) { + String responseJson = Utils.convertMapIntoJsonString(companyRequest.getVatCheckResponse()); + companyEntity.setJson(responseJson); + updateCodiceAtecoFieldWithNewJson(companyEntity); + } + } companyRepository.save(companyEntity); log.info("Company updated and saved. companyId: {}", companyEntity.getId()); diff --git a/src/main/java/net/gepafin/tendermanagement/entities/CompanyEntity.java b/src/main/java/net/gepafin/tendermanagement/entities/CompanyEntity.java index 8a6c94e1..0f47d5d9 100644 --- a/src/main/java/net/gepafin/tendermanagement/entities/CompanyEntity.java +++ b/src/main/java/net/gepafin/tendermanagement/entities/CompanyEntity.java @@ -64,4 +64,7 @@ public class CompanyEntity extends BaseEntity{ @Column(name = "PEC") private String pec; + + @Column(name = "VALID_VAT") + private Boolean validVat; } diff --git a/src/main/resources/application-dev.properties b/src/main/resources/application-dev.properties index 356735fb..6db0a637 100644 --- a/src/main/resources/application-dev.properties +++ b/src/main/resources/application-dev.properties @@ -29,4 +29,6 @@ spring.rabbitmq.host=172.18.0.7 spring.rabbitmq.port=61613 spring.rabbitmq.username=guest spring.rabbitmq.password=guest -spring.rabbitmq.virtual-host=/ \ No newline at end of file +spring.rabbitmq.virtual-host=/ + +rabbitmq.enabled=false \ No newline at end of file diff --git a/src/main/resources/application-local.properties b/src/main/resources/application-local.properties index b61da230..e1abc344 100644 --- a/src/main/resources/application-local.properties +++ b/src/main/resources/application-local.properties @@ -26,4 +26,5 @@ spring.rabbitmq.host=localhost spring.rabbitmq.port=61613 spring.rabbitmq.username=guest spring.rabbitmq.password=guest -spring.rabbitmq.virtual-host=/ \ No newline at end of file +spring.rabbitmq.virtual-host=/ +rabbitmq.enabled=false \ No newline at end of file diff --git a/src/main/resources/application-production.properties b/src/main/resources/application-production.properties index a38014af..bfb4a6c3 100644 --- a/src/main/resources/application-production.properties +++ b/src/main/resources/application-production.properties @@ -38,4 +38,6 @@ spring.rabbitmq.username=guest spring.rabbitmq.password=guest spring.rabbitmq.virtual-host=/ -isSviluppumbriaProtocolEnabled = false \ No newline at end of file +isSviluppumbriaProtocolEnabled = false + +rabbitmq.enabled=true \ No newline at end of file diff --git a/src/main/resources/application-testing.properties b/src/main/resources/application-testing.properties index 532deb3f..912727b9 100644 --- a/src/main/resources/application-testing.properties +++ b/src/main/resources/application-testing.properties @@ -24,4 +24,5 @@ spring.rabbitmq.host=rabbitmq.bflows.ai spring.rabbitmq.port=61613 spring.rabbitmq.username=guest spring.rabbitmq.password=guest -spring.rabbitmq.virtual-host=/ \ No newline at end of file +spring.rabbitmq.virtual-host=/ +rabbitmq.enabled=false \ No newline at end of file diff --git a/src/main/resources/db/changelog/db.changelog-1.0.0.xml b/src/main/resources/db/changelog/db.changelog-1.0.0.xml index 32c8690d..36e1c630 100644 --- a/src/main/resources/db/changelog/db.changelog-1.0.0.xml +++ b/src/main/resources/db/changelog/db.changelog-1.0.0.xml @@ -3193,5 +3193,10 @@ + + + + + diff --git a/src/main/resources/message_en.properties b/src/main/resources/message_en.properties index b26ec2c2..64331f3d 100644 --- a/src/main/resources/message_en.properties +++ b/src/main/resources/message_en.properties @@ -431,6 +431,8 @@ amendment.appropiate.status=Application amendment is not in appropiate status fo upload.company.document.to.application=Uploaded company document to application successfully. company.document.not.found.with.ids=Company document not found. Missing IDs: {0}. amount.field.not.provided= Please provide the required amount fields. +vat.or.tax.code.required=VAT Number or Tax Code is required. +provide.valid.vat.number=Please provide a valid vat number to proceed NDG. diff --git a/src/main/resources/message_it.properties b/src/main/resources/message_it.properties index 041e08a2..5265325f 100644 --- a/src/main/resources/message_it.properties +++ b/src/main/resources/message_it.properties @@ -71,7 +71,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. Per favore riprova +common_message=Qualcosa č andato storto. Riprova. invalid_signature=Gettone non valido. invalid_login=Nome utente o password errati req_validation_er=Errore di convalida @@ -422,3 +422,5 @@ amendment.appropiate.status=L'emendamento dell'applicazione non ďż˝ in stato app upload.company.document.to.application=Documento aziendale caricato correttamente nell'applicazione. company.document.not.found.with.ids=Documento aziendale non trovato. ID mancanti: {0} amount.field.not.provided= Si prega di fornire i campi obbligatori per l'importo. +vat.or.tax.code.required=Č obbligatorio il numero di partita IVA o il codice fiscale. +provide.valid.vat.number=Inserisci un numero di partita IVA valido per procedere con NDG. \ No newline at end of file From 4daf5b863a6cf3f4ac8776c268a4a2af5d0d1e8c Mon Sep 17 00:00:00 2001 From: rajesh Date: Tue, 10 Mar 2026 18:32:21 +0530 Subject: [PATCH 23/42] Updated code --- src/main/java/net/gepafin/tendermanagement/dao/CompanyDao.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/net/gepafin/tendermanagement/dao/CompanyDao.java b/src/main/java/net/gepafin/tendermanagement/dao/CompanyDao.java index 8c6a8b1b..92c90f23 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/CompanyDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/CompanyDao.java @@ -1,5 +1,6 @@ package net.gepafin.tendermanagement.dao; +import net.gepafin.tendermanagement.model.util.NanoIdUtils; import org.springframework.data.domain.Pageable; // Correct package import java.util.ArrayList; @@ -180,7 +181,7 @@ public class CompanyDao { CompanyEntity entity = new CompanyEntity(); entity.setCompanyName(request.getCompanyName()); if(request.getVatNumber()==null){ - entity.setVatNumber(request.getCodiceFiscale()); + entity.setVatNumber(NanoIdUtils.randomNanoId()); }else { entity.setVatNumber(request.getVatNumber()); } From 8673bcdde827cb6c39953dbb3fc95c8817c6f1cd Mon Sep 17 00:00:00 2001 From: rajesh Date: Wed, 11 Mar 2026 12:20:06 +0530 Subject: [PATCH 24/42] Created new endpoint to reject emails by director --- .../constants/GepafinConstant.java | 1 + .../tendermanagement/dao/PecMailDao.java | 18 ++++++++++++++++++ .../entities/EmailLogEntity.java | 3 +++ .../tendermanagement/enums/StatusTypeEnum.java | 3 ++- .../enums/UserActionContextEnum.java | 1 + .../model/response/PecEmailLogResponse.java | 2 ++ .../service/PecMailService.java | 2 ++ .../service/impl/PecMailSerivceImpl.java | 5 +++++ .../web/rest/api/PecMailApi.java | 11 +++++++++++ .../web/rest/api/impl/PecMailController.java | 11 +++++++++++ .../db/changelog/db.changelog-1.0.0.xml | 6 ++++++ src/main/resources/message_en.properties | 1 + src/main/resources/message_it.properties | 1 + 13 files changed, 64 insertions(+), 1 deletion(-) diff --git a/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java b/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java index 46a8eb23..f223d922 100644 --- a/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java +++ b/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java @@ -634,6 +634,7 @@ public class GepafinConstant { public static final String APPLICATION_NOT_APPROVED="application.not.approved"; public static final String SUBJECT_AND_BODY_REQUIRED="subject.body.required"; public static final String MAIL_SENT_SUCCESSFULLY="mail.send.successfully"; + public static final String PEC_EMAIL_REJECTED_SUCCESSFULLY="pec.email.rejected.successfully"; public static final String EMAIL_LOG_FETCHED="email.log.fetched"; public static final String APPLICATION_AMENDMENT_APPROPIATE_STATUS="amendment.appropiate.status"; public static final String UPLOAD_COMPANY_DOCUMENT_TO_APPLICATION_MSG="upload.company.document.to.application"; diff --git a/src/main/java/net/gepafin/tendermanagement/dao/PecMailDao.java b/src/main/java/net/gepafin/tendermanagement/dao/PecMailDao.java index 18240044..fcaecbcc 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/PecMailDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/PecMailDao.java @@ -15,6 +15,7 @@ import net.gepafin.tendermanagement.repositories.EmailLogRepository; import net.gepafin.tendermanagement.repositories.UserActionsRepository; import net.gepafin.tendermanagement.service.ApplicationService; import net.gepafin.tendermanagement.service.CallService; +import net.gepafin.tendermanagement.util.DateTimeUtil; import net.gepafin.tendermanagement.util.Utils; import net.gepafin.tendermanagement.util.Validator; import net.gepafin.tendermanagement.web.rest.api.errors.CustomValidationException; @@ -24,6 +25,7 @@ import org.checkerframework.checker.units.qual.A; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; +import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; @@ -74,6 +76,21 @@ public class PecMailDao { return pecMailResponses; } + public PecMailResponse rejectPecMail(HttpServletRequest request, Long userActionId, String motivation) { + List emailLogs = getEmailLogEntities(request, userActionId); + LocalDateTime rejectedAt = DateTimeUtil.DateServerToUTC(LocalDateTime.now()); + for (EmailLogEntity log : emailLogs) { + log.setSendStatus(StatusTypeEnum.REJECTED.getValue()); + log.setMotivation(motivation); + log.setSendDateTime(rejectedAt); + } + emailLogRepository.saveAll(emailLogs); + EmailLogEntity firstLog = emailLogs.get(0); + ApplicationEntity applicationEntity = applicationService.validateApplication(firstLog.getApplicationId()); + String callName = applicationEntity.getCall().getName(); + return createPecMailResponse(firstLog.getUserAction().getId(), firstLog, callName); + } + private List getEmailLogEntities(HttpServletRequest request, Long userActionId) { UserActionEntity userActionEntity = userActionsRepository.findUserActionByIdAndIsDeletedFalse(userActionId); if (userActionEntity == null) { @@ -117,6 +134,7 @@ public class PecMailDao { pecEmailLogResponse.setSubject(emailLogEntity.getEmailSubject()); pecEmailLogResponse.setHtmlContent(emailLogEntity.getEmailBody()); pecEmailLogResponse.setCallId(emailLogEntity.getCallId()); + pecEmailLogResponse.setMotivation(emailLogEntity.getMotivation()); return pecEmailLogResponse; } private PecMailResponse createPecMailResponse(Long userActionId, EmailLogEntity emailLogEntity, String callName) { diff --git a/src/main/java/net/gepafin/tendermanagement/entities/EmailLogEntity.java b/src/main/java/net/gepafin/tendermanagement/entities/EmailLogEntity.java index 0a4dfc90..ba21ec3d 100644 --- a/src/main/java/net/gepafin/tendermanagement/entities/EmailLogEntity.java +++ b/src/main/java/net/gepafin/tendermanagement/entities/EmailLogEntity.java @@ -62,5 +62,8 @@ public class EmailLogEntity extends BaseEntity{ @Column(name = "ATTACHMENTS") private String attachments; + + @Column(name = "MOTIVATION", columnDefinition = "TEXT") + private String motivation; } diff --git a/src/main/java/net/gepafin/tendermanagement/enums/StatusTypeEnum.java b/src/main/java/net/gepafin/tendermanagement/enums/StatusTypeEnum.java index 738a6a17..65e9c262 100644 --- a/src/main/java/net/gepafin/tendermanagement/enums/StatusTypeEnum.java +++ b/src/main/java/net/gepafin/tendermanagement/enums/StatusTypeEnum.java @@ -5,7 +5,8 @@ import com.fasterxml.jackson.annotation.JsonValue; public enum StatusTypeEnum { PENDING ("PENDING"), SUCCESS ("SUCCESS"), - FAILED("FAILED"); + FAILED("FAILED"), + REJECTED("REJECTED"); private String value; diff --git a/src/main/java/net/gepafin/tendermanagement/enums/UserActionContextEnum.java b/src/main/java/net/gepafin/tendermanagement/enums/UserActionContextEnum.java index 9092c769..8ada4d3c 100644 --- a/src/main/java/net/gepafin/tendermanagement/enums/UserActionContextEnum.java +++ b/src/main/java/net/gepafin/tendermanagement/enums/UserActionContextEnum.java @@ -231,6 +231,7 @@ public enum UserActionContextEnum { FETCH_APPLICATION_CONTRACT_BY_APPLICATION_ID("FETCH_APPLICATION_CONTRACT_BY_APPLICATION_ID"), FETCH_APPLICATION_CONTRACT_BY_BENEFICIARY_USER_ID("FETCH_APPLICATION_CONTRACT_BY_BENEFICIARY_USER_ID"), SEND_PEC_MAIL("SEND_PEC_MAIL"), + 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"); diff --git a/src/main/java/net/gepafin/tendermanagement/model/response/PecEmailLogResponse.java b/src/main/java/net/gepafin/tendermanagement/model/response/PecEmailLogResponse.java index 6b0ada2f..7b646e84 100644 --- a/src/main/java/net/gepafin/tendermanagement/model/response/PecEmailLogResponse.java +++ b/src/main/java/net/gepafin/tendermanagement/model/response/PecEmailLogResponse.java @@ -27,4 +27,6 @@ public class PecEmailLogResponse { private Long callId; + private String motivation; + } diff --git a/src/main/java/net/gepafin/tendermanagement/service/PecMailService.java b/src/main/java/net/gepafin/tendermanagement/service/PecMailService.java index 77339aae..2d26dec4 100644 --- a/src/main/java/net/gepafin/tendermanagement/service/PecMailService.java +++ b/src/main/java/net/gepafin/tendermanagement/service/PecMailService.java @@ -10,6 +10,8 @@ public interface PecMailService { public List sendPecMail(HttpServletRequest request, List userActionIds); + public PecMailResponse rejectPecMail(HttpServletRequest request, Long userActionId, String motivation); + public List getEmailLogByUserActionId(HttpServletRequest request, Long userActionId); public List getAllEmailLogs(HttpServletRequest request); diff --git a/src/main/java/net/gepafin/tendermanagement/service/impl/PecMailSerivceImpl.java b/src/main/java/net/gepafin/tendermanagement/service/impl/PecMailSerivceImpl.java index 8e0daef1..f87b74b8 100644 --- a/src/main/java/net/gepafin/tendermanagement/service/impl/PecMailSerivceImpl.java +++ b/src/main/java/net/gepafin/tendermanagement/service/impl/PecMailSerivceImpl.java @@ -21,6 +21,11 @@ public class PecMailSerivceImpl implements PecMailService { return pecMailDao.sendPecMail(request,userActionIds); } + @Override + public PecMailResponse rejectPecMail(HttpServletRequest request, Long userActionId, String motivation) { + return pecMailDao.rejectPecMail(request, userActionId, motivation); + } + @Override public List getEmailLogByUserActionId(HttpServletRequest request, Long userActionId) { return pecMailDao.getEmailLogByUserActionId(request,userActionId); diff --git a/src/main/java/net/gepafin/tendermanagement/web/rest/api/PecMailApi.java b/src/main/java/net/gepafin/tendermanagement/web/rest/api/PecMailApi.java index 27fdcc27..461c3087 100644 --- a/src/main/java/net/gepafin/tendermanagement/web/rest/api/PecMailApi.java +++ b/src/main/java/net/gepafin/tendermanagement/web/rest/api/PecMailApi.java @@ -31,6 +31,17 @@ public interface PecMailApi { ResponseEntity>> sendPecMail(HttpServletRequest request, @Parameter(description = "The user action id", required = true) @RequestParam("userActionIds") List userActionIds); + @Operation(summary = "Api to reject PEC email for a user action with motivation.", 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 = "/userAction/{userActionId}/reject", produces = "application/json") + ResponseEntity> rejectPecMail(HttpServletRequest request, + @Parameter(description = "The user action id", required = true) @PathVariable("userActionId") Long userActionId, + @Parameter(description = "Motivation for rejection", required = true) @RequestParam("motivation") String motivation); @Operation(summary = "Api to get email log by user action id", responses = { @ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "404", description = "Not Found", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, examples = @ExampleObject(value = diff --git a/src/main/java/net/gepafin/tendermanagement/web/rest/api/impl/PecMailController.java b/src/main/java/net/gepafin/tendermanagement/web/rest/api/impl/PecMailController.java index 41e8ed58..e22be37a 100644 --- a/src/main/java/net/gepafin/tendermanagement/web/rest/api/impl/PecMailController.java +++ b/src/main/java/net/gepafin/tendermanagement/web/rest/api/impl/PecMailController.java @@ -44,6 +44,17 @@ public class PecMailController implements PecMailApi { } + @Override + public ResponseEntity> rejectPecMail(HttpServletRequest request, Long userActionId, String motivation) { + loggingUtil.logUserAction(UserActionRequest.builder().request(request).actionType(UserActionLogsEnum.EMAIL) + .actionContext(UserActionContextEnum.REJECT_PEC_MAIL).build()); + + PecMailResponse pecMailResponse = pecMailService.rejectPecMail(request, userActionId, motivation); + + return ResponseEntity.status(HttpStatus.OK) + .body(new Response<>(pecMailResponse, Status.SUCCESS, Translator.toLocale(GepafinConstant.PEC_EMAIL_REJECTED_SUCCESSFULLY))); + } + @Override public ResponseEntity>> getEmailLogByUserActionId(HttpServletRequest request, Long userActionId) { loggingUtil.logUserAction(UserActionRequest.builder().request(request).actionType(UserActionLogsEnum.EMAIL) diff --git a/src/main/resources/db/changelog/db.changelog-1.0.0.xml b/src/main/resources/db/changelog/db.changelog-1.0.0.xml index 36e1c630..7f005f1f 100644 --- a/src/main/resources/db/changelog/db.changelog-1.0.0.xml +++ b/src/main/resources/db/changelog/db.changelog-1.0.0.xml @@ -3199,4 +3199,10 @@ + + + + + + diff --git a/src/main/resources/message_en.properties b/src/main/resources/message_en.properties index 64331f3d..65b69eff 100644 --- a/src/main/resources/message_en.properties +++ b/src/main/resources/message_en.properties @@ -426,6 +426,7 @@ application.contract.already.exist=Application contract already exist for this a application.not.approved=Application is not approved. subject.body.required=Subject and body is required to create contract. mail.send.successfully=Mail sent succesfully. +pec.email.rejected.successfully=Email rejected successfully. email.log.fetched=Email log fetched successfully. amendment.appropiate.status=Application amendment is not in appropiate status for this operation. upload.company.document.to.application=Uploaded company document to application successfully. diff --git a/src/main/resources/message_it.properties b/src/main/resources/message_it.properties index 5265325f..2437fa0b 100644 --- a/src/main/resources/message_it.properties +++ b/src/main/resources/message_it.properties @@ -417,6 +417,7 @@ application.contract.already.exist=Il contratto di applicazione esiste giďż˝ per application.not.approved=La domanda non ďż˝ stata approvata. subject.body.required=Per creare un contratto sono necessari oggetto e corpo. mail.send.successfully=Email inviata con successo. +pec.email.rejected.successfully=Email rifiutata con successo. email.log.fetched=Registro email recuperato correttamente. amendment.appropiate.status=L'emendamento dell'applicazione non ďż˝ in stato appropriato per questa operazione. upload.company.document.to.application=Documento aziendale caricato correttamente nell'applicazione. From 13301c79c5384f992e28d205544bd333e29cf3fe Mon Sep 17 00:00:00 2001 From: rajesh Date: Wed, 11 Mar 2026 16:20:35 +0530 Subject: [PATCH 25/42] Created new endpoint to update VAT number of company --- .../tendermanagement/dao/CompanyDao.java | 57 ++++++++++++------- .../enums/UserActionContextEnum.java | 1 + .../model/response/CompanyResponse.java | 1 + .../service/CompanyService.java | 2 + .../service/impl/CompanyServiceImpl.java | 8 +++ .../web/rest/api/CompanyApi.java | 13 +++++ .../rest/api/impl/CompanyApiController.java | 9 +++ 7 files changed, 69 insertions(+), 22 deletions(-) diff --git a/src/main/java/net/gepafin/tendermanagement/dao/CompanyDao.java b/src/main/java/net/gepafin/tendermanagement/dao/CompanyDao.java index 92c90f23..400d30f6 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/CompanyDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/CompanyDao.java @@ -229,6 +229,7 @@ public class CompanyDao { response.setUpdatedDate(entity.getUpdatedDate()); response.setContactName(userWithCompanyEntity.getContactName()); response.setContactEmail(userWithCompanyEntity.getContactEmail()); + response.setValidVat(entity.getValidVat()); return response; } @@ -259,28 +260,6 @@ public class CompanyDao { // // } - //allow adding VAT later - if(StringUtils.isNotBlank(companyRequest.getVatNumber()) - && StringUtils.isBlank(companyEntity.getVatNumber())) { - CompanyEntity existingCompany = companyRepository.findByVatNumberAndHubId(companyRequest.getVatNumber(), userEntity.getHub().getId()); - if(existingCompany!=null){ - throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.VATNUMBER_ALREADY_EXISTS)); - } - Boolean validVat=Boolean.FALSE; - if(companyRequest.getVatNumber()!=null){ - VatCheckResponseBean vatCheckResponseBean=vatCheckDao.checkVatNumber(companyRequest.getVatNumber(), userEntity.getHub().getId()); - if(vatCheckResponseBean!=null && Boolean.TRUE.equals(vatCheckResponseBean.getValid())){ - validVat=Boolean.TRUE; - } - } - companyEntity.setVatNumber(companyRequest.getVatNumber()); - companyEntity.setValidVat(validVat); - if(companyRequest.getVatCheckResponse() != null) { - String responseJson = Utils.convertMapIntoJsonString(companyRequest.getVatCheckResponse()); - companyEntity.setJson(responseJson); - updateCodiceAtecoFieldWithNewJson(companyEntity); - } - } companyRepository.save(companyEntity); log.info("Company updated and saved. companyId: {}", companyEntity.getId()); @@ -311,6 +290,40 @@ public class CompanyDao { return convertCompanyEntityToCompanyResponse(companyEntity, userWithCompanyEntity); } + /** + * Updates only the VAT number for the given company: runs VAT check, sets validVat and json (vat check response), and updates codiceAteco from response. + */ + public CompanyResponse updateCompanyVatNumber(UserEntity userEntity, Long companyId, String vatNumber) { + log.info("Updating company VAT number. companyId: {}, userId: {}", companyId, userEntity.getId()); + if (StringUtils.isBlank(vatNumber)) { + throw new CustomValidationException(Status.VALIDATION_ERROR, Translator.toLocale(GepafinConstant.VAT_OR_TAX_CODE_REQUIRED)); + } + CompanyEntity companyEntity = validateCompany(companyId); + Long hubId = userEntity.getHub().getId(); + CompanyEntity existingCompany = companyRepository.findByVatNumberAndHubId(vatNumber, hubId); + if (existingCompany != null && !existingCompany.getId().equals(companyId)) { + throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.VATNUMBER_ALREADY_EXISTS)); + } + VatCheckResponseBean vatCheckResponseBean = vatCheckDao.checkVatNumber(vatNumber, hubId); + Boolean validVat = Boolean.FALSE; + if (vatCheckResponseBean != null && Boolean.TRUE.equals(vatCheckResponseBean.getValid())) { + validVat = Boolean.TRUE; + } + CompanyEntity oldCompanyData = Utils.getClonedEntityForData(companyEntity); + companyEntity.setVatNumber(vatNumber); + companyEntity.setValidVat(validVat); + if (vatCheckResponseBean != null && vatCheckResponseBean.getVatCheckResponse() != null) { + companyEntity.setJson(Utils.convertMapIntoJsonString(vatCheckResponseBean.getVatCheckResponse())); + updateCodiceAtecoFieldWithNewJson(companyEntity); + } + companyRepository.save(companyEntity); + log.info("Company VAT number updated and saved. companyId: {}", companyEntity.getId()); + loggingUtil.addVersionHistory( + VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldCompanyData).newData(companyEntity).build()); + UserWithCompanyEntity userWithCompanyEntity = getUserWithCompany(userEntity.getId(), companyId); + return convertCompanyEntityToCompanyResponse(companyEntity, userWithCompanyEntity); + } + public CompanyEntity validateCompany(Long companyId) { log.info("Validating company. companyId: {}", companyId); return companyRepository.findById(companyId).orElseThrow(() -> new ResourceNotFoundException(Status.NOT_FOUND, diff --git a/src/main/java/net/gepafin/tendermanagement/enums/UserActionContextEnum.java b/src/main/java/net/gepafin/tendermanagement/enums/UserActionContextEnum.java index 8ada4d3c..9dc8b16d 100644 --- a/src/main/java/net/gepafin/tendermanagement/enums/UserActionContextEnum.java +++ b/src/main/java/net/gepafin/tendermanagement/enums/UserActionContextEnum.java @@ -60,6 +60,7 @@ public enum UserActionContextEnum { CREATE_COMPANY("CREATE_COMPANY"), GET_COMPANY("GET_COMPANY"), UPDATE_COMPANY("UPDATE_COMPANY"), + UPDATE_COMPANY_VAT_NUMBER("UPDATE_COMPANY_VAT_NUMBER"), DELETE_COMPANY("DELETE_COMPANY"), UPLOAD_COMPANY_DELEGATION("UPLOAD_COMPANY_DELEGATION"), DOWNLOAD_COMPANY_DELEGATION_TEMPLATE("DOWNLOAD_COMPANY_DELEGATION_TEMPLATE"), diff --git a/src/main/java/net/gepafin/tendermanagement/model/response/CompanyResponse.java b/src/main/java/net/gepafin/tendermanagement/model/response/CompanyResponse.java index ddd62f38..4752eed7 100644 --- a/src/main/java/net/gepafin/tendermanagement/model/response/CompanyResponse.java +++ b/src/main/java/net/gepafin/tendermanagement/model/response/CompanyResponse.java @@ -25,4 +25,5 @@ public class CompanyResponse extends BaseBean{ private String contactName; private String contactEmail; private String codiceAteco; + private Boolean validVat; } diff --git a/src/main/java/net/gepafin/tendermanagement/service/CompanyService.java b/src/main/java/net/gepafin/tendermanagement/service/CompanyService.java index 4c868f1e..6427341f 100644 --- a/src/main/java/net/gepafin/tendermanagement/service/CompanyService.java +++ b/src/main/java/net/gepafin/tendermanagement/service/CompanyService.java @@ -21,6 +21,8 @@ public interface CompanyService { CompanyResponse updateCompany(HttpServletRequest request, Long companyId, CompanyRequest companyRequest); + CompanyResponse updateCompanyVatNumber(HttpServletRequest request, Long companyId, String vatNumber); + CompanyResponse getCompany(HttpServletRequest request, Long companyId); void deleteCompany(HttpServletRequest request, Long companyId); diff --git a/src/main/java/net/gepafin/tendermanagement/service/impl/CompanyServiceImpl.java b/src/main/java/net/gepafin/tendermanagement/service/impl/CompanyServiceImpl.java index f9a8b8c8..ba8779bb 100644 --- a/src/main/java/net/gepafin/tendermanagement/service/impl/CompanyServiceImpl.java +++ b/src/main/java/net/gepafin/tendermanagement/service/impl/CompanyServiceImpl.java @@ -56,6 +56,14 @@ public class CompanyServiceImpl implements CompanyService { validator.validateUserWithCompany(request, companyId); return companyDao.updateCompany(userEntity, companyId, companyRequest); } + + @Override + @Transactional(rollbackFor = Exception.class) + public CompanyResponse updateCompanyVatNumber(HttpServletRequest request, Long companyId, String vatNumber) { + UserEntity userEntity = validator.validateUser(request); + validator.validateUserWithCompany(request, companyId); + return companyDao.updateCompanyVatNumber(userEntity, companyId, vatNumber); + } @Override @Transactional(readOnly = true) diff --git a/src/main/java/net/gepafin/tendermanagement/web/rest/api/CompanyApi.java b/src/main/java/net/gepafin/tendermanagement/web/rest/api/CompanyApi.java index a30e18be..3f566b5d 100644 --- a/src/main/java/net/gepafin/tendermanagement/web/rest/api/CompanyApi.java +++ b/src/main/java/net/gepafin/tendermanagement/web/rest/api/CompanyApi.java @@ -55,6 +55,19 @@ public interface CompanyApi { @Parameter(description = "The company id", required = true) @PathVariable("companyId") Long companyId, @Parameter(description = "Company request object", required = true) @RequestBody CompanyRequest companyRequest); + @Operation(summary = "Api to update only company VAT number", 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) })) }) + @PutMapping(value = "/{companyId}/vatNumber", produces = { "application/json" }) + ResponseEntity> updateCompanyVatNumber(HttpServletRequest request, + @Parameter(description = "The company id", required = true) @PathVariable("companyId") Long companyId, + @Parameter(description = "VAT number", required = true) @RequestParam("vatNumber") String vatNumber); + + @Operation(summary = "Api to delete company", 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) })), diff --git a/src/main/java/net/gepafin/tendermanagement/web/rest/api/impl/CompanyApiController.java b/src/main/java/net/gepafin/tendermanagement/web/rest/api/impl/CompanyApiController.java index 08d34ccf..d798ccfb 100644 --- a/src/main/java/net/gepafin/tendermanagement/web/rest/api/impl/CompanyApiController.java +++ b/src/main/java/net/gepafin/tendermanagement/web/rest/api/impl/CompanyApiController.java @@ -73,6 +73,15 @@ public class CompanyApiController implements CompanyApi{ .body(new Response<>(data, Status.SUCCESS, Translator.toLocale(GepafinConstant.COMPANY_UPDATED_SUCCESS_MSG))); } + @Override + public ResponseEntity> updateCompanyVatNumber(HttpServletRequest request, Long companyId, String vatNumber) { + log.info("Update company VAT number with companyId: {}, vatNumber: {}", companyId, vatNumber); + loggingUtil.logUserAction(UserActionRequest.builder().request(request).actionType(UserActionLogsEnum.UPDATE).actionContext(UserActionContextEnum.UPDATE_COMPANY_VAT_NUMBER).build()); + CompanyResponse data = companyService.updateCompanyVatNumber(request, companyId, vatNumber); + return ResponseEntity.status(HttpStatus.OK) + .body(new Response<>(data, Status.SUCCESS, Translator.toLocale(GepafinConstant.COMPANY_UPDATED_SUCCESS_MSG))); + } + @Override public ResponseEntity> getCompany(HttpServletRequest request, Long companyId) { From 12e6d8af9a8ba67591ca245a29e613299ae73e7a Mon Sep 17 00:00:00 2001 From: rajesh Date: Thu, 12 Mar 2026 16:30:22 +0530 Subject: [PATCH 26/42] Updated the update company API --- .../tendermanagement/dao/CompanyDao.java | 29 +++++++++++++------ 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/src/main/java/net/gepafin/tendermanagement/dao/CompanyDao.java b/src/main/java/net/gepafin/tendermanagement/dao/CompanyDao.java index 400d30f6..dd487692 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/CompanyDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/CompanyDao.java @@ -250,15 +250,26 @@ public class CompanyDao { setIfUpdated(companyEntity::getCountry, companyEntity::setCountry, companyRequest.getCountry()); setIfUpdated(companyEntity::getNumberOfEmployees, companyEntity::setNumberOfEmployees, companyRequest.getNumberOfEmployees()); setIfUpdated(companyEntity::getAnnualRevenue, companyEntity::setAnnualRevenue, companyRequest.getAnnualRevenue()); -// -// if(StringUtils.isNotBlank(companyRequest.getVatNumber())) { -// CompanyEntity existingCompany = companyRepository.findByVatNumberAndHubId(companyRequest.getVatNumber(), userEntity.getHub().getId()); -// if(existingCompany!=null){ -// throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.VATNUMBER_ALREADY_EXISTS)); -// } -// companyEntity.setVatNumber(companyRequest.getVatNumber()); -// -// } + + // Same VAT logic as updateCompanyVatNumber: run VAT check, set validVat, json, and codiceAteco when vatNumber is provided + if (StringUtils.isNotBlank(companyRequest.getVatNumber())) { + Long hubId = userEntity.getHub().getId(); + CompanyEntity existingCompany = companyRepository.findByVatNumberAndHubId(companyRequest.getVatNumber(), hubId); + if (existingCompany != null) { + throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.VATNUMBER_ALREADY_EXISTS)); + } + VatCheckResponseBean vatCheckResponseBean = vatCheckDao.checkVatNumber(companyRequest.getVatNumber(), hubId); + Boolean validVat = Boolean.FALSE; + if (vatCheckResponseBean != null && Boolean.TRUE.equals(vatCheckResponseBean.getValid())) { + validVat = Boolean.TRUE; + } + companyEntity.setVatNumber(companyRequest.getVatNumber()); + companyEntity.setValidVat(validVat); + if (vatCheckResponseBean != null && vatCheckResponseBean.getVatCheckResponse() != null) { + companyEntity.setJson(Utils.convertMapIntoJsonString(vatCheckResponseBean.getVatCheckResponse())); + updateCodiceAtecoFieldWithNewJson(companyEntity); + } + } companyRepository.save(companyEntity); log.info("Company updated and saved. companyId: {}", companyEntity.getId()); From e3b4b04b80825f7901f03924ecde57065971965d Mon Sep 17 00:00:00 2001 From: rajesh Date: Fri, 13 Mar 2026 20:26:04 +0530 Subject: [PATCH 27/42] Handled rejected email rollback cases --- .../dao/ApplicationAmendmentRequestDao.java | 31 ++++- .../tendermanagement/dao/PecMailDao.java | 120 ++++++++++++++++++ .../ApplicationAmendmentRequestEnum.java | 4 +- .../VersionHistoryRepository.java | 2 + 4 files changed, 152 insertions(+), 5 deletions(-) diff --git a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationAmendmentRequestDao.java b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationAmendmentRequestDao.java index 45b867b2..0c12eb8e 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationAmendmentRequestDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationAmendmentRequestDao.java @@ -786,6 +786,10 @@ public class ApplicationAmendmentRequestDao { log.warn("Permission denied: Beneficiary tried to update amendment ID {} with status RESPONSE_RECEIVED", id); throw new CustomValidationException(Status.VALIDATION_ERROR,Translator.toLocale(GepafinConstant.PERMISSION_DENIED)); } + if(Boolean.TRUE.equals(isBeneficiary) && existingApplicationAmendment.getStatus().equals(ApplicationAmendmentRequestEnum.DRAFT.getValue())){ + log.warn("Permission denied: Beneficiary tried to update amendment ID {} with status DRAFT (only instructor can update)", id); + throw new CustomValidationException(Status.VALIDATION_ERROR,Translator.toLocale(GepafinConstant.PERMISSION_DENIED)); + } if(Boolean.FALSE.equals(isBeneficiary) && existingApplicationAmendment.getStatus().equals(ApplicationAmendmentRequestEnum.AWAITING.getValue())){ log.warn("Permission denied: Non-beneficiary tried to update amendment ID {} with status AWAITING", id); throw new CustomValidationException(Status.VALIDATION_ERROR,Translator.toLocale(GepafinConstant.PERMISSION_DENIED)); @@ -1278,16 +1282,35 @@ public class ApplicationAmendmentRequestDao { log.info("Updating application amendment with status: {}", id); ApplicationAmendmentRequestEntity existingApplicationAmendment = validateApplicationAmendmentRequest(id); ApplicationAmendmentRequestEntity oldApplicationAmendmentEntity = Utils.getClonedEntityForData(existingApplicationAmendment); - if (Boolean.TRUE.equals(existingApplicationAmendment.getStatus().equals(ApplicationAmendmentRequestEnum.AWAITING.getValue())) && Boolean.TRUE.equals(statusTypeEnum.equals(ApplicationAmendmentRequestEnum.RESPONSE_RECEIVED))) { + + if (ApplicationAmendmentRequestEnum.AWAITING.equals(statusTypeEnum)) { + // Only instructor can set status to AWAITING (e.g. after finishing edits post email rejection); beneficiary must not be allowed + if (Boolean.TRUE.equals(validator.checkIsBeneficiary()) || Boolean.TRUE.equals(validator.checkIsConfidi())) { + log.warn("Permission denied: Beneficiary/Confidi tried to set amendment ID {} status to AWAITING", id); + throw new CustomValidationException(Status.VALIDATION_ERROR, Translator.toLocale(GepafinConstant.PERMISSION_DENIED)); + } + validator.validatePreInstructor(request, existingApplicationAmendment.getApplicationEvaluationEntity().getUserId()); + // Allow transition to AWAITING only from DRAFT (e.g. after instructor finished edits post email rejection) + if (!ApplicationAmendmentRequestEnum.DRAFT.getValue().equals(existingApplicationAmendment.getStatus())) { + log.warn("Invalid status transition: amendment ID {} is not in DRAFT (current: {})", id, existingApplicationAmendment.getStatus()); + throw new CustomValidationException(Status.VALIDATION_ERROR, Translator.toLocale(GepafinConstant.APPLICATION_AMENDMENT_APPROPIATE_STATUS)); + } + log.info("Updating amendment ID {} status from DRAFT to AWAITING", id); + existingApplicationAmendment.setStatus(ApplicationAmendmentRequestEnum.AWAITING.getValue()); + existingApplicationAmendment.setUpdatedDate(DateTimeUtil.DateServerToUTC(LocalDateTime.now())); + applicationAmendmentRequestRepository.save(existingApplicationAmendment); + loggingUtil.addVersionHistory(VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldApplicationAmendmentEntity).newData(existingApplicationAmendment).build()); + // Send the same mail as when (normal) amendment was created; only normal amendments can be in DRAFT (special amendment emails are sent at creation, not held) + emailNotificationDao.sendMailToNotifyBeneficiaryRegardingNewAmendment(existingApplicationAmendment); + } else if (Boolean.TRUE.equals(existingApplicationAmendment.getStatus().equals(ApplicationAmendmentRequestEnum.AWAITING.getValue())) && Boolean.TRUE.equals(statusTypeEnum.equals(ApplicationAmendmentRequestEnum.RESPONSE_RECEIVED))) { log.info("Updating amendment ID {} status from {} to {}", id, existingApplicationAmendment.getStatus(), statusTypeEnum); existingApplicationAmendment.setStatus(ApplicationAmendmentRequestEnum.RESPONSE_RECEIVED.getValue()); existingApplicationAmendment.setUpdatedDate(DateTimeUtil.DateServerToUTC(LocalDateTime.now())); applicationAmendmentRequestRepository.save(existingApplicationAmendment); - - /** This code is responsible for adding a version history log for the "Update Application Amendment" operation. **/ loggingUtil.addVersionHistory(VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldApplicationAmendmentEntity).newData(existingApplicationAmendment).build()); } - ApplicationAmendmentRequestResponse response = convertEntityToResponse(existingApplicationAmendment,false); + + ApplicationAmendmentRequestResponse response = convertEntityToResponse(existingApplicationAmendment, false); log.info("Amendment status updated successfully: {}", response); return response; } diff --git a/src/main/java/net/gepafin/tendermanagement/dao/PecMailDao.java b/src/main/java/net/gepafin/tendermanagement/dao/PecMailDao.java index fcaecbcc..d25e1507 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/PecMailDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/PecMailDao.java @@ -6,13 +6,22 @@ import lombok.extern.log4j.Log4j2; import net.gepafin.tendermanagement.config.Translator; import net.gepafin.tendermanagement.constants.GepafinConstant; import net.gepafin.tendermanagement.entities.*; +import net.gepafin.tendermanagement.enums.ApplicationEvaluationStatusTypeEnum; +import net.gepafin.tendermanagement.enums.AssignedApplicationEnum; +import net.gepafin.tendermanagement.enums.EmailScenarioTypeEnum; import net.gepafin.tendermanagement.enums.EmailServiceTypeEnum; import net.gepafin.tendermanagement.enums.StatusTypeEnum; import net.gepafin.tendermanagement.model.response.EmailLogResponse; import net.gepafin.tendermanagement.model.response.PecEmailLogResponse; import net.gepafin.tendermanagement.model.response.PecMailResponse; +import net.gepafin.tendermanagement.enums.ApplicationAmendmentRequestEnum; +import net.gepafin.tendermanagement.repositories.ApplicationAmendmentRequestRepository; +import net.gepafin.tendermanagement.repositories.ApplicationEvaluationRepository; +import net.gepafin.tendermanagement.repositories.ApplicationRepository; +import net.gepafin.tendermanagement.repositories.AssignedApplicationsRepository; import net.gepafin.tendermanagement.repositories.EmailLogRepository; import net.gepafin.tendermanagement.repositories.UserActionsRepository; +import net.gepafin.tendermanagement.repositories.VersionHistoryRepository; import net.gepafin.tendermanagement.service.ApplicationService; import net.gepafin.tendermanagement.service.CallService; import net.gepafin.tendermanagement.util.DateTimeUtil; @@ -28,6 +37,8 @@ import org.springframework.stereotype.Component; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; +import java.util.Map; +import java.util.Optional; @Component @Log4j2 @@ -51,6 +62,22 @@ public class PecMailDao { @Autowired private ApplicationService applicationService; + @Autowired + private ApplicationRepository applicationRepository; + + @Autowired + private ApplicationEvaluationRepository applicationEvaluationRepository; + + @Autowired + private AssignedApplicationsRepository assignedApplicationsRepository; + + @Autowired + private VersionHistoryRepository versionHistoryRepository; + + @Autowired + private ApplicationAmendmentRequestRepository applicationAmendmentRequestRepository; + + private static final String TABLE_APPLICATION = "APPLICATION"; public List sendPecMail(HttpServletRequest request, List userActionIds) { @@ -86,11 +113,104 @@ public class PecMailDao { } emailLogRepository.saveAll(emailLogs); EmailLogEntity firstLog = emailLogs.get(0); + + rollbackDomainChangesForRejectedEmail(firstLog, userActionId); + ApplicationEntity applicationEntity = applicationService.validateApplication(firstLog.getApplicationId()); String callName = applicationEntity.getCall().getName(); return createPecMailResponse(firstLog.getUserAction().getId(), firstLog, callName); } + /** + * Rolls back domain changes using version history (audit) for the user action that triggered the email. + * Restores application, application_evaluation, and assigned_applications from oldData in version history. + */ + private void rollbackDomainChangesForRejectedEmail(EmailLogEntity firstLog, Long userActionId) { + if (firstLog == null || firstLog.getApplicationId() == null || firstLog.getEmailType() == null || userActionId == null) { + return; + } + + String scenario = firstLog.getEmailType(); + Long applicationId = firstLog.getApplicationId(); + + // Only normal amendment emails are rejectable in PEC flow; set amendment to DRAFT so only instructor can update + if (EmailScenarioTypeEnum.APPLICATION_AMENDMENT_REQUESTED.getValue().equals(scenario)) { + Long amendmentId = firstLog.getAmendmentId(); + if (amendmentId != null) { + applicationAmendmentRequestRepository.findByIdAndIsDeletedFalse(amendmentId).ifPresent(amendment -> { + amendment.setStatus(ApplicationAmendmentRequestEnum.DRAFT.getValue()); + applicationAmendmentRequestRepository.save(amendment); + log.info("Set amendment id={} to DRAFT after email rejected (userActionId={})", amendmentId, userActionId); + }); + } + return; + } + + // Only rollback for the three evaluation-outcome email scenarios + if (!EmailScenarioTypeEnum.APPLICATION_TECHNICAL_EVALUATION_REJECTED.getValue().equals(scenario) + && !EmailScenarioTypeEnum.APPLICATION_REJECTED.getValue().equals(scenario) + && !EmailScenarioTypeEnum.APPLICATION_ADMISSIBLE.getValue().equals(scenario)) { + return; + } + + List appHistory = versionHistoryRepository.findVersionHistoryByUserActionIdAndTableName(userActionId, TABLE_APPLICATION); + Optional applicationVersion = appHistory.stream() + .filter(v -> applicationId.equals(v.getRecordId()) && v.getOldData() != null && !v.getOldData().isEmpty()) + .findFirst(); + + ApplicationEntity applicationEntity = applicationService.validateApplication(applicationId); + if (applicationVersion.isPresent()) { + Map oldDataMap = Utils.convertJsonStringToMap(applicationVersion.get().getOldData()); + if (oldDataMap != null) { + String previousStatus = Utils.extractString(oldDataMap, "status"); + if (previousStatus != null) { + applicationEntity.setStatus(previousStatus); + } + Object dateRejectedObj = oldDataMap.get("dateRejected"); + applicationEntity.setDateRejected(parseLocalDateTimeFromAudit(dateRejectedObj)); + applicationRepository.save(applicationEntity); + log.info("Rolled back application id={} from version history (userActionId={})", applicationId, userActionId); + } + } else { + log.warn("No APPLICATION version history with oldData found for userActionId={}, applicationId={}; skipping application rollback", userActionId, applicationId); + } + + // Only set application_evaluation and assigned_applications to OPEN for REJECTED and TECHNICAL_EVALUATION_REJECTED (not for ADMISSIBLE) + boolean reopenEvaluationAndAssigned = EmailScenarioTypeEnum.APPLICATION_TECHNICAL_EVALUATION_REJECTED.getValue().equals(scenario) + || EmailScenarioTypeEnum.APPLICATION_REJECTED.getValue().equals(scenario); + if (!reopenEvaluationAndAssigned) { + return; + } + + applicationEvaluationRepository.findByApplicationIdAndIsDeletedFalse(applicationId).ifPresent(evaluation -> { + evaluation.setStatus(ApplicationEvaluationStatusTypeEnum.OPEN.getValue()); + evaluation.setClosingDate(null); + evaluation.setActiveDays(null); + applicationEvaluationRepository.save(evaluation); + log.info("Set application_evaluation id={} to OPEN (userActionId={})", evaluation.getId(), userActionId); + }); + + assignedApplicationsRepository.findByApplicationIdAndIsDeletedFalse(applicationId).ifPresent(assigned -> { + assigned.setStatus(AssignedApplicationEnum.OPEN.getValue()); + assignedApplicationsRepository.save(assigned); + log.info("Set assigned_applications id={} to OPEN (userActionId={})", assigned.getId(), userActionId); + }); + } + + private static LocalDateTime parseLocalDateTimeFromAudit(Object value) { + if (value == null) { + return null; + } + if (value instanceof String str && !str.isEmpty()) { + try { + return DateTimeUtil.parseStringToLocalDateTime(str); + } catch (Exception e) { + return null; + } + } + return null; + } + private List getEmailLogEntities(HttpServletRequest request, Long userActionId) { UserActionEntity userActionEntity = userActionsRepository.findUserActionByIdAndIsDeletedFalse(userActionId); if (userActionEntity == null) { diff --git a/src/main/java/net/gepafin/tendermanagement/enums/ApplicationAmendmentRequestEnum.java b/src/main/java/net/gepafin/tendermanagement/enums/ApplicationAmendmentRequestEnum.java index d25ef71d..eeee1100 100644 --- a/src/main/java/net/gepafin/tendermanagement/enums/ApplicationAmendmentRequestEnum.java +++ b/src/main/java/net/gepafin/tendermanagement/enums/ApplicationAmendmentRequestEnum.java @@ -6,7 +6,9 @@ public enum ApplicationAmendmentRequestEnum { AWAITING("AWAITING"), RESPONSE_RECEIVED("RESPONSE_RECEIVED"), CLOSE("CLOSE"), - EXPIRED("EXPIRED"); + EXPIRED("EXPIRED"), + /** Amendment PEC email was rejected; only instructor can update until status is set back to AWAITING. */ + DRAFT("DRAFT"); private String value; diff --git a/src/main/java/net/gepafin/tendermanagement/repositories/VersionHistoryRepository.java b/src/main/java/net/gepafin/tendermanagement/repositories/VersionHistoryRepository.java index 853bb945..782a2cc6 100644 --- a/src/main/java/net/gepafin/tendermanagement/repositories/VersionHistoryRepository.java +++ b/src/main/java/net/gepafin/tendermanagement/repositories/VersionHistoryRepository.java @@ -13,4 +13,6 @@ public interface VersionHistoryRepository extends JpaRepository findVersionHistoryByUserActionIdAndUserIdNull(Long id); List findVersionHistoryByUserActionId(Long id); + + List findVersionHistoryByUserActionIdAndTableName(Long userActionId, String tableName); } From 9b21791f9fbc746dc1e96f0590a9d30ba6d3dcd9 Mon Sep 17 00:00:00 2001 From: rajesh Date: Mon, 16 Mar 2026 16:38:53 +0530 Subject: [PATCH 28/42] Updated amendment rejection email flow and handled for special amendment --- .../dao/ApplicationAmendmentRequestDao.java | 25 +-------- .../dao/EmailNotificationDao.java | 4 +- .../tendermanagement/dao/PecMailDao.java | 54 +++++++++++++++---- .../ApplicationAmendmentRequestEnum.java | 4 +- .../db/changelog/db.changelog-1.0.0.xml | 4 ++ ..._template_special_amendment_16_03_2026.sql | 7 +++ 6 files changed, 62 insertions(+), 36 deletions(-) create mode 100644 src/main/resources/db/dump/update_system_email_template_special_amendment_16_03_2026.sql diff --git a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationAmendmentRequestDao.java b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationAmendmentRequestDao.java index 0c12eb8e..33e70a17 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationAmendmentRequestDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationAmendmentRequestDao.java @@ -786,10 +786,6 @@ public class ApplicationAmendmentRequestDao { log.warn("Permission denied: Beneficiary tried to update amendment ID {} with status RESPONSE_RECEIVED", id); throw new CustomValidationException(Status.VALIDATION_ERROR,Translator.toLocale(GepafinConstant.PERMISSION_DENIED)); } - if(Boolean.TRUE.equals(isBeneficiary) && existingApplicationAmendment.getStatus().equals(ApplicationAmendmentRequestEnum.DRAFT.getValue())){ - log.warn("Permission denied: Beneficiary tried to update amendment ID {} with status DRAFT (only instructor can update)", id); - throw new CustomValidationException(Status.VALIDATION_ERROR,Translator.toLocale(GepafinConstant.PERMISSION_DENIED)); - } if(Boolean.FALSE.equals(isBeneficiary) && existingApplicationAmendment.getStatus().equals(ApplicationAmendmentRequestEnum.AWAITING.getValue())){ log.warn("Permission denied: Non-beneficiary tried to update amendment ID {} with status AWAITING", id); throw new CustomValidationException(Status.VALIDATION_ERROR,Translator.toLocale(GepafinConstant.PERMISSION_DENIED)); @@ -1283,26 +1279,7 @@ public class ApplicationAmendmentRequestDao { ApplicationAmendmentRequestEntity existingApplicationAmendment = validateApplicationAmendmentRequest(id); ApplicationAmendmentRequestEntity oldApplicationAmendmentEntity = Utils.getClonedEntityForData(existingApplicationAmendment); - if (ApplicationAmendmentRequestEnum.AWAITING.equals(statusTypeEnum)) { - // Only instructor can set status to AWAITING (e.g. after finishing edits post email rejection); beneficiary must not be allowed - if (Boolean.TRUE.equals(validator.checkIsBeneficiary()) || Boolean.TRUE.equals(validator.checkIsConfidi())) { - log.warn("Permission denied: Beneficiary/Confidi tried to set amendment ID {} status to AWAITING", id); - throw new CustomValidationException(Status.VALIDATION_ERROR, Translator.toLocale(GepafinConstant.PERMISSION_DENIED)); - } - validator.validatePreInstructor(request, existingApplicationAmendment.getApplicationEvaluationEntity().getUserId()); - // Allow transition to AWAITING only from DRAFT (e.g. after instructor finished edits post email rejection) - if (!ApplicationAmendmentRequestEnum.DRAFT.getValue().equals(existingApplicationAmendment.getStatus())) { - log.warn("Invalid status transition: amendment ID {} is not in DRAFT (current: {})", id, existingApplicationAmendment.getStatus()); - throw new CustomValidationException(Status.VALIDATION_ERROR, Translator.toLocale(GepafinConstant.APPLICATION_AMENDMENT_APPROPIATE_STATUS)); - } - log.info("Updating amendment ID {} status from DRAFT to AWAITING", id); - existingApplicationAmendment.setStatus(ApplicationAmendmentRequestEnum.AWAITING.getValue()); - existingApplicationAmendment.setUpdatedDate(DateTimeUtil.DateServerToUTC(LocalDateTime.now())); - applicationAmendmentRequestRepository.save(existingApplicationAmendment); - loggingUtil.addVersionHistory(VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldApplicationAmendmentEntity).newData(existingApplicationAmendment).build()); - // Send the same mail as when (normal) amendment was created; only normal amendments can be in DRAFT (special amendment emails are sent at creation, not held) - emailNotificationDao.sendMailToNotifyBeneficiaryRegardingNewAmendment(existingApplicationAmendment); - } else if (Boolean.TRUE.equals(existingApplicationAmendment.getStatus().equals(ApplicationAmendmentRequestEnum.AWAITING.getValue())) && Boolean.TRUE.equals(statusTypeEnum.equals(ApplicationAmendmentRequestEnum.RESPONSE_RECEIVED))) { + if (Boolean.TRUE.equals(existingApplicationAmendment.getStatus().equals(ApplicationAmendmentRequestEnum.AWAITING.getValue())) && Boolean.TRUE.equals(statusTypeEnum.equals(ApplicationAmendmentRequestEnum.RESPONSE_RECEIVED))) { log.info("Updating amendment ID {} status from {} to {}", id, existingApplicationAmendment.getStatus(), statusTypeEnum); existingApplicationAmendment.setStatus(ApplicationAmendmentRequestEnum.RESPONSE_RECEIVED.getValue()); existingApplicationAmendment.setUpdatedDate(DateTimeUtil.DateServerToUTC(LocalDateTime.now())); diff --git a/src/main/java/net/gepafin/tendermanagement/dao/EmailNotificationDao.java b/src/main/java/net/gepafin/tendermanagement/dao/EmailNotificationDao.java index 7cfb73cc..60d867bc 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/EmailNotificationDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/EmailNotificationDao.java @@ -427,7 +427,9 @@ public class EmailNotificationDao { if(Boolean.TRUE.equals(emailLogEntity.getEmailType().equals(EmailScenarioTypeEnum.APPLICATION_TECHNICAL_EVALUATION_REJECTED.getValue())) || Boolean.TRUE.equals(emailLogEntity.getEmailType().equals(EmailScenarioTypeEnum.APPLICATION_ADMISSIBLE.getValue())) || Boolean.TRUE.equals(emailLogEntity.getEmailType().equals(EmailScenarioTypeEnum.APPLICATION_REJECTED.getValue())) - || Boolean.TRUE.equals(emailLogEntity.getEmailType().equals(EmailScenarioTypeEnum.APPLICATION_AMENDMENT_REQUESTED.getValue()))) { + || Boolean.TRUE.equals(emailLogEntity.getEmailType().equals(EmailScenarioTypeEnum.APPLICATION_AMENDMENT_REQUESTED.getValue())) + || Boolean.TRUE.equals(emailLogEntity.getEmailType().equals(EmailScenarioTypeEnum.SPECIAL_APPLICATION_AMENDMENT_REQUESTED.getValue())) + || Boolean.TRUE.equals(emailLogEntity.getEmailType().equals(EmailScenarioTypeEnum.SPECIAL_APPLICATION_AMENDMENT_REQUESTED_BLUE_TONGUE.getValue()))) { isSendEmail = Boolean.FALSE; } } diff --git a/src/main/java/net/gepafin/tendermanagement/dao/PecMailDao.java b/src/main/java/net/gepafin/tendermanagement/dao/PecMailDao.java index d25e1507..73a352c6 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/PecMailDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/PecMailDao.java @@ -7,6 +7,7 @@ import net.gepafin.tendermanagement.config.Translator; import net.gepafin.tendermanagement.constants.GepafinConstant; import net.gepafin.tendermanagement.entities.*; import net.gepafin.tendermanagement.enums.ApplicationEvaluationStatusTypeEnum; +import net.gepafin.tendermanagement.enums.ApplicationStatusTypeEnum; import net.gepafin.tendermanagement.enums.AssignedApplicationEnum; import net.gepafin.tendermanagement.enums.EmailScenarioTypeEnum; import net.gepafin.tendermanagement.enums.EmailServiceTypeEnum; @@ -133,16 +134,39 @@ public class PecMailDao { String scenario = firstLog.getEmailType(); Long applicationId = firstLog.getApplicationId(); - // Only normal amendment emails are rejectable in PEC flow; set amendment to DRAFT so only instructor can update - if (EmailScenarioTypeEnum.APPLICATION_AMENDMENT_REQUESTED.getValue().equals(scenario)) { - Long amendmentId = firstLog.getAmendmentId(); - if (amendmentId != null) { - applicationAmendmentRequestRepository.findByIdAndIsDeletedFalse(amendmentId).ifPresent(amendment -> { - amendment.setStatus(ApplicationAmendmentRequestEnum.DRAFT.getValue()); - applicationAmendmentRequestRepository.save(amendment); - log.info("Set amendment id={} to DRAFT after email rejected (userActionId={})", amendmentId, userActionId); - }); + // Amendment email rejected (normal or special): revert application, then mark amendment REJECTED and soft-deleted + boolean isNormalAmendment = EmailScenarioTypeEnum.APPLICATION_AMENDMENT_REQUESTED.getValue().equals(scenario); + boolean isSpecialAmendment = EmailScenarioTypeEnum.SPECIAL_APPLICATION_AMENDMENT_REQUESTED.getValue().equals(scenario) + || EmailScenarioTypeEnum.SPECIAL_APPLICATION_AMENDMENT_REQUESTED_BLUE_TONGUE.getValue().equals(scenario); + + if (isNormalAmendment || isSpecialAmendment) { + ApplicationEntity applicationEntity = applicationService.validateApplication(applicationId); + if (isNormalAmendment) { + List appHistory = versionHistoryRepository.findVersionHistoryByUserActionIdAndTableName(userActionId, TABLE_APPLICATION); + Optional applicationVersion = appHistory.stream() + .filter(v -> applicationId.equals(v.getRecordId()) && v.getOldData() != null && !v.getOldData().isEmpty()) + .findFirst(); + if (applicationVersion.isPresent()) { + Map oldDataMap = Utils.convertJsonStringToMap(applicationVersion.get().getOldData()); + if (oldDataMap != null) { + String previousStatus = Utils.extractString(oldDataMap, "status"); + if (previousStatus != null) { + applicationEntity.setStatus(previousStatus); + } + applicationRepository.save(applicationEntity); + log.info("Rolled back application id={} from version history after amendment email rejected (userActionId={})", applicationId, userActionId); + } + } else { + log.warn("No APPLICATION version history with oldData found for userActionId={}, applicationId={}; skipping application rollback", userActionId, applicationId); + } + } else { + applicationEntity.setStatus(ApplicationStatusTypeEnum.ADMISSIBLE.getValue()); + applicationEntity.setDateRejected(null); + applicationRepository.save(applicationEntity); + log.info("Set application id={} to ADMISSIBLE after special amendment email rejected (userActionId={})", applicationId, userActionId); } + + markAmendmentRejectedAndDeleted(firstLog.getAmendmentId(), userActionId); return; } @@ -197,6 +221,18 @@ public class PecMailDao { }); } + private void markAmendmentRejectedAndDeleted(Long amendmentId, Long userActionId) { + if (amendmentId == null) { + return; + } + applicationAmendmentRequestRepository.findByIdAndIsDeletedFalse(amendmentId).ifPresent(amendment -> { + amendment.setStatus(ApplicationAmendmentRequestEnum.REJECTED.getValue()); + amendment.setIsDeleted(true); + applicationAmendmentRequestRepository.save(amendment); + log.info("Set amendment id={} to REJECTED and is_deleted=true after amendment email rejected (userActionId={})", amendmentId, userActionId); + }); + } + private static LocalDateTime parseLocalDateTimeFromAudit(Object value) { if (value == null) { return null; diff --git a/src/main/java/net/gepafin/tendermanagement/enums/ApplicationAmendmentRequestEnum.java b/src/main/java/net/gepafin/tendermanagement/enums/ApplicationAmendmentRequestEnum.java index eeee1100..13a6d8c4 100644 --- a/src/main/java/net/gepafin/tendermanagement/enums/ApplicationAmendmentRequestEnum.java +++ b/src/main/java/net/gepafin/tendermanagement/enums/ApplicationAmendmentRequestEnum.java @@ -7,8 +7,8 @@ public enum ApplicationAmendmentRequestEnum { RESPONSE_RECEIVED("RESPONSE_RECEIVED"), CLOSE("CLOSE"), EXPIRED("EXPIRED"), - /** Amendment PEC email was rejected; only instructor can update until status is set back to AWAITING. */ - DRAFT("DRAFT"); + /** Special amendment PEC email was rejected; amendment status set to REJECTED. */ + REJECTED("REJECTED"); private String value; diff --git a/src/main/resources/db/changelog/db.changelog-1.0.0.xml b/src/main/resources/db/changelog/db.changelog-1.0.0.xml index 7f005f1f..8ce255b6 100644 --- a/src/main/resources/db/changelog/db.changelog-1.0.0.xml +++ b/src/main/resources/db/changelog/db.changelog-1.0.0.xml @@ -3205,4 +3205,8 @@ + + + + diff --git a/src/main/resources/db/dump/update_system_email_template_special_amendment_16_03_2026.sql b/src/main/resources/db/dump/update_system_email_template_special_amendment_16_03_2026.sql new file mode 100644 index 00000000..03cd457d --- /dev/null +++ b/src/main/resources/db/dump/update_system_email_template_special_amendment_16_03_2026.sql @@ -0,0 +1,7 @@ +UPDATE gepafin_schema.system_email_template +SET subject='Comunicazione esito valutazione tecnica ed economico-finanziaria– Avviso {{call_name}} ' +WHERE email_scenario='SPECIAL_APPLICATION_AMENDMENT_REQUESTED'; + +UPDATE gepafin_schema.system_email_template +SET subject='Comunicazione esito valutazione tecnica ed economico-finanziaria– Avviso {{call_name}} ' +WHERE email_scenario='SPECIAL_APPLICATION_AMENDMENT_REQUESTED_BLUE_TONGUE'; \ No newline at end of file From 0c23722c75c055b461f12772a2116dbe46b32959 Mon Sep 17 00:00:00 2001 From: rajesh Date: Tue, 17 Mar 2026 15:12:48 +0530 Subject: [PATCH 29/42] Updated flow for amendment start date --- .../constants/GepafinConstant.java | 1 + .../dao/ApplicationAmendmentRequestDao.java | 89 ++++++++++++++----- .../tendermanagement/dao/PecMailDao.java | 10 +++ .../ApplicationAmendmentRequestEnum.java | 3 + src/main/resources/message_en.properties | 1 + src/main/resources/message_it.properties | 1 + 6 files changed, 82 insertions(+), 23 deletions(-) diff --git a/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java b/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java index f223d922..cf39a573 100644 --- a/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java +++ b/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java @@ -637,6 +637,7 @@ public class GepafinConstant { public static final String PEC_EMAIL_REJECTED_SUCCESSFULLY="pec.email.rejected.successfully"; public static final String EMAIL_LOG_FETCHED="email.log.fetched"; public static final String APPLICATION_AMENDMENT_APPROPIATE_STATUS="amendment.appropiate.status"; + public static final String AMENDMENT_MUST_BE_APPROVED_FIRST_MSG = "amendment.must.be.approved.first"; public static final String UPLOAD_COMPANY_DOCUMENT_TO_APPLICATION_MSG="upload.company.document.to.application"; public static final String COMPANY_DOCUMENT_NOT_FOUND_WITH_IDS="company.document.not.found.with.ids"; public static final String REQUIRED_AMOUNT_FIELD_NOT_PROVIDED = "amount.field.not.provided"; diff --git a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationAmendmentRequestDao.java b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationAmendmentRequestDao.java index 33e70a17..ae26a2a1 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationAmendmentRequestDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationAmendmentRequestDao.java @@ -335,12 +335,12 @@ public class ApplicationAmendmentRequestDao { log.warn("Invalid responseDays received: {}", applicationAmendmentRequest.getResponseDays()); throw new CustomValidationException(Status.BAD_REQUEST,Translator.toLocale(GepafinConstant.RESPONSE_DAYS_NOT_NULL)); } - applicationAmendmentRequestEntity.setEndDate(DateTimeUtil.DateServerToUTC(LocalDateTime.now()).plusDays(applicationAmendmentRequest.getResponseDays())); - applicationAmendmentRequestEntity.setIsEmail(applicationAmendmentRequest.getIsSendEmail()); applicationAmendmentRequestEntity.setIsNotification(applicationAmendmentRequest.getIsSendNotification()); - applicationAmendmentRequestEntity.setStartDate(DateTimeUtil.DateServerToUTC(LocalDateTime.now())); - applicationAmendmentRequestEntity.setStatus(ApplicationAmendmentRequestEnum.AWAITING.getValue()); + // startDate and endDate set to null until director approves (sends PEC); status DRAFT until then + applicationAmendmentRequestEntity.setStartDate(null); + applicationAmendmentRequestEntity.setEndDate(null); + applicationAmendmentRequestEntity.setStatus(ApplicationAmendmentRequestEnum.DRAFT.getValue()); applicationAmendmentRequestEntity.setType(ApplicationAmendmentRequestTypeEnum.REGULAR.getValue()); ApplicationEvaluationEntity applicationEvaluationEntity = applicationEvaluationService.validateApplicationEvaluation(applicationEvaluationId); //cloned for old data entity @@ -367,20 +367,7 @@ public class ApplicationAmendmentRequestDao { applicationAmendmentRequestEntity.setFormFields(formFieldsJson); } List amendmentRequest = applicationAmendmentRequestRepository.findAllByApplicationEvaluationIdAndIsDeletedFalse(applicationEvaluationEntity.getId()); - // Ensure startDate and initialDays are not null to avoid NullPointerException - if (amendmentRequest !=null && amendmentRequest.isEmpty()) { - if (applicationEvaluationEntity.getStartDate() != null && applicationEvaluationEntity.getInitialDays() != null) { - Long initialDays = applicationEvaluationEntity.getInitialDays(); - LocalDateTime startDate = applicationEvaluationEntity.getStartDate(); - LocalDateTime nowInUTC = DateTimeUtil.DateServerToUTC(LocalDateTime.now()); - // Calculate remaining days - Long remainingDays = initialDays - DAYS.between(startDate, nowInUTC); - // Set remaining days in the entity - applicationEvaluationEntity.setRemainingDays(remainingDays); - //Set stop date time in the entity becuase amendment has started - applicationEvaluationEntity.setStopDateTime(DateTimeUtil.DateServerToUTC(LocalDateTime.now())); - } - } + // remainingDays/stopDateTime are set when director approves (see approveAmendment) boolean noneClosedOrExpired = amendmentRequest.stream() .noneMatch(amendment -> @@ -1221,7 +1208,8 @@ public class ApplicationAmendmentRequestDao { log.info("Extending response days for Application Amendment ID: {}, Additional Days: {}", id, newResponseDays); ApplicationAmendmentRequestEntity applicationAmendmentRequestEntity = validateApplicationAmendmentRequest(id); if(Boolean.TRUE.equals(applicationAmendmentRequestEntity.getStatus().equals(ApplicationAmendmentRequestEnum.CLOSE.getValue())) - || Boolean.TRUE.equals(applicationAmendmentRequestEntity.getStatus().equals(ApplicationAmendmentRequestEnum.AWAITING.getValue()))) { + || Boolean.TRUE.equals(applicationAmendmentRequestEntity.getStatus().equals(ApplicationAmendmentRequestEnum.AWAITING.getValue())) + || Boolean.TRUE.equals(applicationAmendmentRequestEntity.getStatus().equals(ApplicationAmendmentRequestEnum.DRAFT.getValue()))) { throw new CustomValidationException(Status.VALIDATION_ERROR,Translator.toLocale(GepafinConstant.APPLICATION_AMENDMENT_APPROPIATE_STATUS)); } log.info("Extending response days for Application Amendment ID: {}, Additional Days: {}", id, newResponseDays); @@ -1280,6 +1268,9 @@ public class ApplicationAmendmentRequestDao { ApplicationAmendmentRequestEntity oldApplicationAmendmentEntity = Utils.getClonedEntityForData(existingApplicationAmendment); if (Boolean.TRUE.equals(existingApplicationAmendment.getStatus().equals(ApplicationAmendmentRequestEnum.AWAITING.getValue())) && Boolean.TRUE.equals(statusTypeEnum.equals(ApplicationAmendmentRequestEnum.RESPONSE_RECEIVED))) { + if (existingApplicationAmendment.getStartDate() == null) { + throw new CustomValidationException(Status.VALIDATION_ERROR, Translator.toLocale(GepafinConstant.AMENDMENT_MUST_BE_APPROVED_FIRST_MSG)); + } log.info("Updating amendment ID {} status from {} to {}", id, existingApplicationAmendment.getStatus(), statusTypeEnum); existingApplicationAmendment.setStatus(ApplicationAmendmentRequestEnum.RESPONSE_RECEIVED.getValue()); existingApplicationAmendment.setUpdatedDate(DateTimeUtil.DateServerToUTC(LocalDateTime.now())); @@ -1292,12 +1283,63 @@ public class ApplicationAmendmentRequestDao { return response; } + /** + * Sets amendment startDate, endDate and status AWAITING when director sends PEC mail (approval). + * Called from PecMailDao after PEC send succeeds; amendment id comes from email log (user action). + * No-op if amendment not found, not DRAFT (or already has startDate set), or startDate already set. + */ + public void setAmendmentStartAndEndDateOnPecSent(Long amendmentId) { + if (amendmentId == null) { + return; + } + Optional optional = applicationAmendmentRequestRepository.findByIdAndIsDeletedFalse(amendmentId); + if (optional.isEmpty()) { + return; + } + ApplicationAmendmentRequestEntity amendment = optional.get(); + boolean isDraft = ApplicationAmendmentRequestEnum.DRAFT.getValue().equals(amendment.getStatus()); + boolean isAwaitingWithNoStart = ApplicationAmendmentRequestEnum.AWAITING.getValue().equals(amendment.getStatus()) && amendment.getStartDate() == null; + if ((!isDraft && !isAwaitingWithNoStart) || amendment.getStartDate() != null) { + return; + } + log.info("Setting amendment ID {} startDate/endDate and status AWAITING after PEC mail sent.", amendmentId); + Long responseDays = amendment.getResponseDays() != null ? amendment.getResponseDays() : 0L; + LocalDateTime nowUtc = DateTimeUtil.DateServerToUTC(LocalDateTime.now()); + ApplicationAmendmentRequestEntity oldEntity = Utils.getClonedEntityForData(amendment); + amendment.setStartDate(nowUtc); + amendment.setEndDate(nowUtc.plusDays(responseDays)); + amendment.setStatus(ApplicationAmendmentRequestEnum.AWAITING.getValue()); + amendment.setUpdatedDate(nowUtc); + + ApplicationEvaluationEntity evaluation = amendment.getApplicationEvaluationEntity(); + List allForEvaluation = applicationAmendmentRequestRepository.findAllByApplicationEvaluationIdAndIsDeletedFalse(evaluation.getId()); + boolean isFirstApprovedAmendment = allForEvaluation.stream() + .filter(a -> !a.getId().equals(amendment.getId())) + .noneMatch(a -> a.getStartDate() != null); + + if (isFirstApprovedAmendment && evaluation.getStartDate() != null && evaluation.getInitialDays() != null) { + Long initialDays = evaluation.getInitialDays(); + LocalDateTime evalStartDate = evaluation.getStartDate(); + Long remainingDays = initialDays - DAYS.between(evalStartDate, nowUtc); + evaluation.setRemainingDays(remainingDays); + evaluation.setStopDateTime(nowUtc); + applicationEvaluationRepository.save(evaluation); + } + + applicationAmendmentRequestRepository.save(amendment); + loggingUtil.addVersionHistory(VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldEntity).newData(amendment).build()); + log.info("Amendment ID {} startDate and endDate set after PEC sent.", amendmentId); + } + public EmailReminderResponse sendReminderEmail(Long amendmentId) { log.info("Initiating reminder email process for Amendment ID: {}", amendmentId); ApplicationAmendmentRequestEntity amendment = applicationAmendmentRequestRepository.findByIdAndIsDeletedFalse(amendmentId) .orElseThrow(() -> { log.error("Amendment not found with ID: {}", amendmentId); return new ResourceNotFoundException(Status.NOT_FOUND, Translator.toLocale(GepafinConstant.APPLICATION_AMENDMENT_NOT_FOUND_MSG)); }); + if (!ApplicationAmendmentRequestEnum.AWAITING.getValue().equals(amendment.getStatus())) { + throw new CustomValidationException(Status.VALIDATION_ERROR, Translator.toLocale(GepafinConstant.APPLICATION_AMENDMENT_APPROPIATE_STATUS)); + } Optional entityOptional = applicationEvaluationRepository.findByIdAndIsDeletedFalse(amendment.getApplicationEvaluationEntity().getId()); EmailReminderResponse emailReminderResponse = new EmailReminderResponse(); @@ -1875,11 +1917,12 @@ public class ApplicationAmendmentRequestDao { else { applicationAmendmentRequestEntity.setResponseDays(10l); } - applicationAmendmentRequestEntity.setEndDate(DateTimeUtil.DateServerToUTC(LocalDateTime.now()).plusDays(applicationAmendmentRequestEntity.getResponseDays())); + // startDate and endDate set when director approves (see approveAmendment) + applicationAmendmentRequestEntity.setEndDate(null); applicationAmendmentRequestEntity.setIsEmail(Boolean.TRUE); applicationAmendmentRequestEntity.setIsNotification(Boolean.FALSE); - applicationAmendmentRequestEntity.setStartDate(DateTimeUtil.DateServerToUTC(LocalDateTime.now())); - applicationAmendmentRequestEntity.setStatus(ApplicationAmendmentRequestEnum.AWAITING.getValue()); + applicationAmendmentRequestEntity.setStartDate(null); + applicationAmendmentRequestEntity.setStatus(ApplicationAmendmentRequestEnum.DRAFT.getValue()); // if ( applicationEvaluationEntity.getStartDate() != null && applicationEvaluationEntity.getInitialDays() != null ) { // Long initialDays = applicationEvaluationEntity.getInitialDays(); // LocalDateTime startDate = applicationEvaluationEntity.getStartDate(); @@ -1938,7 +1981,7 @@ public class ApplicationAmendmentRequestDao { ApplicationAmendmentRequestResponse applicationAmendmentRequestResponse = convertEntityToResponse(applicationAmendmentRequestEntity,false); if (!Boolean.TRUE.equals(emailSendResponse.getIsEmailSend())){ - log.info("Sending mail for the special amendment for amendment ID = {}", applicationAmendment.getId()); +// log.info("Sending mail for the special amendment for amendment ID = {}", applicationAmendment.getId()); saveEmailSendResponse(emailSendResponse, applicationAmendmentRequestEntity); applicationAmendmentRequestResponse.setEmailSendResponse(responses); } diff --git a/src/main/java/net/gepafin/tendermanagement/dao/PecMailDao.java b/src/main/java/net/gepafin/tendermanagement/dao/PecMailDao.java index 73a352c6..4bc3abe9 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/PecMailDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/PecMailDao.java @@ -78,6 +78,9 @@ public class PecMailDao { @Autowired private ApplicationAmendmentRequestRepository applicationAmendmentRequestRepository; + @Autowired + private ApplicationAmendmentRequestDao applicationAmendmentRequestDao; + private static final String TABLE_APPLICATION = "APPLICATION"; public List sendPecMail(HttpServletRequest request, List userActionIds) { @@ -95,6 +98,13 @@ public class PecMailDao { recipients, log ); + // When PEC send succeeded and this is an amendment email (regular or special), set amendment startDate/endDate and status AWAITING + boolean isAmendmentEmail = EmailScenarioTypeEnum.APPLICATION_AMENDMENT_REQUESTED.getValue().equals(log.getEmailType()) + || EmailScenarioTypeEnum.SPECIAL_APPLICATION_AMENDMENT_REQUESTED.getValue().equals(log.getEmailType()) + || EmailScenarioTypeEnum.SPECIAL_APPLICATION_AMENDMENT_REQUESTED_BLUE_TONGUE.getValue().equals(log.getEmailType()); + if (isAmendmentEmail && log.getAmendmentId() != null && StatusTypeEnum.SUCCESS.getValue().equals(log.getSendStatus())) { + applicationAmendmentRequestDao.setAmendmentStartAndEndDateOnPecSent(log.getAmendmentId()); + } ApplicationEntity applicationEntity=applicationService.validateApplication(log.getApplicationId()); String callName=applicationEntity.getCall().getName(); PecMailResponse pecMailResponse=createPecMailResponse(log.getUserAction().getId(),log,callName); diff --git a/src/main/java/net/gepafin/tendermanagement/enums/ApplicationAmendmentRequestEnum.java b/src/main/java/net/gepafin/tendermanagement/enums/ApplicationAmendmentRequestEnum.java index 13a6d8c4..daf5685a 100644 --- a/src/main/java/net/gepafin/tendermanagement/enums/ApplicationAmendmentRequestEnum.java +++ b/src/main/java/net/gepafin/tendermanagement/enums/ApplicationAmendmentRequestEnum.java @@ -3,6 +3,9 @@ package net.gepafin.tendermanagement.enums; import com.fasterxml.jackson.annotation.JsonValue; public enum ApplicationAmendmentRequestEnum { + /** Created but not yet approved (PEC not sent by director). */ + DRAFT("DRAFT"), + /** Director sent PEC (approved); amendment is active and awaiting beneficiary response. */ AWAITING("AWAITING"), RESPONSE_RECEIVED("RESPONSE_RECEIVED"), CLOSE("CLOSE"), diff --git a/src/main/resources/message_en.properties b/src/main/resources/message_en.properties index 65b69eff..b223a21d 100644 --- a/src/main/resources/message_en.properties +++ b/src/main/resources/message_en.properties @@ -429,6 +429,7 @@ mail.send.successfully=Mail sent succesfully. pec.email.rejected.successfully=Email rejected successfully. email.log.fetched=Email log fetched successfully. amendment.appropiate.status=Application amendment is not in appropiate status for this operation. +amendment.must.be.approved.first=Amendment must be approved by the director before response can be recorded. upload.company.document.to.application=Uploaded company document to application successfully. company.document.not.found.with.ids=Company document not found. Missing IDs: {0}. amount.field.not.provided= Please provide the required amount fields. diff --git a/src/main/resources/message_it.properties b/src/main/resources/message_it.properties index 2437fa0b..bcff6a8f 100644 --- a/src/main/resources/message_it.properties +++ b/src/main/resources/message_it.properties @@ -420,6 +420,7 @@ mail.send.successfully=Email inviata con successo. pec.email.rejected.successfully=Email rifiutata con successo. email.log.fetched=Registro email recuperato correttamente. amendment.appropiate.status=L'emendamento dell'applicazione non ďż˝ in stato appropriato per questa operazione. +amendment.must.be.approved.first=L'emendamento deve essere approvato dal direttore prima di poter registrare la risposta. 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. From 22eb51a041590378b29e8004d9b9fe9cad486250 Mon Sep 17 00:00:00 2001 From: rajesh Date: Tue, 24 Mar 2026 12:51:31 +0530 Subject: [PATCH 30/42] Added NDG field in the application pagination API --- .../tendermanagement/dao/ApplicationDao.java | 9 +- .../entities/ApplicationView.java | 2 + .../model/response/ApplicationResponse.java | 2 + .../web/rest/api/ApplicationApi.java | 2 +- .../db/changelog/db.changelog-1.0.0.xml | 4 + .../update_application_view_23_03_2026.sql | 86 +++++++++++++++++++ 6 files changed, 103 insertions(+), 2 deletions(-) create mode 100644 src/main/resources/db/dump/update_application_view_23_03_2026.sql diff --git a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java index 7a351d08..044e78fc 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java @@ -507,6 +507,7 @@ public class ApplicationDao { responseBean.setAmountRequested(applicationEntity.getAmountRequested()); responseBean.setDateAccepted(applicationEntity.getDateAccepted()); responseBean.setDateRejected(applicationEntity.getDateRejected()); + responseBean.setNdg(applicationEntity.getNdg()); return responseBean; } @@ -2004,8 +2005,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 +2077,7 @@ public class ApplicationDao { responseBean.setAmountRequested(applicationView.getAmountRequested()); responseBean.setDateAccepted(applicationView.getDateAccepted()); responseBean.setDateRejected(applicationView.getDateRejected()); + responseBean.setNdg(applicationView.getNdg()); return responseBean; } diff --git a/src/main/java/net/gepafin/tendermanagement/entities/ApplicationView.java b/src/main/java/net/gepafin/tendermanagement/entities/ApplicationView.java index 54593c2e..2a2d11fa 100644 --- a/src/main/java/net/gepafin/tendermanagement/entities/ApplicationView.java +++ b/src/main/java/net/gepafin/tendermanagement/entities/ApplicationView.java @@ -91,5 +91,7 @@ public class ApplicationView implements Serializable { @Column(name = "CREATED_DATE") private LocalDateTime createdDate; + @Column(name = "NDG") + private String ndg; } diff --git a/src/main/java/net/gepafin/tendermanagement/model/response/ApplicationResponse.java b/src/main/java/net/gepafin/tendermanagement/model/response/ApplicationResponse.java index 559edcc7..abbfd164 100644 --- a/src/main/java/net/gepafin/tendermanagement/model/response/ApplicationResponse.java +++ b/src/main/java/net/gepafin/tendermanagement/model/response/ApplicationResponse.java @@ -52,4 +52,6 @@ public class ApplicationResponse{ private EvaluationVersionEnum evaluationVersion; + private String ndg; + } \ No newline at end of file diff --git a/src/main/java/net/gepafin/tendermanagement/web/rest/api/ApplicationApi.java b/src/main/java/net/gepafin/tendermanagement/web/rest/api/ApplicationApi.java index 989088d5..964418bd 100644 --- a/src/main/java/net/gepafin/tendermanagement/web/rest/api/ApplicationApi.java +++ b/src/main/java/net/gepafin/tendermanagement/web/rest/api/ApplicationApi.java @@ -234,7 +234,7 @@ public interface ApplicationApi { @ExampleObject(value = ErrorConstants.BADREQUEST_ERROR_EXAMPLE)})) }) @GetMapping(value = "/call/{callId}/csv") - @PreAuthorize("hasRole('ROLE_SUPER_ADMIN')") + @PreAuthorize("hasRole('ROLE_SUPER_ADMIN')|| hasRole('ROLE_INSTRUCTOR_MANAGER')") public ResponseEntity exportCsv( HttpServletRequest request, @Parameter(description = "The call id", required = true) @PathVariable(value = "callId", required = true) Long callId); diff --git a/src/main/resources/db/changelog/db.changelog-1.0.0.xml b/src/main/resources/db/changelog/db.changelog-1.0.0.xml index 8ce255b6..173a6bd4 100644 --- a/src/main/resources/db/changelog/db.changelog-1.0.0.xml +++ b/src/main/resources/db/changelog/db.changelog-1.0.0.xml @@ -3209,4 +3209,8 @@ + + + + diff --git a/src/main/resources/db/dump/update_application_view_23_03_2026.sql b/src/main/resources/db/dump/update_application_view_23_03_2026.sql new file mode 100644 index 00000000..3d2e97d8 --- /dev/null +++ b/src/main/resources/db/dump/update_application_view_23_03_2026.sql @@ -0,0 +1,86 @@ +DROP VIEW IF EXISTS gepafin_schema.application_view ; + + +CREATE OR REPLACE VIEW application_view AS + +SELECT + a.id, + a.status, + a.submission_date, + a.comments, + a.amount_requested, + a.amount_accepted, + a.date_accepted, + a.date_rejected, + a.is_deleted, + a.hub_id, + a.user_id, + a.ndg, + + a.evaluation_version AS evaluation_version, + a.updated_date AS modified_date, + a.created_date AS created_date, + + + + -- Call Details + a.call_id AS call_id, + cl.name AS call_title, + cl.end_date AS call_end_date, + cl.end_time AS call_end_time, + + -- Company Details + a.COMPANY_ID AS company_id, + c.company_name AS company_name, + + -- Protocol Details + p.protocol_number AS protocol_number, + + + -- Assigned User Details from ASSIGNED_APPLICATION and GEPAFIN_USER + COALESCE(aa.user_id, NULL) AS assigned_user_id, + COALESCE( + NULLIF( + TRIM(CONCAT( + COALESCE(u.first_name, ''), ' ', + COALESCE(u.last_name, '') + )), + '' + ), + '' + ) AS assigned_user_name, + +-- User with Company Details (From Application's User) + COALESCE(uwc.id, NULL) AS user_with_company_id + + +FROM gepafin_schema.APPLICATION a + +-- Join Call Entity +LEFT JOIN gepafin_schema.CALL cl + ON a.CALL_ID = cl.id + +-- Join Company Entity (Ensuring it is not deleted) +LEFT JOIN gepafin_schema.COMPANY c + ON a.COMPANY_ID = c.id + +-- Join Protocol Entity +LEFT JOIN gepafin_schema.PROTOCOL p + ON a.PROTOCOL_NUMBER = p.id + +-- Join Assigned Application Entity (Ensuring it is not deleted) +LEFT JOIN gepafin_schema.assigned_applications aa + ON a.id = aa.APPLICATION_ID + AND (aa.IS_DELETED IS FALSE OR aa.IS_DELETED IS NULL) + + -- Join User Entity (Get First & Last Name Combined) +LEFT JOIN gepafin_schema.GEPAFIN_USER u + ON aa.user_id = u.id + + -- Get User With Company ID (From Application's User & Company) + LEFT JOIN gepafin_schema.USER_WITH_COMPANY uwc + ON a.user_id = uwc.user_id + AND a.COMPANY_ID = uwc.company_id + AND uwc.is_deleted = FALSE -- Ensuring the user is active + +WHERE a.IS_DELETED IS FALSE OR a.IS_DELETED IS NULL; \ No newline at end of file From 06da5007c50015216989965caf99f0797b13a537 Mon Sep 17 00:00:00 2001 From: rajesh Date: Wed, 25 Mar 2026 20:28:36 +0530 Subject: [PATCH 31/42] Added new form field --- .../resources/db/changelog/db.changelog-1.0.0.xml | 4 ++++ .../db/dump/insert_form_field_25_03_2026.sql | 12 ++++++++++++ 2 files changed, 16 insertions(+) create mode 100644 src/main/resources/db/dump/insert_form_field_25_03_2026.sql diff --git a/src/main/resources/db/changelog/db.changelog-1.0.0.xml b/src/main/resources/db/changelog/db.changelog-1.0.0.xml index 173a6bd4..8295204e 100644 --- a/src/main/resources/db/changelog/db.changelog-1.0.0.xml +++ b/src/main/resources/db/changelog/db.changelog-1.0.0.xml @@ -3213,4 +3213,8 @@ + + + + diff --git a/src/main/resources/db/dump/insert_form_field_25_03_2026.sql b/src/main/resources/db/dump/insert_form_field_25_03_2026.sql new file mode 100644 index 00000000..17f8a507 --- /dev/null +++ b/src/main/resources/db/dump/insert_form_field_25_03_2026.sql @@ -0,0 +1,12 @@ +INSERT INTO FORM_FIELD (SORT_ORDER, NAME, LABEL, DESCRIPTION, SETTINGS, VALIDATORS, CREATED_DATE, UPDATED_DATE) +VALUES +( + 24, + 'spreadsheet', + 'Foglio di Calcolo', + 'Modello di foglio di calcolo con variabili dinamiche', + '[{name: "label",value: "Foglio di Calcolo"},{name: "template",value: {}}]', + '{}', + CURRENT_TIMESTAMP, + CURRENT_TIMESTAMP +); \ No newline at end of file From c43de36cc0c04a077012edc7d5beeb8c075c5bd3 Mon Sep 17 00:00:00 2001 From: rajesh Date: Thu, 26 Mar 2026 16:30:31 +0530 Subject: [PATCH 32/42] Done task GEPAFINBE-6311 Implemented upload application company document flow --- .../dao/ApplicationEvaluationDao.java | 7 ++ .../dao/CompanyDocumentDao.java | 106 +++++++++++++++--- .../tendermanagement/dao/NotificationDao.java | 36 +++++- .../entities/CompanyDocumentEntity.java | 2 + .../enums/CompanyDocumentTypeEnum.java | 3 +- .../enums/UserActionContextEnum.java | 1 + .../ApplicationEvaluationFormResponse.java | 1 + .../ApplicationEvaluationResponse.java | 1 + .../response/CompanyDocumentResponseBean.java | 2 + .../CompanyDocumentRepository.java | 2 + .../UserWithCompanyRepository.java | 2 + .../CompanyDocumentExpirationScheduler.java | 2 +- .../service/CompanyDocumentService.java | 2 +- .../impl/CompanyDocumentServiceImpl.java | 16 +++ .../web/rest/api/CompanyDocumentApi.java | 22 ++++ .../impl/CompanyDocumentApiControlller.java | 8 ++ .../db/changelog/db.changelog-1.0.0.xml | 7 ++ 17 files changed, 197 insertions(+), 23 deletions(-) diff --git a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationEvaluationDao.java b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationEvaluationDao.java index d5edecb7..052feb17 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationEvaluationDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationEvaluationDao.java @@ -115,6 +115,9 @@ public class ApplicationEvaluationDao { @Autowired private ApplicationAmendmentRequestDao applicationAmendmentRequestDao; + @Autowired + private CompanyDocumentDao companyDocumentDao; + @Autowired private HubService hubService; @@ -215,6 +218,9 @@ public class ApplicationEvaluationDao { setEvaluationDocResponse(response, allDocs); setApplicationDetails(response, entity); setRejectedDocuments(applicationEntity, response); + response.setApplicationCompanyDocuments( + companyDocumentDao.listApplicationCompanyDocumentsForEvaluation( + entity.getApplicationId(), applicationEntity.getCompanyId())); return response; } @@ -2407,6 +2413,7 @@ public class ApplicationEvaluationDao { response.setAmountAccepted(applicationEvaluationResponse.getAmountAccepted()); response.setDateAccepted(applicationEvaluationResponse.getDateAccepted()); response.setDateRejected(applicationEvaluationResponse.getDateRejected()); + response.setApplicationCompanyDocuments(applicationEvaluationResponse.getApplicationCompanyDocuments()); return response; } diff --git a/src/main/java/net/gepafin/tendermanagement/dao/CompanyDocumentDao.java b/src/main/java/net/gepafin/tendermanagement/dao/CompanyDocumentDao.java index 2eba35f5..ac92bf04 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/CompanyDocumentDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/CompanyDocumentDao.java @@ -3,6 +3,8 @@ package net.gepafin.tendermanagement.dao; import com.amazonaws.services.s3.AmazonS3; import com.amazonaws.services.s3.AmazonS3Client; import com.amazonaws.services.s3.model.CopyObjectRequest; +import jakarta.persistence.criteria.Join; +import jakarta.persistence.criteria.JoinType; import jakarta.persistence.criteria.Predicate; import jakarta.servlet.http.HttpServletRequest; import lombok.extern.slf4j.Slf4j; @@ -18,6 +20,7 @@ import net.gepafin.tendermanagement.model.response.DocumentResponseBean; import net.gepafin.tendermanagement.model.response.UploadFileOnAmazonS3Response; 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; @@ -38,6 +41,7 @@ import java.net.URL; import java.time.LocalDateTime; 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 +95,58 @@ public class CompanyDocumentDao { @Autowired private AmazonS3 amazonS3; + @Autowired + private UserWithCompanyRepository userWithCompanyRepository; + + /** + * Instructor uploads a company document tied to an application. Files are stored under the same S3 layout as + * {@link CompanyDocumentTypeEnum#COMPANY_DOCUMENT}; persisted row type remains {@link CompanyDocumentTypeEnum#APPLICATION_DOCUMENT}. + */ + public List uploadInstructorCompanyDocumentToApplication(Long userId, List files, Long companyId, Long applicationId, Long documentCategoryId, LocalDateTime expirationDate, String name) { + log.info("Instructor upload company document to application. userId={}, companyId={}, applicationId={}", userId, companyId, applicationId); + applicationService.validateApplicationWithCompany(applicationId, companyId); + DocumentCategoryEntity categoryEntity = categoryDao.validateCategory(documentCategoryId); + Optional userWithCompanyOpt = userWithCompanyRepository.findByUserIdAndCompanyIdAndIsDeletedFalse(userId, companyId); + + LocalDateTime currentDate = LocalDateTime.now(); + if (expirationDate.isBefore(currentDate)) { + log.warn("Expiration date {} is before current time {}", expirationDate, currentDate); + throw new CustomValidationException(Status.VALIDATION_ERROR, Translator.toLocale(GepafinConstant.INVALID_EXPIRATION_DATE)); + } + CompanyDocumentTypeEnum storedType = CompanyDocumentTypeEnum.APPLICATION_DOCUMENT; + List companyDocumentEntities = new ArrayList<>(); + for (MultipartFile file : files) { + log.info("Uploading instructor company document '{}' for companyId={}, applicationId={}", file.getOriginalFilename(), companyId, applicationId); + UploadFileOnAmazonS3Response uploadFileOnAmazonS3Response = uploadFileOnAmazonS3(file, CompanyDocumentTypeEnum.COMPANY_DOCUMENT, companyId); + if (uploadFileOnAmazonS3Response != null) { + CompanyDocumentEntity companyDocumentEntity = new CompanyDocumentEntity(); + companyDocumentEntity.setFileName(uploadFileOnAmazonS3Response.getFileName()); + companyDocumentEntity.setCompanyId(companyId); + companyDocumentEntity.setApplicationId(applicationId); + companyDocumentEntity.setType(storedType.getValue()); + companyDocumentEntity.setFilePath(uploadFileOnAmazonS3Response.getFilePath()); + companyDocumentEntity.setIsDeleted(false); + companyDocumentEntity.setUploadedBy(userId); + companyDocumentEntity.setName(name); + if (expirationDate.isBefore(currentDate.plusDays(7))) { + companyDocumentEntity.setStatus(CompanyDocumentStatusEnum.DUE.getValue()); + } else { + companyDocumentEntity.setStatus(CompanyDocumentStatusEnum.VALID.getValue()); + } + companyDocumentEntity.setCategoryEntity(categoryEntity); + companyDocumentEntity.setUserWithCompany(userWithCompanyOpt.orElse(null)); + companyDocumentEntity.setExpirationDate(expirationDate); + companyDocumentEntities.add(companyDocumentEntity); + } + } + companyDocumentRepository.saveAll(companyDocumentEntities); + companyDocumentEntities.forEach(entity -> loggingUtil.addVersionHistory( + VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.INSERT).oldData(null).newData(entity).build())); + return companyDocumentEntities.stream() + .map(this::convertToCompanyDocumentResponseBean) + .collect(Collectors.toList()); + } + public List uploadFileForCompany(Long userId, List files, Long companyId, Long documentCategoryId, CompanyDocumentTypeEnum companyDocumentSourceTypeEnum, LocalDateTime expirationDate,String name){ log.info("Uploading files for company. userId={}, companyId={}, documentCategoryId={}", userId, companyId, documentCategoryId); @@ -173,7 +229,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 +240,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,6 +329,9 @@ 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; } @@ -314,10 +375,14 @@ public class CompanyDocumentDao { public List getAllCompanyDocument(UserEntity user , Long companyId, CompanyDocumentTypeEnum typeEnum){ log.info("Fetching all company documents for Company ID '{}', User ID '{}', Type '{}'", companyId, user.getId(), typeEnum); - if(Boolean.TRUE.equals(validator.checkIsBeneficiary())) { - validator.validateUserWithCompany(request, companyId); + CompanyEntity companyEntity = companyService.validateCompany(companyId); + if (Boolean.TRUE.equals(validator.checkIsBeneficiary())) { + if (typeEnum == CompanyDocumentTypeEnum.PERSONAL_DOCUMENT || typeEnum == null) { + validator.validateUserWithCompany(request, companyId); + } else { + validator.validateHubId(request, companyEntity.getHub().getId()); + } } - companyService.validateCompany(companyId); Specification spec = filterCompanyDocuments(companyId, user.getId(), typeEnum); @@ -345,6 +410,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 +421,28 @@ public class CompanyDocumentDao { builder.equal(root.get("userWithCompany").get("userId"), userId) ); } - }else { - // Case 3: If typeEnum is null, fetch all documents for the company and personal documents for the user - Predicate companyPredicate = builder.equal(root.get("companyId"), companyId); - Predicate personalPredicate = builder.and( - builder.equal(root.get("type"), CompanyDocumentTypeEnum.PERSONAL_DOCUMENT.getValue()), - builder.equal(root.get("userWithCompany").get("userId"), userId) + } else { + + Predicate companyAndApplicationDocs = root.get("type").in( + CompanyDocumentTypeEnum.COMPANY_DOCUMENT.getValue(), + CompanyDocumentTypeEnum.APPLICATION_DOCUMENT.getValue() ); - predicate = builder.and(predicate, builder.or(companyPredicate, personalPredicate)); + predicate = builder.and(predicate, companyAndApplicationDocs); } return predicate; }; } - - + public List listApplicationCompanyDocumentsForEvaluation(Long applicationId, Long companyId) { + if (applicationId == null || companyId == null) { + return List.of(); + } + List entities = companyDocumentRepository.findByApplicationIdAndCompanyIdAndTypeAndStatusNot( + applicationId, + companyId, + CompanyDocumentTypeEnum.APPLICATION_DOCUMENT.getValue(), + CompanyDocumentStatusEnum.EXPIRED.getValue()); + return entities.stream().map(this::convertToCompanyDocumentResponseBean).collect(Collectors.toList()); + } } diff --git a/src/main/java/net/gepafin/tendermanagement/dao/NotificationDao.java b/src/main/java/net/gepafin/tendermanagement/dao/NotificationDao.java index 57236418..2f923c3d 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/NotificationDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/NotificationDao.java @@ -36,6 +36,7 @@ import org.springframework.stereotype.Component; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; @@ -423,15 +424,40 @@ public class NotificationDao { } public void sendNotificationToBeneficiaryForDocumentExpiration(CompanyDocumentEntity companyDocumentEntity, NotificationTypeEnum notificationTypeEnum) { - - CompanyEntity companyEntity = companyService.validateCompany(companyDocumentEntity.getCompanyId()); + Long companyId = companyDocumentEntity.getCompanyId(); + if (companyId == null) { + log.warn("Skipping document expiration notification: companyId is null for document id {}", companyDocumentEntity.getId()); + return; + } + CompanyEntity companyEntity = companyService.validateCompany(companyId); + List linkedUsers = userWithCompanyRepository.findByCompanyIdAndIsDeletedFalse(companyId); + if (linkedUsers == null || linkedUsers.isEmpty()) { + log.warn("Skipping document expiration notification: no users linked to company {} for document id {}", companyId, companyDocumentEntity.getId()); + return; + } Map placeHolders = new HashMap<>(); placeHolders.put("{{file_name}}", companyDocumentEntity.getFileName()); placeHolders.put("{{company_name}}", companyEntity.getCompanyName()); placeHolders.put("{{expiration_date}}", companyDocumentEntity.getExpirationDate().toString()); - NotificationReq notificationReq = createNotificationReq(notificationTypeEnum.getValue(), placeHolders, companyDocumentEntity.getUserWithCompany().getUserId(), companyDocumentEntity.getUserWithCompany(), - listOf(companyDocumentEntity.getCompanyId())); - sendNotification(notificationReq); + Map oneRowPerUser = new LinkedHashMap<>(); + for (UserWithCompanyEntity uwc : linkedUsers) { + if (uwc.getUserId() != null) { + oneRowPerUser.putIfAbsent(uwc.getUserId(), uwc); + } + } + if (oneRowPerUser.isEmpty()) { + log.warn("Skipping document expiration notification: no user ids on USER_WITH_COMPANY for company {}, document id {}", companyId, companyDocumentEntity.getId()); + return; + } + for (UserWithCompanyEntity uwc : oneRowPerUser.values()) { + NotificationReq notificationReq = createNotificationReq( + notificationTypeEnum.getValue(), + placeHolders, + uwc.getUserId(), + uwc, + listOf(companyId)); + sendNotification(notificationReq); + } } public PageableResponseBean> getNotificationsByUserIdAndCompanyIdByPagination(Long userId, Long companyId, NotificationRequestBean notificationRequestBean) { diff --git a/src/main/java/net/gepafin/tendermanagement/entities/CompanyDocumentEntity.java b/src/main/java/net/gepafin/tendermanagement/entities/CompanyDocumentEntity.java index 1a08fffc..22ebd420 100644 --- a/src/main/java/net/gepafin/tendermanagement/entities/CompanyDocumentEntity.java +++ b/src/main/java/net/gepafin/tendermanagement/entities/CompanyDocumentEntity.java @@ -44,5 +44,7 @@ public class CompanyDocumentEntity extends BaseEntity { @JoinColumn(name = "DOCUMENT_CATEGORY_ID") private DocumentCategoryEntity categoryEntity; + @Column(name = "APPLICATION_ID") + private Long applicationId; } diff --git a/src/main/java/net/gepafin/tendermanagement/enums/CompanyDocumentTypeEnum.java b/src/main/java/net/gepafin/tendermanagement/enums/CompanyDocumentTypeEnum.java index 9b163da0..973d17c4 100644 --- a/src/main/java/net/gepafin/tendermanagement/enums/CompanyDocumentTypeEnum.java +++ b/src/main/java/net/gepafin/tendermanagement/enums/CompanyDocumentTypeEnum.java @@ -2,7 +2,8 @@ package net.gepafin.tendermanagement.enums; public enum CompanyDocumentTypeEnum { COMPANY_DOCUMENT("COMPANY_DOCUMENT"), - PERSONAL_DOCUMENT("PERSONAL_DOCUMENT"); + PERSONAL_DOCUMENT("PERSONAL_DOCUMENT"), + APPLICATION_DOCUMENT("APPLICATION_DOCUMENT"); private String value; diff --git a/src/main/java/net/gepafin/tendermanagement/enums/UserActionContextEnum.java b/src/main/java/net/gepafin/tendermanagement/enums/UserActionContextEnum.java index 9dc8b16d..5cf65f56 100644 --- a/src/main/java/net/gepafin/tendermanagement/enums/UserActionContextEnum.java +++ b/src/main/java/net/gepafin/tendermanagement/enums/UserActionContextEnum.java @@ -204,6 +204,7 @@ public enum UserActionContextEnum { GET_COMPANY_DOCUMENT("GET_COMPANY_DOCUMENT"), DELETE_COMPANY_DOCUMENT("DELETE_COMPANY_DOCUMENT"), UPLOAD_COMPANY_DOCUMENT("UPLOAD_COMPANY_DOCUMENT"), + UPLOAD_COMPANY_APPLICATION_DOCUMENT("UPLOAD_COMPANY_APPLICATION_DOCUMENT"), UPLOAD_COMPANY_PERSONAL_DOCUMENT("UPLOAD_COMPANY_PERSONAL_DOCUMENT"), GET_ALL_COMPANY_DOCUMENT("GET_ALL_COMPANY_DOCUMENT"), UPDATE_COMPANY_DOCUMENT("UPDATE_COMPANY_DOCUMENT"), diff --git a/src/main/java/net/gepafin/tendermanagement/model/response/ApplicationEvaluationFormResponse.java b/src/main/java/net/gepafin/tendermanagement/model/response/ApplicationEvaluationFormResponse.java index 3903daed..4e117c6f 100644 --- a/src/main/java/net/gepafin/tendermanagement/model/response/ApplicationEvaluationFormResponse.java +++ b/src/main/java/net/gepafin/tendermanagement/model/response/ApplicationEvaluationFormResponse.java @@ -24,6 +24,7 @@ public class ApplicationEvaluationFormResponse { private ApplicationEvaluationFormResponseBean applicationEvaluationFormResponse; private List files; private List evaluationDocument; + private List applicationCompanyDocuments; private List amendmentDetails; private LocalDateTime createdDate; private LocalDateTime updatedDate; diff --git a/src/main/java/net/gepafin/tendermanagement/model/response/ApplicationEvaluationResponse.java b/src/main/java/net/gepafin/tendermanagement/model/response/ApplicationEvaluationResponse.java index d05f5816..86788e88 100644 --- a/src/main/java/net/gepafin/tendermanagement/model/response/ApplicationEvaluationResponse.java +++ b/src/main/java/net/gepafin/tendermanagement/model/response/ApplicationEvaluationResponse.java @@ -25,6 +25,7 @@ public class ApplicationEvaluationResponse { private List checklist; private List files; private List evaluationDocument; + private List applicationCompanyDocuments; private List amendmentDetails; private LocalDateTime createdDate; private LocalDateTime updatedDate; diff --git a/src/main/java/net/gepafin/tendermanagement/model/response/CompanyDocumentResponseBean.java b/src/main/java/net/gepafin/tendermanagement/model/response/CompanyDocumentResponseBean.java index 73c13fdc..47aa3d00 100644 --- a/src/main/java/net/gepafin/tendermanagement/model/response/CompanyDocumentResponseBean.java +++ b/src/main/java/net/gepafin/tendermanagement/model/response/CompanyDocumentResponseBean.java @@ -26,6 +26,8 @@ public class CompanyDocumentResponseBean extends BaseBean { private Long userWithCompanyId; + private Long applicationId; + private DocumentCategoryResponse category; } diff --git a/src/main/java/net/gepafin/tendermanagement/repositories/CompanyDocumentRepository.java b/src/main/java/net/gepafin/tendermanagement/repositories/CompanyDocumentRepository.java index c37b993a..39731a4e 100644 --- a/src/main/java/net/gepafin/tendermanagement/repositories/CompanyDocumentRepository.java +++ b/src/main/java/net/gepafin/tendermanagement/repositories/CompanyDocumentRepository.java @@ -37,5 +37,7 @@ public interface CompanyDocumentRepository extends JpaRepository findByIdInAndIsDeletedFalseAndStatusNot(List ids, String status); + List findByApplicationIdAndCompanyIdAndTypeAndStatusNot( + Long applicationId, Long companyId, String type, String status); } diff --git a/src/main/java/net/gepafin/tendermanagement/repositories/UserWithCompanyRepository.java b/src/main/java/net/gepafin/tendermanagement/repositories/UserWithCompanyRepository.java index 13a197f1..357c3b0e 100644 --- a/src/main/java/net/gepafin/tendermanagement/repositories/UserWithCompanyRepository.java +++ b/src/main/java/net/gepafin/tendermanagement/repositories/UserWithCompanyRepository.java @@ -20,4 +20,6 @@ public interface UserWithCompanyRepository extends JpaRepository findByCompanyIdAndIsDeletedFalse(Long companyId); + } diff --git a/src/main/java/net/gepafin/tendermanagement/scheduler/CompanyDocumentExpirationScheduler.java b/src/main/java/net/gepafin/tendermanagement/scheduler/CompanyDocumentExpirationScheduler.java index e5a40408..6cd8b592 100644 --- a/src/main/java/net/gepafin/tendermanagement/scheduler/CompanyDocumentExpirationScheduler.java +++ b/src/main/java/net/gepafin/tendermanagement/scheduler/CompanyDocumentExpirationScheduler.java @@ -38,7 +38,7 @@ public class CompanyDocumentExpirationScheduler { @Autowired private ExpirationConfigRepository expirationConfigRepository; - private static final Logger log = LoggerFactory.getLogger(ExpirationScheduler.class); + private static final Logger log = LoggerFactory.getLogger(CompanyDocumentExpirationScheduler.class); @Scheduled(cron = "0 0 4 * * ?") public void processDocumentExpiration(){ diff --git a/src/main/java/net/gepafin/tendermanagement/service/CompanyDocumentService.java b/src/main/java/net/gepafin/tendermanagement/service/CompanyDocumentService.java index ab640840..69cd7391 100644 --- a/src/main/java/net/gepafin/tendermanagement/service/CompanyDocumentService.java +++ b/src/main/java/net/gepafin/tendermanagement/service/CompanyDocumentService.java @@ -24,6 +24,6 @@ public interface CompanyDocumentService { List getAllCompanyDocument(HttpServletRequest request ,Long companyId , CompanyDocumentTypeEnum typeEnum); - + List uploadInstructorCompanyDocumentToApplication(HttpServletRequest request, List files, Long companyId, Long applicationId, Long documentCategoryId, LocalDateTime expirationDate, String name); } diff --git a/src/main/java/net/gepafin/tendermanagement/service/impl/CompanyDocumentServiceImpl.java b/src/main/java/net/gepafin/tendermanagement/service/impl/CompanyDocumentServiceImpl.java index b76a82f0..2281a79a 100644 --- a/src/main/java/net/gepafin/tendermanagement/service/impl/CompanyDocumentServiceImpl.java +++ b/src/main/java/net/gepafin/tendermanagement/service/impl/CompanyDocumentServiceImpl.java @@ -2,6 +2,7 @@ package net.gepafin.tendermanagement.service.impl; import jakarta.servlet.http.HttpServletRequest; import net.gepafin.tendermanagement.dao.CompanyDocumentDao; +import net.gepafin.tendermanagement.entities.CompanyEntity; import net.gepafin.tendermanagement.entities.UserEntity; import net.gepafin.tendermanagement.enums.CompanyDocumentTypeEnum; import net.gepafin.tendermanagement.enums.DocumentTypeEnum; @@ -9,6 +10,7 @@ import net.gepafin.tendermanagement.model.request.CompanyDocumentRequest; import net.gepafin.tendermanagement.model.response.CompanyDocumentResponseBean; import net.gepafin.tendermanagement.model.response.DocumentResponseBean; import net.gepafin.tendermanagement.service.CompanyDocumentService; +import net.gepafin.tendermanagement.service.CompanyService; import net.gepafin.tendermanagement.util.Utils; import net.gepafin.tendermanagement.util.Validator; import org.springframework.beans.factory.annotation.Autowired; @@ -28,6 +30,9 @@ public class CompanyDocumentServiceImpl implements CompanyDocumentService { @Autowired private CompanyDocumentDao companyDocumentDao; + @Autowired + private CompanyService companyService; + @Override public List uploadFileForCompany(HttpServletRequest request, List files, Long companyId, Long documentCategoryId , CompanyDocumentTypeEnum documentSourceTypeEnum, LocalDateTime expirationDate,String name) { Map userInfo = validator.getUserInfoFromToken(request); @@ -37,6 +42,17 @@ public class CompanyDocumentServiceImpl implements CompanyDocumentService { return companyDocumentDao.uploadFileForCompany(userId,files,companyId,documentCategoryId,documentSourceTypeEnum,expirationDate,name); } + @Override + public List uploadInstructorCompanyDocumentToApplication(HttpServletRequest request, List files, Long companyId, Long applicationId, Long documentCategoryId, LocalDateTime expirationDate, String name) { + Map userInfo = validator.getUserInfoFromToken(request); + Long userId = validator.getUserId(userInfo); + files.forEach(Utils::validateFileType); + validator.validateUser(request); + CompanyEntity company = companyService.validateCompany(companyId); + validator.validateHubId(request, company.getHub().getId()); + return companyDocumentDao.uploadInstructorCompanyDocumentToApplication(userId, files, companyId, applicationId, documentCategoryId, expirationDate, name); + } + @Override public CompanyDocumentResponseBean updateCompanyDocument(HttpServletRequest request, Long companyDocumentId, CompanyDocumentRequest companyDocumentRequest) { validator.validateUser(request); diff --git a/src/main/java/net/gepafin/tendermanagement/web/rest/api/CompanyDocumentApi.java b/src/main/java/net/gepafin/tendermanagement/web/rest/api/CompanyDocumentApi.java index fdd53635..4b8656ca 100644 --- a/src/main/java/net/gepafin/tendermanagement/web/rest/api/CompanyDocumentApi.java +++ b/src/main/java/net/gepafin/tendermanagement/web/rest/api/CompanyDocumentApi.java @@ -19,6 +19,7 @@ import org.springframework.format.annotation.DateTimeFormat; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; @@ -48,6 +49,27 @@ public interface CompanyDocumentApi { return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED); } + @PreAuthorize("hasRole('ROLE_SUPER_ADMIN') || hasRole('ROLE_INSTRUCTOR_MANAGER') || hasRole('ROLE_PRE_INSTRUCTOR')") + @Operation(summary = "API to upload company document for an application (Only for instructor)", + responses = { + @ApiResponse(responseCode = "200", description = "OK"), + @ApiResponse(responseCode = "404", description = "Not Found", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, examples = { + @ExampleObject(value = ErrorConstants.NOTFOUND_ERROR_EXAMPLE) })), + @ApiResponse(responseCode = "401", description = "Unauthorized", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, examples = { + @ExampleObject(value = ErrorConstants.UNAUTHORIZED_ERROR_EXAMPLE) })), + @ApiResponse(responseCode = "400", description = "Bad Request", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, examples = { + @ExampleObject(value = ErrorConstants.BADREQUEST_ERROR_EXAMPLE) }))}) + @PostMapping(value = "company/{companyId}/application/{applicationId}/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + default ResponseEntity>> uploadInstructorCompanyDocumentToApplication(HttpServletRequest httpServletRequest, + @Parameter(description = "Company Id", required = true) @PathVariable("companyId") Long companyId, + @Parameter(description = "Application Id", required = true) @PathVariable("applicationId") Long applicationId, + @Parameter(description = "Document category id", required = true) @RequestParam(value = "documentCategoryId") Long documentCategoryId, + @Parameter(description = "Display name", required = true) @RequestParam(value = "name") String name, + @Parameter(description = "Expiration date (ISO-8601)", required = true) @RequestParam("expirationDate") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime expirationDate, + @RequestParam("file") List files) { + return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED); + } + @Operation(summary = "Api to update company document", responses = { @ApiResponse(responseCode = "200", description = "OK"), diff --git a/src/main/java/net/gepafin/tendermanagement/web/rest/api/impl/CompanyDocumentApiControlller.java b/src/main/java/net/gepafin/tendermanagement/web/rest/api/impl/CompanyDocumentApiControlller.java index 543ec40d..191d7a31 100644 --- a/src/main/java/net/gepafin/tendermanagement/web/rest/api/impl/CompanyDocumentApiControlller.java +++ b/src/main/java/net/gepafin/tendermanagement/web/rest/api/impl/CompanyDocumentApiControlller.java @@ -57,6 +57,14 @@ public class CompanyDocumentApiControlller implements CompanyDocumentApi { } } + @Override + public ResponseEntity>> uploadInstructorCompanyDocumentToApplication(HttpServletRequest request, Long companyId, Long applicationId, Long documentCategoryId, String name, LocalDateTime expirationDate, List files) { + loggingUtil.logUserAction(UserActionRequest.builder().request(request).actionType(UserActionLogsEnum.UPLOAD).actionContext(UserActionContextEnum.UPLOAD_COMPANY_DOCUMENT_TO_APPLICATION).build()); + List responseBeans = companyDocumentService.uploadInstructorCompanyDocumentToApplication(request, files, companyId, applicationId, documentCategoryId, expirationDate, name); + return ResponseEntity.status(HttpStatus.CREATED) + .body(new Response<>(responseBeans, Status.SUCCESS, Translator.toLocale(GepafinConstant.FILES_UPLOADED_MSG))); + } + @Override public ResponseEntity> updateCompanyDocument(HttpServletRequest httpServletRequest, Long companyDocumentId, CompanyDocumentRequest companyDocumentRequest) { diff --git a/src/main/resources/db/changelog/db.changelog-1.0.0.xml b/src/main/resources/db/changelog/db.changelog-1.0.0.xml index 173a6bd4..8f066cc5 100644 --- a/src/main/resources/db/changelog/db.changelog-1.0.0.xml +++ b/src/main/resources/db/changelog/db.changelog-1.0.0.xml @@ -3213,4 +3213,11 @@ + + + + + + + From 4a46cf68afd47521a2278ffbb5ce68c269768847 Mon Sep 17 00:00:00 2001 From: rajesh Date: Mon, 30 Mar 2026 12:40:02 +0530 Subject: [PATCH 33/42] Implemented logic to copy company document to evaluation --- .../dao/ApplicationEvaluationDao.java | 5 - .../dao/CompanyDocumentDao.java | 124 +++++++++++++++++- .../ApplicationEvaluationResponse.java | 1 - 3 files changed, 123 insertions(+), 7 deletions(-) diff --git a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationEvaluationDao.java b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationEvaluationDao.java index 052feb17..6b8d5d50 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationEvaluationDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationEvaluationDao.java @@ -218,10 +218,6 @@ public class ApplicationEvaluationDao { setEvaluationDocResponse(response, allDocs); setApplicationDetails(response, entity); setRejectedDocuments(applicationEntity, response); - response.setApplicationCompanyDocuments( - companyDocumentDao.listApplicationCompanyDocumentsForEvaluation( - entity.getApplicationId(), applicationEntity.getCompanyId())); - return response; } @@ -2413,7 +2409,6 @@ public class ApplicationEvaluationDao { response.setAmountAccepted(applicationEvaluationResponse.getAmountAccepted()); response.setDateAccepted(applicationEvaluationResponse.getDateAccepted()); response.setDateRejected(applicationEvaluationResponse.getDateRejected()); - response.setApplicationCompanyDocuments(applicationEvaluationResponse.getApplicationCompanyDocuments()); return response; } diff --git a/src/main/java/net/gepafin/tendermanagement/dao/CompanyDocumentDao.java b/src/main/java/net/gepafin/tendermanagement/dao/CompanyDocumentDao.java index ac92bf04..c8e87f88 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/CompanyDocumentDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/CompanyDocumentDao.java @@ -3,6 +3,7 @@ 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; @@ -13,11 +14,14 @@ 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; @@ -38,7 +42,10 @@ 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; @@ -98,13 +105,16 @@ public class CompanyDocumentDao { @Autowired private UserWithCompanyRepository userWithCompanyRepository; + @Autowired + private ApplicationEvaluationRepository applicationEvaluationRepository; + /** * Instructor uploads a company document tied to an application. Files are stored under the same S3 layout as * {@link CompanyDocumentTypeEnum#COMPANY_DOCUMENT}; persisted row type remains {@link CompanyDocumentTypeEnum#APPLICATION_DOCUMENT}. */ public List uploadInstructorCompanyDocumentToApplication(Long userId, List files, Long companyId, Long applicationId, Long documentCategoryId, LocalDateTime expirationDate, String name) { log.info("Instructor upload company document to application. userId={}, companyId={}, applicationId={}", userId, companyId, applicationId); - applicationService.validateApplicationWithCompany(applicationId, companyId); + ApplicationEntity application = applicationService.validateApplicationWithCompany(applicationId, companyId); DocumentCategoryEntity categoryEntity = categoryDao.validateCategory(documentCategoryId); Optional userWithCompanyOpt = userWithCompanyRepository.findByUserIdAndCompanyIdAndIsDeletedFalse(userId, companyId); @@ -142,6 +152,9 @@ public class CompanyDocumentDao { 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()); @@ -373,6 +386,115 @@ public class CompanyDocumentDao { return responseBean; } + /** + * For instructor uploads: copies each saved company document into the evaluation S3 folder, persists + * {@link DocumentEntity} rows (source EVALUATION), and merges entries into the existing {@code EVALUATION_DOCUMENT} + * column ({@link ApplicationEvaluationEntity#getEvaluationDocument()} JSON array of {@link EvaluationDocumentRequest}), + * appending to any data already stored there. + */ + private void copyInstructorCompanyDocumentsToEvaluationDocuments(ApplicationEntity application, + List instructorCompanyDocuments, Long userId) { + if (instructorCompanyDocuments == null || instructorCompanyDocuments.isEmpty()) { + return; + } + Long evaluationId = application.getApplicationEvaluationId(); + if (evaluationId == null || evaluationId <= 0) { + log.debug("Skip evaluation document copy for applicationId={}: no applicationEvaluationId set", application.getId()); + return; + } + Optional evaluationOpt = applicationEvaluationRepository.findByIdAndIsDeletedFalse(evaluationId); + if (evaluationOpt.isEmpty()) { + log.warn("Skip evaluation document copy: applicationEvaluationId={} not found or deleted", evaluationId); + return; + } + + Long callId = application.getCall().getId(); + Long applicationId = application.getId(); + String evaluationFolderPath = s3ConfigBean.generateDocumentPath( + DocumentSourceTypeEnum.EVALUATION, callId, applicationId, evaluationId, 0L, 0L); + + List newEntries = new ArrayList<>(); + for (CompanyDocumentEntity companyDoc : instructorCompanyDocuments) { + DocumentEntity saved = persistInstructorCompanyDocumentAsEvaluationDocument( + companyDoc, evaluationFolderPath, evaluationId, userId); + if (saved.getId() == null) { + continue; + } + EvaluationDocumentRequest entry = new EvaluationDocumentRequest(); + SecureRandom random = new SecureRandom(); + + String key = NanoIdUtils.randomNanoId( + random, + NanoIdUtils.DEFAULT_ALPHABET, + 10 + ); + entry.setFieldId(key); + entry.setNameValue(instructorDocumentDisplayName(companyDoc)); + entry.setFileValue(String.valueOf(saved.getId())); + entry.setValid(null); + newEntries.add(entry); + } + if (newEntries.isEmpty()) { + return; + } + + ApplicationEvaluationEntity evaluation = evaluationOpt.get(); + List merged = new ArrayList<>(parseEvaluationDocumentJson(evaluation.getEvaluationDocument())); + merged.addAll(newEntries); + evaluation.setEvaluationDocument(Utils.convertObjectToJson(merged)); + applicationEvaluationRepository.save(evaluation); + } + + private static List parseEvaluationDocumentJson(String json) { + if (StringUtils.isBlank(json)) { + return new ArrayList<>(); + } + List parsed = Utils.convertJsonToList(json, + new TypeReference>() {}); + return parsed != null ? new ArrayList<>(parsed) : new ArrayList<>(); + } + + + private static String instructorDocumentDisplayName(CompanyDocumentEntity companyDoc) { + if (companyDoc == null) { + return ""; + } + if (StringUtils.isNotBlank(companyDoc.getName())) { + return companyDoc.getName().trim(); + } + return StringUtils.defaultString(companyDoc.getFileName()); + } + + private DocumentEntity persistInstructorCompanyDocumentAsEvaluationDocument(CompanyDocumentEntity companyDocumentEntity, + String evaluationDocumentFolderPath, Long evaluationId, Long userId) { + String companyDocumentPath = companyDocumentEntity.getFilePath(); + log.info("Instructor evaluation copy: companyDocumentId={}, oldPath={}, evaluationFolder={}", + companyDocumentEntity.getId(), companyDocumentPath, evaluationDocumentFolderPath); + + UploadFileOnAmazonS3Response response; + try { + response = amazonS3ServiceImpl.copyFile(companyDocumentEntity.getName(), companyDocumentPath, evaluationDocumentFolderPath); + } catch (Exception e) { + 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)); + } + + DocumentEntity entity = new DocumentEntity(); + entity.setFilePath(response.getFilePath()); + entity.setFileName(response.getFileName()); + entity.setSource(DocumentSourceTypeEnum.EVALUATION.getValue()); + entity.setType(DocumentTypeEnum.DOCUMENT.getValue()); + entity.setSourceId(evaluationId); + entity.setUploadedBy(userId); + documentRepository.save(entity); + + loggingUtil.addVersionHistory( + VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.INSERT).oldData(null).newData(entity).build()); + return entity; + } + public List getAllCompanyDocument(UserEntity user , Long companyId, CompanyDocumentTypeEnum typeEnum){ log.info("Fetching all company documents for Company ID '{}', User ID '{}', Type '{}'", companyId, user.getId(), typeEnum); CompanyEntity companyEntity = companyService.validateCompany(companyId); diff --git a/src/main/java/net/gepafin/tendermanagement/model/response/ApplicationEvaluationResponse.java b/src/main/java/net/gepafin/tendermanagement/model/response/ApplicationEvaluationResponse.java index 86788e88..d05f5816 100644 --- a/src/main/java/net/gepafin/tendermanagement/model/response/ApplicationEvaluationResponse.java +++ b/src/main/java/net/gepafin/tendermanagement/model/response/ApplicationEvaluationResponse.java @@ -25,7 +25,6 @@ public class ApplicationEvaluationResponse { private List checklist; private List files; private List evaluationDocument; - private List applicationCompanyDocuments; private List amendmentDetails; private LocalDateTime createdDate; private LocalDateTime updatedDate; From 949f3ca5cfa0d0fe7567fbd027cf0b6436e3821f Mon Sep 17 00:00:00 2001 From: rajesh Date: Mon, 30 Mar 2026 16:22:53 +0530 Subject: [PATCH 34/42] Updated code --- .../constants/GepafinConstant.java | 1 + .../dao/CompanyDocumentDao.java | 145 +++++++++++++----- src/main/resources/message_en.properties | 1 + src/main/resources/message_it.properties | 1 + 4 files changed, 108 insertions(+), 40 deletions(-) diff --git a/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java b/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java index cf39a573..a2c0f8c1 100644 --- a/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java +++ b/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java @@ -445,6 +445,7 @@ public class GepafinConstant { public static final String COMPANY_DOCUMENT_NOT_FOUND = "company.document.not.found"; public static final String COMPANY_DOCUMENT_UPDATED_SUCCESSFULLY = "company.document.updated.successfully"; public static final String COMPANY_DOCUMENT_COPIED_SUCCESSFULLY = "company.document.copied.successfully"; + public static final String COMPANY_DOCUMENT_COPY_EVALUATION_REQUIRES_APPLICATION_EVALUATION = "company.document.copy.evaluation.requires.application.evaluation"; public static final String COMPANY_DOCUMENT_FETCHED_SUCCESSFULLY = "company.document.fetched.successfully"; public static final String DOCUMENT_CATEGORY_CREATE_SUCCESS = "document.category.success"; diff --git a/src/main/java/net/gepafin/tendermanagement/dao/CompanyDocumentDao.java b/src/main/java/net/gepafin/tendermanagement/dao/CompanyDocumentDao.java index c8e87f88..b24736b3 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/CompanyDocumentDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/CompanyDocumentDao.java @@ -33,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; @@ -123,7 +124,7 @@ public class CompanyDocumentDao { 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.APPLICATION_DOCUMENT; + CompanyDocumentTypeEnum storedType = CompanyDocumentTypeEnum.COMPANY_DOCUMENT; List companyDocumentEntities = new ArrayList<>(); for (MultipartFile file : files) { log.info("Uploading instructor company document '{}' for companyId={}, applicationId={}", file.getOriginalFilename(), companyId, applicationId); @@ -348,42 +349,118 @@ public class CompanyDocumentDao { 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()); - String companyDocumentPath = companyDocumentEntity.getFilePath(); - String documentPath = s3ConfigBean.generateDocumentPath(DocumentSourceTypeEnum.APPLICATION,applicationEntity.getCall().getId(),applicationId,0L,0L,0L); + 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()); - log.info("Original Paths - oldPath: {}, newPath: {}", companyDocumentPath, documentPath); + if (beneficiaryOrConfidi) { + ApplicationEntity applicationEntity = applicationService.validateApplication(applicationId); + validator.validateUserWithCompany(request, companyDocumentEntity.getCompanyId()); - 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); + String companyDocumentPath = companyDocumentEntity.getFilePath(); + String documentPath = s3ConfigBean.generateDocumentPath(DocumentSourceTypeEnum.APPLICATION, applicationEntity.getCall().getId(), applicationId, 0L, 0L, 0L); + + log.info("Original Paths - oldPath: {}, newPath: {}", companyDocumentPath, documentPath); + + UploadFileOnAmazonS3Response response; + try { + response = amazonS3ServiceImpl.copyFile(companyDocumentEntity.getName(), companyDocumentPath, documentPath); + } catch (Exception e) { + log.error("Error occurred while uploading file from Amazon S3: {} for application ID '{}' and company Document ID '{}' ", e, applicationId, companyDocumentId); + throw new CustomValidationException(Status.VALIDATION_ERROR, + Translator.toLocale(GepafinConstant.UPLOAD_ERROR_S3)); + } + + DocumentEntity entity = new DocumentEntity(); + entity.setFilePath(response.getFilePath()); + entity.setFileName(response.getFileName()); + entity.setSource(DocumentSourceTypeEnum.APPLICATION.getValue()); + entity.setType(documentTypeEnum.getValue()); + entity.setSourceId(applicationId); + entity.setUploadedBy(userId); + + documentRepository.save(entity); + + /** This code is responsible for adding a version history log for the "inserting data" operation. **/ + loggingUtil.addVersionHistory( + VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.INSERT).oldData(null).newData(entity).build()); + + return callDao.convertToDocumentResponseBean(entity); + } + + if (staffEvaluationRoles) { + ApplicationEntity applicationEntity = applicationService.validateApplicationWithCompany(applicationId, companyDocumentEntity.getCompanyId()); + CompanyEntity companyEntity = companyService.validateCompany(companyDocumentEntity.getCompanyId()); + validator.validateHubId(request, companyEntity.getHub().getId()); + return addCompanyDocumentToEvaluation(applicationEntity, companyDocumentEntity, userId); + } + + throw new ForbiddenAccessException(Status.FORBIDDEN, Translator.toLocale(GepafinConstant.PERMISSION_DENIED)); + } + + /** + * Copies one company document into the evaluation S3 folder, persists a {@link DocumentEntity} with source + * {@link DocumentSourceTypeEnum#EVALUATION}, and appends the corresponding entry to + * {@link ApplicationEvaluationEntity#getEvaluationDocument()} (same JSON shape as instructor upload). + */ + private DocumentResponseBean addCompanyDocumentToEvaluation(ApplicationEntity application, + CompanyDocumentEntity companyDoc, Long userId) { + Long evaluationId = application.getApplicationEvaluationId(); + if (evaluationId == null || evaluationId <= 0) { + throw new CustomValidationException(Status.VALIDATION_ERROR, + Translator.toLocale(GepafinConstant.COMPANY_DOCUMENT_COPY_EVALUATION_REQUIRES_APPLICATION_EVALUATION)); + } + Optional evaluationOpt = + applicationEvaluationRepository.findByIdAndIsDeletedFalse(evaluationId); + if (evaluationOpt.isEmpty()) { + throw new CustomValidationException(Status.VALIDATION_ERROR, + Translator.toLocale(GepafinConstant.APPLICATION_EVALUATION_NOT_FOUND, evaluationId)); + } + + Long callId = application.getCall().getId(); + Long applicationId = application.getId(); + String evaluationFolderPath = s3ConfigBean.generateDocumentPath( + DocumentSourceTypeEnum.EVALUATION, callId, applicationId, evaluationId, 0L, 0L); + + DocumentEntity saved = persistInstructorCompanyDocumentAsEvaluationDocument( + companyDoc, evaluationFolderPath, evaluationId, userId); + EvaluationDocumentRequest entry = buildEvaluationDocumentEntry(companyDoc, saved); + if (entry == null) { throw new CustomValidationException(Status.VALIDATION_ERROR, Translator.toLocale(GepafinConstant.UPLOAD_ERROR_S3)); } - 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); + ApplicationEvaluationEntity evaluation = evaluationOpt.get(); + List merged = new ArrayList<>(parseEvaluationDocumentJson(evaluation.getEvaluationDocument())); + merged.add(entry); + evaluation.setEvaluationDocument(Utils.convertObjectToJson(merged)); + applicationEvaluationRepository.save(evaluation); - documentRepository.save(entity); + return callDao.convertToDocumentResponseBean(saved); + } - /** 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; + 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; } /** @@ -417,22 +494,10 @@ public class CompanyDocumentDao { for (CompanyDocumentEntity companyDoc : instructorCompanyDocuments) { DocumentEntity saved = persistInstructorCompanyDocumentAsEvaluationDocument( companyDoc, evaluationFolderPath, evaluationId, userId); - if (saved.getId() == null) { - continue; + EvaluationDocumentRequest entry = buildEvaluationDocumentEntry(companyDoc, saved); + if (entry != null) { + newEntries.add(entry); } - EvaluationDocumentRequest entry = new EvaluationDocumentRequest(); - SecureRandom random = new SecureRandom(); - - String key = NanoIdUtils.randomNanoId( - random, - NanoIdUtils.DEFAULT_ALPHABET, - 10 - ); - entry.setFieldId(key); - entry.setNameValue(instructorDocumentDisplayName(companyDoc)); - entry.setFileValue(String.valueOf(saved.getId())); - entry.setValid(null); - newEntries.add(entry); } if (newEntries.isEmpty()) { return; diff --git a/src/main/resources/message_en.properties b/src/main/resources/message_en.properties index b223a21d..5cb20603 100644 --- a/src/main/resources/message_en.properties +++ b/src/main/resources/message_en.properties @@ -389,6 +389,7 @@ company.document.fetched.successfully = Company Document fetched successfully. company.document.updated.successfully = Company Document Updated successfully. category.cannot.be.deleted = Category cannot be deleted as it is associated with company documents. company.document.copied.successfully = Company Document Copied successfully. +company.document.copy.evaluation.requires.application.evaluation=Copy to evaluation requires an application evaluation linked to this application. invalid.expiration.date = Invalid Expiration Date diff --git a/src/main/resources/message_it.properties b/src/main/resources/message_it.properties index bcff6a8f..1f210422 100644 --- a/src/main/resources/message_it.properties +++ b/src/main/resources/message_it.properties @@ -374,6 +374,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. From 3f257535157ed538687d7604c5a5caf6df01991e Mon Sep 17 00:00:00 2001 From: rajesh Date: Mon, 30 Mar 2026 19:56:45 +0530 Subject: [PATCH 35/42] Created new endpoint for application and fixed amendment issue --- .../constants/GepafinConstant.java | 2 + .../dao/ApplicationAmendmentRequestDao.java | 35 +++++++- .../tendermanagement/dao/ApplicationDao.java | 82 +++++++++++++++++++ .../enums/ApplicationStatusForEvaluation.java | 4 +- .../enums/ApplicationStatusTypeEnum.java | 4 +- .../enums/RoleStatusEnum.java | 3 +- .../service/ApplicationService.java | 2 + .../service/impl/ApplicationServiceImpl.java | 7 ++ .../tendermanagement/util/Validator.java | 20 +++++ .../web/rest/api/ApplicationApi.java | 15 ++++ .../api/impl/ApplicationApiController.java | 14 ++++ src/main/resources/message_en.properties | 2 + src/main/resources/message_it.properties | 4 +- 13 files changed, 186 insertions(+), 8 deletions(-) diff --git a/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java b/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java index cf39a573..daeef83f 100644 --- a/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java +++ b/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java @@ -205,6 +205,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"; diff --git a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationAmendmentRequestDao.java b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationAmendmentRequestDao.java index ae26a2a1..d2d3bace 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationAmendmentRequestDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationAmendmentRequestDao.java @@ -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 existingAmendmentsForEval = applicationAmendmentRequestRepository.findAllByApplicationEvaluationIdAndIsDeletedFalse(applicationEvaluationEntity.getId()); + boolean noneClosedOrExpiredForSpecial = existingAmendmentsForEval.stream() + .noneMatch(amendment -> + amendment.getStatus().equals(ApplicationAmendmentRequestEnum.CLOSE.getValue()) || + amendment.getStatus().equals(ApplicationAmendmentRequestEnum.EXPIRED.getValue()) + ); + if (Boolean.TRUE.equals(noneClosedOrExpiredForSpecial)) { + applicationEntity.setPreviousStatus(oldApplicationEntity.getStatus()); + } ApplicationAmendmentRequestEntity applicationAmendmentRequestEntity = new ApplicationAmendmentRequestEntity(); if(Boolean.TRUE.equals(applicationAmendmentRequest.getAmendmentDocumentType().equals(AmendmentDocumentTypeEnum.ALTRE_GARANZIE))) { applicationAmendmentRequestEntity.setResponseDays(20l); diff --git a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java index 044e78fc..346554b6 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java @@ -69,6 +69,7 @@ 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.regex.Matcher; @@ -1047,6 +1048,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); @@ -1120,6 +1125,83 @@ 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); + } + + /** + * Sets all non-terminal amendments, the application evaluation (if any), and the assigned application row to CLOSE. + */ + private void closeAmendmentsEvaluationAndAssignedApplicationForRegistry(HttpServletRequest httpRequest, Long applicationId) { + LocalDateTime nowUtc = DateTimeUtil.DateServerToUTC(LocalDateTime.now()); + + List amendments = applicationAmendmentRequestRepository.findByApplicationIdAndIsDeletedFalse(applicationId); + for (ApplicationAmendmentRequestEntity amendment : amendments) { + if (ApplicationAmendmentRequestEnum.CLOSE.getValue().equals(amendment.getStatus()) + || ApplicationAmendmentRequestEnum.EXPIRED.getValue().equals(amendment.getStatus()) + || ApplicationAmendmentRequestEnum.REJECTED.getValue().equals(amendment.getStatus())) { + continue; + } + ApplicationAmendmentRequestEntity oldAmendment = Utils.getClonedEntityForData(amendment); + amendment.setStatus(ApplicationAmendmentRequestEnum.CLOSE.getValue()); + amendment.setClosingDate(nowUtc); + applicationAmendmentRequestRepository.save(amendment); + loggingUtil.addVersionHistory( + VersionHistoryRequest.builder().request(httpRequest).actionType(VersionActionTypeEnum.UPDATE).oldData(oldAmendment).newData(amendment).build()); + } + + applicationEvaluationRepository.findByApplicationIdAndIsDeletedFalse(applicationId).ifPresent(evaluation -> { + if (ApplicationEvaluationStatusTypeEnum.CLOSE.getValue().equals(evaluation.getStatus())) { + return; + } + ApplicationEvaluationEntity oldEvaluation = Utils.getClonedEntityForData(evaluation); + evaluation.setStatus(ApplicationEvaluationStatusTypeEnum.CLOSE.getValue()); + evaluation.setClosingDate(nowUtc); + if (evaluation.getStartDate() != null && evaluation.getClosingDate() != null) { + long activeDays = ChronoUnit.DAYS.between(evaluation.getStartDate(), evaluation.getClosingDate()); + activeDays -= evaluation.getSuspendedDays() != null ? evaluation.getSuspendedDays() : 0; + evaluation.setActiveDays(activeDays); + } + applicationEvaluationRepository.save(evaluation); + loggingUtil.addVersionHistory( + VersionHistoryRequest.builder().request(httpRequest).actionType(VersionActionTypeEnum.UPDATE).oldData(oldEvaluation).newData(evaluation).build()); + }); + + assignedApplicationsRepository.findByApplicationIdAndIsDeletedFalse(applicationId).ifPresent(assigned -> { + if (AssignedApplicationEnum.CLOSE.getValue().equals(assigned.getStatus())) { + return; + } + AssignedApplicationsEntity oldAssigned = Utils.getClonedEntityForData(assigned); + assigned.setStatus(AssignedApplicationEnum.CLOSE.getValue()); + assignedApplicationsRepository.save(assigned); + loggingUtil.addVersionHistory( + VersionHistoryRequest.builder().request(httpRequest).actionType(VersionActionTypeEnum.UPDATE).oldData(oldAssigned).newData(assigned).build()); + }); + } + public Integer calculateProgress(Long totalSteps, Long completedSteps) { if (FieldValidator.isNullOrZero(totalSteps)) { throw new CustomValidationException(Status.BAD_REQUEST,Translator.toLocale(GepafinConstant.TOTAL_STEPS_NOT_BE_ZERO)); diff --git a/src/main/java/net/gepafin/tendermanagement/enums/ApplicationStatusForEvaluation.java b/src/main/java/net/gepafin/tendermanagement/enums/ApplicationStatusForEvaluation.java index 1a03e846..3055d749 100644 --- a/src/main/java/net/gepafin/tendermanagement/enums/ApplicationStatusForEvaluation.java +++ b/src/main/java/net/gepafin/tendermanagement/enums/ApplicationStatusForEvaluation.java @@ -8,7 +8,9 @@ public enum ApplicationStatusForEvaluation { ADMISSIBLE("ADMISSIBLE"), TECHNICAL_EVALUATION("TECHNICAL_EVALUATION"), AWAITING_TECHNICAL_EVALUATION("AWAITING_TECHNICAL_EVALUATION"), - TECHNICAL_EVALUATION_REJECTED("TECHNICAL_EVALUATION_REJECTED"); + TECHNICAL_EVALUATION_REJECTED("TECHNICAL_EVALUATION_REJECTED"), + DELETED("DELETED"), + DELETED_CONFIRMED("DELETED_CONFIRMED");; private String value; diff --git a/src/main/java/net/gepafin/tendermanagement/enums/ApplicationStatusTypeEnum.java b/src/main/java/net/gepafin/tendermanagement/enums/ApplicationStatusTypeEnum.java index d686a347..0796f22a 100644 --- a/src/main/java/net/gepafin/tendermanagement/enums/ApplicationStatusTypeEnum.java +++ b/src/main/java/net/gepafin/tendermanagement/enums/ApplicationStatusTypeEnum.java @@ -20,7 +20,9 @@ public enum ApplicationStatusTypeEnum { TECHNICAL_EVALUATION_REJECTED("TECHNICAL_EVALUATION_REJECTED"), AWAITING_TECHNICAL_EVALUATION("AWAITING_TECHNICAL_EVALUATION"), AWAITING_CONTRACT("AWAITING_CONTRACT"), - CONTRACT_SIGNED("CONTRACT_SIGNED"); + CONTRACT_SIGNED("CONTRACT_SIGNED"), + DELETED("DELETED"), + DELETED_CONFIRMED("DELETED_CONFIRMED"); private String value; diff --git a/src/main/java/net/gepafin/tendermanagement/enums/RoleStatusEnum.java b/src/main/java/net/gepafin/tendermanagement/enums/RoleStatusEnum.java index 0891dc93..e332583f 100644 --- a/src/main/java/net/gepafin/tendermanagement/enums/RoleStatusEnum.java +++ b/src/main/java/net/gepafin/tendermanagement/enums/RoleStatusEnum.java @@ -9,7 +9,8 @@ public enum RoleStatusEnum { ROLE_PRE_INSTRUCTOR("ROLE_PRE_INSTRUCTOR"), ROLE_GEPAFIN_OPERATOR("ROLE_GEPAFIN_OPERATOR"), ROLE_INSTRUCTOR_MANAGER("ROLE_INSTRUCTOR_MANAGER"), - ROLE_CONFIDI("ROLE_CONFIDI"); + ROLE_CONFIDI("ROLE_CONFIDI"), + ROLE_DIRECTOR("ROLE_DIRECTOR"); private String value; diff --git a/src/main/java/net/gepafin/tendermanagement/service/ApplicationService.java b/src/main/java/net/gepafin/tendermanagement/service/ApplicationService.java index a1206ee3..bce96ba9 100644 --- a/src/main/java/net/gepafin/tendermanagement/service/ApplicationService.java +++ b/src/main/java/net/gepafin/tendermanagement/service/ApplicationService.java @@ -34,6 +34,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); diff --git a/src/main/java/net/gepafin/tendermanagement/service/impl/ApplicationServiceImpl.java b/src/main/java/net/gepafin/tendermanagement/service/impl/ApplicationServiceImpl.java index 12769ac7..c3abf79e 100644 --- a/src/main/java/net/gepafin/tendermanagement/service/impl/ApplicationServiceImpl.java +++ b/src/main/java/net/gepafin/tendermanagement/service/impl/ApplicationServiceImpl.java @@ -107,6 +107,13 @@ public class ApplicationServiceImpl implements ApplicationService { return applicationDao.updateApplicationStatus(request, applicationId, status); } + @Override + @Transactional(rollbackFor = Exception.class) + public ApplicationResponse recordApplicationRegistrySegment(HttpServletRequest request, Long applicationId,ApplicationStatusTypeEnum status) { + UserEntity userEntity = validator.validateUser(request); + return applicationDao.recordApplicationRegistrySegment(request, applicationId, status); + } + @Override @Transactional(readOnly = true) public List getAllApplications(HttpServletRequest request, Long callId, Long companyId ,List statusList) { diff --git a/src/main/java/net/gepafin/tendermanagement/util/Validator.java b/src/main/java/net/gepafin/tendermanagement/util/Validator.java index 895e63d9..e946c170 100644 --- a/src/main/java/net/gepafin/tendermanagement/util/Validator.java +++ b/src/main/java/net/gepafin/tendermanagement/util/Validator.java @@ -197,6 +197,26 @@ public class Validator { } return false; } + + public Boolean checkIsDirector() { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + if (authentication != null && authentication.isAuthenticated()) { + for (GrantedAuthority authority : authentication.getAuthorities()) { + if (RoleStatusEnum.ROLE_DIRECTOR.getValue().equals(authority.getAuthority())) { + return true; + } + } + } + return false; + } + + /** Super admin (any hub) or director (hub must match application — enforced separately). */ + public void validateSuperAdminOrDirector() { + if (Boolean.TRUE.equals(checkIsSuperAdmin()) || Boolean.TRUE.equals(checkIsDirector())) { + return; + } + throw new ForbiddenAccessException(Status.FORBIDDEN, Translator.toLocale(GepafinConstant.PERMISSION_DENIED)); + } public Boolean checkIsConfidi() { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if (authentication != null && authentication.isAuthenticated()) { diff --git a/src/main/java/net/gepafin/tendermanagement/web/rest/api/ApplicationApi.java b/src/main/java/net/gepafin/tendermanagement/web/rest/api/ApplicationApi.java index 964418bd..92308f56 100644 --- a/src/main/java/net/gepafin/tendermanagement/web/rest/api/ApplicationApi.java +++ b/src/main/java/net/gepafin/tendermanagement/web/rest/api/ApplicationApi.java @@ -132,6 +132,21 @@ public interface ApplicationApi { @Parameter(description = "The application id", required = true) @PathVariable("applicationId") Long applicationId, @Parameter(description = "status", required = true)@RequestParam(value = "status", required = true) ApplicationStatusTypeEnum status); + @Operation(summary = "Api to update application segment", + 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}/segment", produces = { "application/json" }) + @PreAuthorize("hasRole('ROLE_SUPER_ADMIN') || hasRole('ROLE_DIRECTOR')") + ResponseEntity> updateApplicationSegment(HttpServletRequest request, + @Parameter(description = "The application id", required = true) @PathVariable("applicationId") Long applicationId, + @Parameter(description = "Application status", required = true) @RequestParam ApplicationStatusTypeEnum status); + @Operation(summary = "API to generate PDF for an application", responses = { @ApiResponse(responseCode = "200", description = "OK", content = @Content(mediaType = "application/pdf")), diff --git a/src/main/java/net/gepafin/tendermanagement/web/rest/api/impl/ApplicationApiController.java b/src/main/java/net/gepafin/tendermanagement/web/rest/api/impl/ApplicationApiController.java index c5817500..3e850280 100644 --- a/src/main/java/net/gepafin/tendermanagement/web/rest/api/impl/ApplicationApiController.java +++ b/src/main/java/net/gepafin/tendermanagement/web/rest/api/impl/ApplicationApiController.java @@ -22,6 +22,8 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import jakarta.validation.Valid; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; @@ -141,6 +143,18 @@ public class ApplicationApiController implements ApplicationApi { .body(new Response<>(applicationResponse, Status.SUCCESS, Translator.toLocale(GepafinConstant.APPLICATION_STATUS_UPDATED_SUCCESSFULLY))); } + @Override + public ResponseEntity> updateApplicationSegment(HttpServletRequest request, Long applicationId, + ApplicationStatusTypeEnum status) { + + loggingUtil.logUserAction( + UserActionRequest.builder().request(request).actionType(UserActionLogsEnum.UPDATE).actionContext(UserActionContextEnum.UPDATE_APPLICATION_STATUS).build()); + + ApplicationResponse applicationResponse = applicationService.recordApplicationRegistrySegment(request, applicationId, status); + return ResponseEntity.status(HttpStatus.OK) + .body(new Response<>(applicationResponse, Status.SUCCESS, Translator.toLocale(GepafinConstant.APPLICATION_STATUS_UPDATED_SUCCESSFULLY))); + } + @Override public ResponseEntity generateApplicationPdf(HttpServletRequest request, Long applicationId) { diff --git a/src/main/resources/message_en.properties b/src/main/resources/message_en.properties index b223a21d..fb31b0bc 100644 --- a/src/main/resources/message_en.properties +++ b/src/main/resources/message_en.properties @@ -230,6 +230,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. diff --git a/src/main/resources/message_it.properties b/src/main/resources/message_it.properties index bcff6a8f..b9acf4f9 100644 --- a/src/main/resources/message_it.properties +++ b/src/main/resources/message_it.properties @@ -425,4 +425,6 @@ upload.company.document.to.application=Documento aziendale caricato correttament company.document.not.found.with.ids=Documento aziendale non trovato. ID mancanti: {0} amount.field.not.provided= Si prega di fornire i campi obbligatori per l'importo. vat.or.tax.code.required=Č obbligatorio il numero di partita IVA o il codice fiscale. -provide.valid.vat.number=Inserisci un numero di partita IVA valido per procedere con NDG. \ No newline at end of file +provide.valid.vat.number=Inserisci un numero di partita IVA valido per procedere con NDG. +application.status.transition.restricted=Questa modifica di stato non č disponibile tramite questa operazione. +application.registry.segment.invalid=Valore del segmento non valido. \ No newline at end of file From 6ae1963c47b9aa621d6174b70aaf30b0d61f28bd Mon Sep 17 00:00:00 2001 From: rajesh Date: Mon, 30 Mar 2026 20:25:46 +0530 Subject: [PATCH 36/42] Updated code --- .../gepafin/tendermanagement/web/rest/api/ApplicationApi.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/net/gepafin/tendermanagement/web/rest/api/ApplicationApi.java b/src/main/java/net/gepafin/tendermanagement/web/rest/api/ApplicationApi.java index 92308f56..6dfbf1fd 100644 --- a/src/main/java/net/gepafin/tendermanagement/web/rest/api/ApplicationApi.java +++ b/src/main/java/net/gepafin/tendermanagement/web/rest/api/ApplicationApi.java @@ -132,7 +132,7 @@ 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 segment", + @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 = { @@ -141,7 +141,7 @@ public interface ApplicationApi { @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}/segment", produces = { "application/json" }) + @PatchMapping(value = "/{applicationId}/external/status", produces = { "application/json" }) @PreAuthorize("hasRole('ROLE_SUPER_ADMIN') || hasRole('ROLE_DIRECTOR')") ResponseEntity> updateApplicationSegment(HttpServletRequest request, @Parameter(description = "The application id", required = true) @PathVariable("applicationId") Long applicationId, From ed856947b5dfbf39cbe44ee1e800adde754c538e Mon Sep 17 00:00:00 2001 From: rajesh Date: Thu, 2 Apr 2026 16:11:23 +0530 Subject: [PATCH 37/42] Implemented spreadsheet logic --- .../constants/GepafinConstant.java | 3 + .../dao/ApplicationEvaluationDao.java | 129 ++++++++++++++++++ src/main/resources/application.properties | 3 + 3 files changed, 135 insertions(+) diff --git a/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java b/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java index 43e5d1ea..32f13ea7 100644 --- a/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java +++ b/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java @@ -141,6 +141,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"; diff --git a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationEvaluationDao.java b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationEvaluationDao.java index 6b8d5d50..c0ad6ca2 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationEvaluationDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationEvaluationDao.java @@ -20,8 +20,14 @@ import org.apache.commons.lang3.StringUtils; import org.json.JSONObject; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Component; import org.springframework.util.CollectionUtils; +import org.springframework.web.client.RestClientException; +import org.springframework.web.client.RestTemplate; import org.springframework.web.multipart.MultipartFile; import java.math.BigDecimal; @@ -157,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; @@ -762,6 +774,11 @@ public class ApplicationEvaluationDao { // Map placeHolders = notificationDao.sendNotificationToBeneficiary(application, NotificationTypeEnum.EVALUATION_CREATION); + // V2 only, idempotent: runs on every create/update of application evaluation (e.g. evaluation created at + // instructor assignment). External fill API is called only for spreadsheet fields that still have no stored + // value. If APPLICATION_EVALUATION_FORM / spreadsheet rows were created here earlier, later + // createApplicationEvaluation and plain updates skip re-fill and leave existing flow unchanged. + populateSpreadsheetEvaluationFormFieldsForV2(application, entity); Map placeHolders = new HashMap<>(); placeHolders.put("{{call_name}}", application.getCall().getName()); String protocolNumber=application.getProtocol().getExternalProtocolNumber(); @@ -2190,6 +2207,118 @@ public class ApplicationEvaluationDao { return response; } + /** + * Evaluation V2 only ({@link ApplicationEntity#getEvaluationVersion()}). + *

+ * Ensures {@link ApplicationEvaluationFormEntity} exists for the call's evaluation form, then for each + * {@code spreadsheet} field calls the external fill API and saves {@code APPLICATION_EVALUATION_FORM_FIELD} + * only when that field has no stored value yet. Safe when this runs at assignment-time (first evaluation create) + * and on later {@code createOrUpdateApplicationEvaluation} updates: no second API call and no overwrite once a + * value is present. {@link #createApplicationEvaluation} continues to work: it reuses the same form row via + * {@link #getApplicationEvaluationFormOrCreate}. + */ + private void populateSpreadsheetEvaluationFormFieldsForV2(ApplicationEntity application, ApplicationEvaluationEntity entity) { + if (!EvaluationVersionEnum.V2.getValue().equals(application.getEvaluationVersion())) { + return; + } + EvaluationFormEntity evaluationFormEntity = evaluationFormRepository.findByCallIdAndIsDeletedFalse(application.getCall().getId()); + if (evaluationFormEntity == null) { + return; + } + ApplicationEvaluationFormEntity applicationEvaluationFormEntity = getApplicationEvaluationFormOrCreate(evaluationFormEntity, entity); + List contentResponseBeans = evaluationFormDao + .convertEvaluationFormEntityToEvaluationFormResponseBean(evaluationFormEntity) + .getContent(); + if (CollectionUtils.isEmpty(contentResponseBeans)) { + return; + } + List spreadsheetUpdates = new ArrayList<>(); + for (ContentResponseBean contentResponseBean : contentResponseBeans) { + if (!GepafinConstant.SPREADSHEET.equals(contentResponseBean.getName())) { + continue; + } + String fieldId = contentResponseBean.getId(); + Optional existingSpreadsheetField = applicationEvaluationFormFieldRepository + .findByApplicationEvaluationFormIdAndFieldIdAndIsDeletedFalse(applicationEvaluationFormEntity.getId(), fieldId); + if (existingSpreadsheetField.isPresent() + && StringUtils.isNotBlank(existingSpreadsheetField.get().getFieldValue())) { + continue; + } + Object populatedSpreadsheetValue = fetchSpreadsheetValueFromExternalApi( + application.getCall().getId(), + application.getId(), + contentResponseBean + ); + if (populatedSpreadsheetValue == null) { + log.warn("Skipping spreadsheet field {}: external API did not return a template workbook value", fieldId); + continue; + } + ApplicationFormFieldRequestBean spreadsheetFieldRequest = new ApplicationFormFieldRequestBean(); + spreadsheetFieldRequest.setFieldId(fieldId); + spreadsheetFieldRequest.setFieldValue(populatedSpreadsheetValue); + spreadsheetUpdates.add(spreadsheetFieldRequest); + } + if (!spreadsheetUpdates.isEmpty()) { + createOrUpdateMultipleFormFields(spreadsheetUpdates, applicationEvaluationFormEntity, evaluationFormEntity); + } + } + + private Object fetchSpreadsheetValueFromExternalApi(Long callId, Long applicationId, ContentResponseBean spreadsheetField) { + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + if (StringUtils.isNotBlank(excelFillApiToken)) { + headers.set(GepafinConstant.AUTHORIZATION, GepafinConstant.BEARER_PREFIX + excelFillApiToken); + } + + Map requestBody = new LinkedHashMap<>(); + requestBody.put("call_id", callId); + requestBody.put("application_id", applicationId); + requestBody.put(GepafinConstant.TEMPLATE, spreadsheetField); + + try { + RestTemplate restTemplate = new RestTemplate(); + ResponseEntity response = restTemplate.postForEntity( + excelFillApiUrl, + new HttpEntity<>(requestBody, headers), + Object.class + ); + return resolveSpreadsheetFieldValueFromApiResponse(response.getBody()); + } catch (RestClientException exception) { + log.error("Failed to populate spreadsheet field {} from external API", spreadsheetField.getId(), exception); + throw new CustomValidationException(Status.BAD_REQUEST, + "Failed to populate spreadsheet field from external API for fieldId: " + spreadsheetField.getId()); + } + } + + /** + * External fill API returns a spreadsheet field-shaped JSON (id, name, settings, …). Persist only the + * workbook object: {@code settings[name=template].value}. If the response is already a bare workbook + * ({@code sheets} / {@code sheetOrder}), it is stored as-is. + */ + private Object resolveSpreadsheetFieldValueFromApiResponse(Object apiResponseBody) { + if (apiResponseBody == null) { + return null; + } + if (!(apiResponseBody instanceof Map map)) { + return apiResponseBody; + } + Object settingsObj = map.get("settings"); + if (settingsObj instanceof List settingsList) { + for (Object setting : settingsList) { + if (!(setting instanceof Map settingMap)) { + continue; + } + Object name = settingMap.get("name"); + if (GepafinConstant.TEMPLATE.equals(name != null ? name.toString() : null)) { + return settingMap.get("value"); + } + } + log.warn("External spreadsheet API returned settings[] without a template entry"); + return null; + } + return apiResponseBody; + } + private ApplicationEvaluationFormEntity getApplicationEvaluationFormOrCreate(EvaluationFormEntity evaluationFormEntity, ApplicationEvaluationEntity applicationEvaluationEntity) { ApplicationEvaluationFormEntity applicationEvaluationFormEntity = applicationEvaluationFormRepository.findByEvaluationIdAndEvaluationFormId(applicationEvaluationEntity.getId(), evaluationFormEntity.getId()); diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 7fffc70b..95d1d9b7 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -77,6 +77,9 @@ spring.rabbitmq.connection-timeout=120000 app.bandi.login.url.suffix=/loginadmin app.confidi.login.url.suffix=/confidi +excel.fill.api.url=https://excel-gepafin-dev-be.bflows.ai/excel/fill +excel.fill.api.token=a3f8c2d1e4b5a6f7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1 + #sviluppumbria protocol codAoo=SVILUMBRIA-01 sviluppumbria.username=protocollatoresvilumbria From 492f0e7dfddc8c1210c033408c9817508dacc312 Mon Sep 17 00:00:00 2001 From: rajesh Date: Fri, 3 Apr 2026 15:19:18 +0530 Subject: [PATCH 38/42] Updated logic spreadsheet --- .../tendermanagement/dao/ApplicationEvaluationDao.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationEvaluationDao.java b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationEvaluationDao.java index c0ad6ca2..01aa9988 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationEvaluationDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationEvaluationDao.java @@ -2225,13 +2225,18 @@ public class ApplicationEvaluationDao { if (evaluationFormEntity == null) { return; } - ApplicationEvaluationFormEntity applicationEvaluationFormEntity = getApplicationEvaluationFormOrCreate(evaluationFormEntity, entity); List contentResponseBeans = evaluationFormDao .convertEvaluationFormEntityToEvaluationFormResponseBean(evaluationFormEntity) .getContent(); if (CollectionUtils.isEmpty(contentResponseBeans)) { return; } + boolean evaluationFormHasSpreadsheet = contentResponseBeans.stream() + .anyMatch(c -> GepafinConstant.SPREADSHEET.equals(c.getName())); + if (!evaluationFormHasSpreadsheet) { + return; + } + ApplicationEvaluationFormEntity applicationEvaluationFormEntity = getApplicationEvaluationFormOrCreate(evaluationFormEntity, entity); List spreadsheetUpdates = new ArrayList<>(); for (ContentResponseBean contentResponseBean : contentResponseBeans) { if (!GepafinConstant.SPREADSHEET.equals(contentResponseBean.getName())) { From 8e0cffb61c57536030c0e9dd9408242fbe913024 Mon Sep 17 00:00:00 2001 From: rajesh Date: Wed, 8 Apr 2026 15:53:34 +0530 Subject: [PATCH 39/42] Done ticket GEPAFINBE-6326 Ranking Management --- .../constants/GepafinConstant.java | 6 + .../tendermanagement/dao/ApplicationDao.java | 117 ++++++++++ .../gepafin/tendermanagement/dao/CallDao.java | 71 ++++++- .../entities/ApplicationEntity.java | 6 + .../entities/ApplicationRankingView.java | 68 ++++++ .../tendermanagement/entities/CallEntity.java | 3 + .../ApplicationRankingActionTypeEnum.java | 20 ++ .../enums/CallRankingTypeEnum.java | 20 ++ .../enums/UserActionContextEnum.java | 5 +- .../ApplicationRankingActionRequest.java | 13 ++ .../model/request/CreateCallRequestStep1.java | 3 + .../model/request/UpdateCallRequestStep1.java | 5 +- .../response/ApplicationRankingResponse.java | 28 +++ .../model/response/ApplicationResponse.java | 5 + .../response/CallRankingSummaryResponse.java | 19 ++ .../model/response/CallResponse.java | 3 + .../ApplicationRankingViewRepository.java | 11 + .../repositories/ApplicationRepository.java | 1 + .../service/ApplicationService.java | 7 + .../tendermanagement/service/CallService.java | 3 + .../service/impl/ApplicationServiceImpl.java | 17 ++ .../service/impl/CallServiceImpl.java | 16 ++ .../web/rest/api/RankingApi.java | 84 ++++++++ .../rest/api/impl/RankingApiController.java | 74 +++++++ .../db/changelog/db.changelog-1.0.0.xml | 18 ++ ...te_application_ranking_view_07_04_2026.sql | 201 ++++++++++++++++++ src/main/resources/message_en.properties | 6 + src/main/resources/message_it.properties | 12 +- 28 files changed, 834 insertions(+), 8 deletions(-) create mode 100644 src/main/java/net/gepafin/tendermanagement/entities/ApplicationRankingView.java create mode 100644 src/main/java/net/gepafin/tendermanagement/enums/ApplicationRankingActionTypeEnum.java create mode 100644 src/main/java/net/gepafin/tendermanagement/enums/CallRankingTypeEnum.java create mode 100644 src/main/java/net/gepafin/tendermanagement/model/request/ApplicationRankingActionRequest.java create mode 100644 src/main/java/net/gepafin/tendermanagement/model/response/ApplicationRankingResponse.java create mode 100644 src/main/java/net/gepafin/tendermanagement/model/response/CallRankingSummaryResponse.java create mode 100644 src/main/java/net/gepafin/tendermanagement/repositories/ApplicationRankingViewRepository.java create mode 100644 src/main/java/net/gepafin/tendermanagement/web/rest/api/RankingApi.java create mode 100644 src/main/java/net/gepafin/tendermanagement/web/rest/api/impl/RankingApiController.java create mode 100644 src/main/resources/db/dump/create_application_ranking_view_07_04_2026.sql diff --git a/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java b/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java index 43e5d1ea..dbdf043b 100644 --- a/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java +++ b/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java @@ -108,6 +108,12 @@ public class GepafinConstant { public static final String INVALID_STATUS_CHANGE_FROM_PUBLISH_TO_DRAFT = "invalid.status.change.from.publish.to.draft"; public static final String STATUS_CANNOT_BE_CHANGED = "status.cannot.be.changed"; public static final String PUBLISHED_CALL_NOT_UPDATE = "published.call.not.update"; + public static final String PUBLISHED_CALL_STEP1_ONLY_RANKING_TYPE_ALLOWED = "published.call.step1.only.ranking.type.allowed"; + public static final String CALL_RANKING_TYPE_UPDATED_SUCCESSFULLY = "call.ranking.type.updated.successfully"; + public static final String APPLICATION_RANKING_ACTION_UPDATED_SUCCESSFULLY = "application.ranking.action.updated.successfully"; + public static final String APPLICATION_RANKING_FETCHED_SUCCESSFULLY = "application.ranking.fetched.successfully"; + public static final String CALL_MUST_BE_CLOSED_FOR_RANKING_ACTION = "call.must.be.closed.for.ranking.action"; + public static final String APPLICATION_RANKING_ACTION_INVALID = "application.ranking.action.invalid"; public static final String INVALID_USER = "invalid_user"; public static final String FLOW_CREATED_SUCCESSFULLY = "flow.created.successfully"; public static final String FLOW_FETCHED_SUCCESSFULLY = "flow.fetched.successfully"; diff --git a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java index 346554b6..631f8733 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java @@ -72,6 +72,7 @@ import java.time.OffsetDateTime; import java.time.temporal.ChronoUnit; import java.time.format.DateTimeFormatter; import java.util.*; +import java.util.Objects; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -215,6 +216,9 @@ public class ApplicationDao { @Autowired private ApplicationFormViewRepository applicationFormViewRepository; + @Autowired + private ApplicationRankingViewRepository applicationRankingViewRepository; + @Autowired private FormRepository formRepository; @@ -509,6 +513,11 @@ public class ApplicationDao { responseBean.setDateAccepted(applicationEntity.getDateAccepted()); responseBean.setDateRejected(applicationEntity.getDateRejected()); responseBean.setNdg(applicationEntity.getNdg()); + if (applicationEntity.getRankingActionType() != null && !applicationEntity.getRankingActionType().isBlank()) { + responseBean.setRankingActionType( + ApplicationRankingActionTypeEnum.valueOf(applicationEntity.getRankingActionType().trim())); + } + responseBean.setManualRanking(applicationEntity.getManualRanking()); return responseBean; } @@ -1152,6 +1161,114 @@ public class ApplicationDao { return getApplicationResponse(applicationEntity); } + public ApplicationResponse updateApplicationRankingAction(HttpServletRequest request, Long applicationId, + ApplicationRankingActionTypeEnum rankingActionType, Long manualRanking) { + ApplicationEntity applicationEntity = validateApplication(applicationId); + validator.validateRequest(request, RoleStatusEnum.ROLE_SUPER_ADMIN); + validateCallClosedForRankingAction(applicationEntity.getCall()); + if (!ApplicationStatusTypeEnum.APPROVED.getValue().equals(applicationEntity.getStatus())) { + throw new CustomValidationException(Status.BAD_REQUEST, + Translator.toLocale(GepafinConstant.APPLICATION_RANKING_ACTION_INVALID)); + } + validateRankingActionRequest(rankingActionType, manualRanking); + ApplicationEntity oldApplicationEntity = Utils.getClonedEntityForData(applicationEntity); + if (rankingActionType == null) { + applicationEntity.setRankingActionType(null); + applicationEntity.setManualRanking(null); + } else { + if (rankingActionType == ApplicationRankingActionTypeEnum.REPOSITION + && applicationRepository.existsByCallIdAndManualRankingAndIsDeletedFalseAndIdNot( + applicationEntity.getCall().getId(), manualRanking, applicationEntity.getId())) { + throw new CustomValidationException(Status.BAD_REQUEST, + Translator.toLocale(GepafinConstant.APPLICATION_RANKING_ACTION_INVALID)); + } + applicationEntity.setRankingActionType(rankingActionType.getValue()); + applicationEntity.setManualRanking( + rankingActionType == ApplicationRankingActionTypeEnum.REPOSITION ? manualRanking : null); + } + applicationEntity = applicationRepository.save(applicationEntity); + loggingUtil.addVersionHistory( + VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE) + .oldData(oldApplicationEntity).newData(applicationEntity).build()); + return getApplicationResponse(applicationEntity); + } + + public CallRankingSummaryResponse getApplicationRanking(Long callId, + List rankingActionTypes) { + CallEntity call = callRepository.findById(callId) + .orElseThrow(() -> new ResourceNotFoundException(Status.NOT_FOUND, + Translator.toLocale(GepafinConstant.CALL_NOT_FOUND))); + + Specification spec = (root, query, criteriaBuilder) -> { + List predicates = new ArrayList<>(); + predicates.add(criteriaBuilder.equal(root.get("callId"), callId)); + if (rankingActionTypes != null && !rankingActionTypes.isEmpty()) { + List types = rankingActionTypes.stream() + .filter(Objects::nonNull) + .map(ApplicationRankingActionTypeEnum::getValue) + .distinct() + .collect(Collectors.toList()); + if (!types.isEmpty()) { + predicates.add(root.get("rankingActionType").in(types)); + } + } + query.orderBy(criteriaBuilder.asc(root.get("rank"))); + return criteriaBuilder.and(predicates.toArray(new jakarta.persistence.criteria.Predicate[0])); + }; + List rows = applicationRankingViewRepository.findAll(spec); + + CallRankingSummaryResponse summary = new CallRankingSummaryResponse(); + summary.setCallId(call.getId()); + summary.setCallName(call.getName()); + summary.setAmount(call.getAmount()); + if (call.getRankingType() != null && !call.getRankingType().isBlank()) { + summary.setRankingType(CallRankingTypeEnum.valueOf(call.getRankingType().trim())); + } + summary.setApplications(rows.stream() + .map(this::convertToApplicationRankingResponse) + .collect(Collectors.toList())); + return summary; + } + + private ApplicationRankingResponse convertToApplicationRankingResponse(ApplicationRankingView entity) { + ApplicationRankingResponse response = new ApplicationRankingResponse(); + response.setApplicationId(entity.getApplicationId()); + response.setCallId(entity.getCallId()); + response.setUserId(entity.getUserId()); + response.setStatus(entity.getStatus()); + response.setSubmissionDate(entity.getSubmissionDate()); + response.setProtocolDatetime(entity.getProtocolDatetime()); + response.setProtocolNumber(entity.getProtocolNumber()); + response.setNdg(entity.getNdg()); + response.setAmountAccepted(entity.getAmountAccepted()); + response.setPecEmail(entity.getPecEmail()); + response.setManualRanking(entity.getManualRanking()); + response.setRank(entity.getRank()); + response.setTotalScore(entity.getTotalScore()); + if (entity.getRankingActionType() != null && !entity.getRankingActionType().isBlank()) { + response.setRankingActionType(ApplicationRankingActionTypeEnum.valueOf(entity.getRankingActionType().trim())); + } + if (entity.getRankingType() != null && !entity.getRankingType().isBlank()) { + response.setRankingType(CallRankingTypeEnum.valueOf(entity.getRankingType().trim())); + } + return response; + } + + private void validateCallClosedForRankingAction(CallEntity callEntity) { + if (!CallStatusEnum.EXPIRED.getValue().equals(callEntity.getStatus())) { + throw new CustomValidationException(Status.BAD_REQUEST, + Translator.toLocale(GepafinConstant.CALL_MUST_BE_CLOSED_FOR_RANKING_ACTION)); + } + } + + private void validateRankingActionRequest(ApplicationRankingActionTypeEnum rankingActionType, Long manualRanking) { + if (rankingActionType == ApplicationRankingActionTypeEnum.REPOSITION + && (manualRanking == null || manualRanking <= 0)) { + throw new CustomValidationException(Status.BAD_REQUEST, + Translator.toLocale(GepafinConstant.APPLICATION_RANKING_ACTION_INVALID)); + } + } + /** * Sets all non-terminal amendments, the application evaluation (if any), and the assigned application row to CLOSE. */ diff --git a/src/main/java/net/gepafin/tendermanagement/dao/CallDao.java b/src/main/java/net/gepafin/tendermanagement/dao/CallDao.java index f5c2b7c0..fddfe702 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/CallDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/CallDao.java @@ -228,6 +228,9 @@ public class CallDao { if (createCallRequest.getAllowMultipleApplications() != null) { callEntity.setAllowMultipleApplications(createCallRequest.getAllowMultipleApplications()); } + if (createCallRequest.getRankingType() != null) { + callEntity.setRankingType(createCallRequest.getRankingType().getValue()); + } callEntity = callRepository.save(callEntity); log.info("CallEntity saved with ID: {} for call name: '{}'", callEntity.getId(), callEntity.getName()); @@ -418,6 +421,9 @@ public class CallDao { createCallResponseBean.setEmail(callEntity.getEmail()); createCallResponseBean.setCreatedDate(callEntity.getCreatedDate()); createCallResponseBean.setUpdatedDate(callEntity.getUpdatedDate()); + if (callEntity.getRankingType() != null && !callEntity.getRankingType().isBlank()) { + createCallResponseBean.setRankingType(CallRankingTypeEnum.valueOf(callEntity.getRankingType().trim())); + } return createCallResponseBean; } @@ -583,6 +589,41 @@ public class CallDao { } } + private boolean hasNonRankingStep1Field(UpdateCallRequestStep1 r) { + return r.getName() != null + || r.getDescriptionShort() != null + || r.getDescriptionLong() != null + || r.getDates() != null + || r.getAmount() != null + || r.getAmountMax() != null + || r.getAimedTo() != null + || r.getDocumentationRequested() != null + || r.getAmountMin() != null + || r.getEmail() != null + || r.getPhoneNumber() != null + || r.getStartTime() != null + || r.getEndTime() != null + || r.getConfidi() != null + || r.getAllowMultipleApplications() != null + || r.getFaq() != null + || r.getNumberOfCheck() != null + || r.getAppointmentTemplateId() != null + || r.getEvaluationVersion() != null; + } + + private CallResponse updatePublishedCallStep1RankingTypeOnly(HttpServletRequest request, CallEntity callEntity, + CallEntity oldCallEntity, UpdateCallRequestStep1 updateCallRequest) { + setIfUpdated(callEntity::getRankingType, callEntity::setRankingType, + updateCallRequest.getRankingType() != null ? updateCallRequest.getRankingType().getValue() : null); + callEntity = callRepository.save(callEntity); + loggingUtil.addVersionHistory( + VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldCallEntity).newData(callEntity).build()); + CallResponse response = getCallResponseBean(callEntity); + response.setCurrentStep(GepafinConstant.STEP_1); + log.info("Published call step 1: ranking type only | callId={}", callEntity.getId()); + return response; + } + public void isValidDateRange(UpdateCallRequestStep1 updateCallRequest, CallEntity callEntity) { List dates = updateCallRequest.getDates(); @@ -615,6 +656,18 @@ public class CallDao { public CallResponse updateCallStep1(HttpServletRequest request,CallEntity callEntity, UpdateCallRequestStep1 updateCallRequest, UserEntity userEntity) { log.info("Updating Call ID: {}, by User ID: {}", callEntity.getId(),userEntity.getId() ); CallEntity oldCallEntity = Utils.getClonedEntityForData(callEntity); + if (CallStatusEnum.PUBLISH.getValue().equals(callEntity.getStatus())) { + if (hasNonRankingStep1Field(updateCallRequest)) { + throw new CustomValidationException(Status.VALIDATION_ERROR, + Translator.toLocale(GepafinConstant.PUBLISHED_CALL_STEP1_ONLY_RANKING_TYPE_ALLOWED)); + } + if (updateCallRequest.getRankingType() != null) { + return updatePublishedCallStep1RankingTypeOnly(request, callEntity, oldCallEntity, updateCallRequest); + } + CallResponse unchanged = getCallResponseBean(callEntity); + unchanged.setCurrentStep(GepafinConstant.STEP_1); + return unchanged; + } isValidDateRange(updateCallRequest, callEntity); setIfUpdated(callEntity::getName, callEntity::setName, updateCallRequest.getName()); setIfUpdated(callEntity::getDescriptionShort, callEntity::setDescriptionShort, @@ -648,8 +701,6 @@ public class CallDao { if (!requestEndTime.equals(storedEndTime)) { setIfUpdated(callEntity::getEndTime, callEntity::setEndTime, DateTimeUtil.parseTime(updateCallRequest.getEndTime())); -// callEntity.setStatus(CallStatusEnum.PUBLISH.getValue()); -// callRepository.save(callEntity); isEndTimeUpdated = true; } } @@ -708,10 +759,15 @@ public class CallDao { setIfUpdated(callEntity::getPhoneNumber, callEntity::setPhoneNumber, updateCallRequest.getPhoneNumber()); setIfUpdated(callEntity::getStartTime, callEntity::setStartTime, DateTimeUtil.parseTime(updateCallRequest.getStartTime())); setIfUpdated(callEntity::getConfidi, callEntity::setConfidi, updateCallRequest.getConfidi()); - setIfUpdated(callEntity::getEvaluationVersion, callEntity::setEvaluationVersion, updateCallRequest.getEvaluationVersion().getValue()); + if (updateCallRequest.getEvaluationVersion() != null) { + setIfUpdated(callEntity::getEvaluationVersion, callEntity::setEvaluationVersion, + updateCallRequest.getEvaluationVersion().getValue()); + } setIfUpdated(callEntity::getNumberOfCheck, callEntity::setNumberOfCheck, updateCallRequest.getNumberOfCheck()); setIfUpdated(callEntity::getAppointmentTemplateId, callEntity::setAppointmentTemplateId, updateCallRequest.getAppointmentTemplateId()); setIfUpdated(callEntity::getAllowMultipleApplications, callEntity::setAllowMultipleApplications, updateCallRequest.getAllowMultipleApplications()); + setIfUpdated(callEntity::getRankingType, callEntity::setRankingType, + updateCallRequest.getRankingType() != null ? updateCallRequest.getRankingType().getValue() : null); callEntity = callRepository.save(callEntity); /** This code is responsible for adding a version history log for the "update call step 1" operation **/ @@ -725,6 +781,15 @@ public class CallDao { return createCallResponseBean; } + public CallResponse updateCallRankingType(HttpServletRequest request, CallEntity callEntity, CallRankingTypeEnum rankingType) { + CallEntity oldCallEntity = Utils.getClonedEntityForData(callEntity); + callEntity.setRankingType(rankingType != null ? rankingType.getValue() : null); + callEntity = callRepository.save(callEntity); + loggingUtil.addVersionHistory( + VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldCallEntity).newData(callEntity).build()); + return getCallResponseBean(callEntity); + } + private void softDeleteFaq(FaqEntity faqEntity) { FaqEntity oldFaqEntity = Utils.getClonedEntityForData(faqEntity); diff --git a/src/main/java/net/gepafin/tendermanagement/entities/ApplicationEntity.java b/src/main/java/net/gepafin/tendermanagement/entities/ApplicationEntity.java index 7670d520..c8d93923 100644 --- a/src/main/java/net/gepafin/tendermanagement/entities/ApplicationEntity.java +++ b/src/main/java/net/gepafin/tendermanagement/entities/ApplicationEntity.java @@ -90,4 +90,10 @@ public class ApplicationEntity extends BaseEntity { @Column(name = "COMPANY_DOCUMENT") private String companyDocument; + + @Column(name = "ranking_action_type") + private String rankingActionType; + + @Column(name = "manual_ranking") + private Long manualRanking; } \ No newline at end of file diff --git a/src/main/java/net/gepafin/tendermanagement/entities/ApplicationRankingView.java b/src/main/java/net/gepafin/tendermanagement/entities/ApplicationRankingView.java new file mode 100644 index 00000000..ffa56217 --- /dev/null +++ b/src/main/java/net/gepafin/tendermanagement/entities/ApplicationRankingView.java @@ -0,0 +1,68 @@ +package net.gepafin.tendermanagement.entities; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import lombok.Data; +import org.hibernate.annotations.Immutable; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.time.LocalDateTime; + +/** + * Maps {@code application_ranking_view} (read-only). + */ +@Data +@Entity +@Immutable +@Table(name = "application_ranking_view") +public class ApplicationRankingView implements Serializable { + + @Id + @Column(name = "application_id") + private Long applicationId; + + @Column(name = "listing_rank") + private Long rank; + + @Column(name = "call_id") + private Long callId; + + @Column(name = "ranking_action_type") + private String rankingActionType; + + @Column(name = "total_score") + private BigDecimal totalScore; + + @Column(name = "user_id") + private Long userId; + + @Column(name = "status") + private String status; + + @Column(name = "submission_date") + private LocalDateTime submissionDate; + + @Column(name = "protocol_datetime") + private LocalDateTime protocolDatetime; + + @Column(name = "protocol_number") + private Long protocolNumber; + + @Column(name = "ndg") + private String ndg; + + @Column(name = "amount_accepted") + private BigDecimal amountAccepted; + + @Column(name = "pec_email") + private String pecEmail; + + @Column(name = "manual_ranking") + private Long manualRanking; + + @Column(name = "ranking_type") + private String rankingType; +} diff --git a/src/main/java/net/gepafin/tendermanagement/entities/CallEntity.java b/src/main/java/net/gepafin/tendermanagement/entities/CallEntity.java index dd2655ed..e7089670 100644 --- a/src/main/java/net/gepafin/tendermanagement/entities/CallEntity.java +++ b/src/main/java/net/gepafin/tendermanagement/entities/CallEntity.java @@ -102,5 +102,8 @@ public class CallEntity extends BaseEntity { @Column(name = "allow_multiple_applications") private Boolean allowMultipleApplications; + + @Column(name = "ranking_type") + private String rankingType; } diff --git a/src/main/java/net/gepafin/tendermanagement/enums/ApplicationRankingActionTypeEnum.java b/src/main/java/net/gepafin/tendermanagement/enums/ApplicationRankingActionTypeEnum.java new file mode 100644 index 00000000..44d0dbd1 --- /dev/null +++ b/src/main/java/net/gepafin/tendermanagement/enums/ApplicationRankingActionTypeEnum.java @@ -0,0 +1,20 @@ +package net.gepafin.tendermanagement.enums; + +import com.fasterxml.jackson.annotation.JsonValue; + +public enum ApplicationRankingActionTypeEnum { + + REMOVE("REMOVE"), + REPOSITION("REPOSITION"); + + private final String value; + + ApplicationRankingActionTypeEnum(String value) { + this.value = value; + } + + @JsonValue + public String getValue() { + return value; + } +} diff --git a/src/main/java/net/gepafin/tendermanagement/enums/CallRankingTypeEnum.java b/src/main/java/net/gepafin/tendermanagement/enums/CallRankingTypeEnum.java new file mode 100644 index 00000000..115218eb --- /dev/null +++ b/src/main/java/net/gepafin/tendermanagement/enums/CallRankingTypeEnum.java @@ -0,0 +1,20 @@ +package net.gepafin.tendermanagement.enums; + +import com.fasterxml.jackson.annotation.JsonValue; + +public enum CallRankingTypeEnum { + + SCORE("SCORE"), + PROTOCOL_DATE_TIME("PROTOCOL_DATE_TIME"); + + private final String value; + + CallRankingTypeEnum(String value) { + this.value = value; + } + + @JsonValue + public String getValue() { + return value; + } +} diff --git a/src/main/java/net/gepafin/tendermanagement/enums/UserActionContextEnum.java b/src/main/java/net/gepafin/tendermanagement/enums/UserActionContextEnum.java index 5cf65f56..6069386d 100644 --- a/src/main/java/net/gepafin/tendermanagement/enums/UserActionContextEnum.java +++ b/src/main/java/net/gepafin/tendermanagement/enums/UserActionContextEnum.java @@ -236,7 +236,10 @@ public enum UserActionContextEnum { REJECT_PEC_MAIL("REJECT_PEC_MAIL"), FETCH_EMAIL_LOG("FETCH_EMAIL_LOG"), FETCH_ALL_EMAIL_LOG("FETCH_ALL_EMAIL_LOG"), - UPLOAD_COMPANY_DOCUMENT_TO_APPLICATION("UPLOAD_COMPANY_DOCUMENT_TO_APPLICATION"); + UPLOAD_COMPANY_DOCUMENT_TO_APPLICATION("UPLOAD_COMPANY_DOCUMENT_TO_APPLICATION"), + UPDATE_CALL_RANKING_TYPE("UPDATE_CALL_RANKING_TYPE"), + UPDATE_APPLICATION_RANKING_ACTION("UPDATE_APPLICATION_RANKING_ACTION"), + GET_APPLICATION_RANKING("GET_APPLICATION_RANKING"); private final String value; diff --git a/src/main/java/net/gepafin/tendermanagement/model/request/ApplicationRankingActionRequest.java b/src/main/java/net/gepafin/tendermanagement/model/request/ApplicationRankingActionRequest.java new file mode 100644 index 00000000..a56c7368 --- /dev/null +++ b/src/main/java/net/gepafin/tendermanagement/model/request/ApplicationRankingActionRequest.java @@ -0,0 +1,13 @@ +package net.gepafin.tendermanagement.model.request; + +import lombok.Data; +import net.gepafin.tendermanagement.enums.ApplicationRankingActionTypeEnum; + +@Data +public class ApplicationRankingActionRequest { + + private ApplicationRankingActionTypeEnum rankingActionType; + + /** Required when {@code rankingActionType} is {@link ApplicationRankingActionTypeEnum#REPOSITION}. */ + private Long manualRanking; +} diff --git a/src/main/java/net/gepafin/tendermanagement/model/request/CreateCallRequestStep1.java b/src/main/java/net/gepafin/tendermanagement/model/request/CreateCallRequestStep1.java index c364db5a..b576cbf3 100644 --- a/src/main/java/net/gepafin/tendermanagement/model/request/CreateCallRequestStep1.java +++ b/src/main/java/net/gepafin/tendermanagement/model/request/CreateCallRequestStep1.java @@ -5,6 +5,7 @@ import java.time.LocalDateTime; import java.util.List; import lombok.Data; +import net.gepafin.tendermanagement.enums.CallRankingTypeEnum; import net.gepafin.tendermanagement.enums.EvaluationVersionEnum; @Data @@ -49,4 +50,6 @@ public class CreateCallRequestStep1 { private List faq; private EvaluationVersionEnum evaluationVersion; + + private CallRankingTypeEnum rankingType; } diff --git a/src/main/java/net/gepafin/tendermanagement/model/request/UpdateCallRequestStep1.java b/src/main/java/net/gepafin/tendermanagement/model/request/UpdateCallRequestStep1.java index 30530381..4df6cb5a 100644 --- a/src/main/java/net/gepafin/tendermanagement/model/request/UpdateCallRequestStep1.java +++ b/src/main/java/net/gepafin/tendermanagement/model/request/UpdateCallRequestStep1.java @@ -5,6 +5,7 @@ import java.time.LocalDateTime; import java.util.List; import lombok.Data; +import net.gepafin.tendermanagement.enums.CallRankingTypeEnum; import net.gepafin.tendermanagement.enums.EvaluationVersionEnum; @Data @@ -46,6 +47,8 @@ public class UpdateCallRequestStep1 { private Long appointmentTemplateId; - private EvaluationVersionEnum evaluationVersion; + private EvaluationVersionEnum evaluationVersion; + + private CallRankingTypeEnum rankingType; } diff --git a/src/main/java/net/gepafin/tendermanagement/model/response/ApplicationRankingResponse.java b/src/main/java/net/gepafin/tendermanagement/model/response/ApplicationRankingResponse.java new file mode 100644 index 00000000..f02f3276 --- /dev/null +++ b/src/main/java/net/gepafin/tendermanagement/model/response/ApplicationRankingResponse.java @@ -0,0 +1,28 @@ +package net.gepafin.tendermanagement.model.response; + +import lombok.Data; +import net.gepafin.tendermanagement.enums.ApplicationRankingActionTypeEnum; +import net.gepafin.tendermanagement.enums.CallRankingTypeEnum; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +@Data +public class ApplicationRankingResponse { + + private Long rank; + private Long applicationId; + private Long callId; + private ApplicationRankingActionTypeEnum rankingActionType; + private BigDecimal totalScore; + private Long userId; + private String status; + private LocalDateTime submissionDate; + private LocalDateTime protocolDatetime; + private Long protocolNumber; + private String ndg; + private BigDecimal amountAccepted; + private String pecEmail; + private Long manualRanking; + private CallRankingTypeEnum rankingType; +} diff --git a/src/main/java/net/gepafin/tendermanagement/model/response/ApplicationResponse.java b/src/main/java/net/gepafin/tendermanagement/model/response/ApplicationResponse.java index abbfd164..3c8f1113 100644 --- a/src/main/java/net/gepafin/tendermanagement/model/response/ApplicationResponse.java +++ b/src/main/java/net/gepafin/tendermanagement/model/response/ApplicationResponse.java @@ -1,6 +1,7 @@ package net.gepafin.tendermanagement.model.response; import lombok.Data; +import net.gepafin.tendermanagement.enums.ApplicationRankingActionTypeEnum; import net.gepafin.tendermanagement.enums.EvaluationVersionEnum; import net.gepafin.tendermanagement.model.response.ApplicationFormFieldResponseBean; @@ -54,4 +55,8 @@ public class ApplicationResponse{ private String ndg; + private ApplicationRankingActionTypeEnum rankingActionType; + + private Long manualRanking; + } \ No newline at end of file diff --git a/src/main/java/net/gepafin/tendermanagement/model/response/CallRankingSummaryResponse.java b/src/main/java/net/gepafin/tendermanagement/model/response/CallRankingSummaryResponse.java new file mode 100644 index 00000000..4568ef1e --- /dev/null +++ b/src/main/java/net/gepafin/tendermanagement/model/response/CallRankingSummaryResponse.java @@ -0,0 +1,19 @@ +package net.gepafin.tendermanagement.model.response; + +import lombok.Data; +import net.gepafin.tendermanagement.enums.CallRankingTypeEnum; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; + +/** Call-level ranking payload: tender info plus ordered ranked applications. */ +@Data +public class CallRankingSummaryResponse { + + private Long callId; + private String callName; + private BigDecimal amount; + private CallRankingTypeEnum rankingType; + private List applications; +} diff --git a/src/main/java/net/gepafin/tendermanagement/model/response/CallResponse.java b/src/main/java/net/gepafin/tendermanagement/model/response/CallResponse.java index 13a90715..48ce3363 100644 --- a/src/main/java/net/gepafin/tendermanagement/model/response/CallResponse.java +++ b/src/main/java/net/gepafin/tendermanagement/model/response/CallResponse.java @@ -7,6 +7,7 @@ import java.util.List; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import lombok.Data; +import net.gepafin.tendermanagement.enums.CallRankingTypeEnum; import net.gepafin.tendermanagement.enums.CallStatusEnum; import net.gepafin.tendermanagement.enums.EvaluationVersionEnum; import net.gepafin.tendermanagement.util.DynamicLocalTimeSerializer; @@ -83,6 +84,8 @@ public class CallResponse { private Long preferredCallId; private EvaluationVersionEnum evaluationVersion; + + private CallRankingTypeEnum rankingType; } diff --git a/src/main/java/net/gepafin/tendermanagement/repositories/ApplicationRankingViewRepository.java b/src/main/java/net/gepafin/tendermanagement/repositories/ApplicationRankingViewRepository.java new file mode 100644 index 00000000..7fbd36d3 --- /dev/null +++ b/src/main/java/net/gepafin/tendermanagement/repositories/ApplicationRankingViewRepository.java @@ -0,0 +1,11 @@ +package net.gepafin.tendermanagement.repositories; + +import net.gepafin.tendermanagement.entities.ApplicationRankingView; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.stereotype.Repository; + +@Repository +public interface ApplicationRankingViewRepository extends JpaRepository, + JpaSpecificationExecutor { +} diff --git a/src/main/java/net/gepafin/tendermanagement/repositories/ApplicationRepository.java b/src/main/java/net/gepafin/tendermanagement/repositories/ApplicationRepository.java index 37d2f270..72c2b678 100644 --- a/src/main/java/net/gepafin/tendermanagement/repositories/ApplicationRepository.java +++ b/src/main/java/net/gepafin/tendermanagement/repositories/ApplicationRepository.java @@ -183,5 +183,6 @@ public interface ApplicationRepository extends JpaRepository findByCallIdAndIsDeletedFalseAndStatusIn(Long callId,List status); + boolean existsByCallIdAndManualRankingAndIsDeletedFalseAndIdNot(Long callId, Long manualRanking, Long id); } diff --git a/src/main/java/net/gepafin/tendermanagement/service/ApplicationService.java b/src/main/java/net/gepafin/tendermanagement/service/ApplicationService.java index bce96ba9..66115c59 100644 --- a/src/main/java/net/gepafin/tendermanagement/service/ApplicationService.java +++ b/src/main/java/net/gepafin/tendermanagement/service/ApplicationService.java @@ -5,6 +5,7 @@ import jakarta.servlet.http.HttpServletResponse; import net.gepafin.tendermanagement.entities.ApplicationEntity; import net.gepafin.tendermanagement.model.request.ApplicationPageableRequestBean; import net.gepafin.tendermanagement.model.request.ApplicationRequest; +import net.gepafin.tendermanagement.enums.ApplicationRankingActionTypeEnum; import net.gepafin.tendermanagement.enums.ApplicationStatusTypeEnum; import net.gepafin.tendermanagement.enums.FormActionEnum; import net.gepafin.tendermanagement.model.request.ApplicationRequestBean; @@ -57,4 +58,10 @@ public interface ApplicationService { public byte[] downloadRankingCsv(HttpServletRequest request, Long callId); public void uploadCompanyDocumentsToApplication(HttpServletRequest request, Long applicationId, List companyDocumentIds); + + ApplicationResponse updateApplicationRankingAction(HttpServletRequest request, Long applicationId, + ApplicationRankingActionTypeEnum rankingActionType, Long manualRanking); + + CallRankingSummaryResponse getApplicationRanking(HttpServletRequest request, Long callId, + List rankingActionTypes); } diff --git a/src/main/java/net/gepafin/tendermanagement/service/CallService.java b/src/main/java/net/gepafin/tendermanagement/service/CallService.java index f0130c29..2c51f3e0 100644 --- a/src/main/java/net/gepafin/tendermanagement/service/CallService.java +++ b/src/main/java/net/gepafin/tendermanagement/service/CallService.java @@ -4,6 +4,7 @@ import java.util.List; import jakarta.servlet.http.HttpServletRequest; import net.gepafin.tendermanagement.entities.CallEntity; +import net.gepafin.tendermanagement.enums.CallRankingTypeEnum; import net.gepafin.tendermanagement.enums.CallStatusEnum; import net.gepafin.tendermanagement.enums.EvaluationVersionEnum; import net.gepafin.tendermanagement.model.request.*; @@ -37,4 +38,6 @@ public interface CallService { CallResponse createCallStep2EvaluationV2(HttpServletRequest request, Long callId, CreateCallRequestStep2EvaluationV2 createCallRequest); + CallResponse updateCallRankingType(HttpServletRequest request, Long callId, CallRankingTypeEnum rankingType); + } diff --git a/src/main/java/net/gepafin/tendermanagement/service/impl/ApplicationServiceImpl.java b/src/main/java/net/gepafin/tendermanagement/service/impl/ApplicationServiceImpl.java index c3abf79e..1ff7aa07 100644 --- a/src/main/java/net/gepafin/tendermanagement/service/impl/ApplicationServiceImpl.java +++ b/src/main/java/net/gepafin/tendermanagement/service/impl/ApplicationServiceImpl.java @@ -12,6 +12,7 @@ import net.gepafin.tendermanagement.entities.CompanyEntity; import net.gepafin.tendermanagement.entities.UserEntity; import net.gepafin.tendermanagement.model.request.ApplicationPageableRequestBean; import net.gepafin.tendermanagement.model.request.ApplicationRequest; +import net.gepafin.tendermanagement.enums.ApplicationRankingActionTypeEnum; import net.gepafin.tendermanagement.enums.ApplicationStatusTypeEnum; import net.gepafin.tendermanagement.enums.FormActionEnum; import net.gepafin.tendermanagement.model.request.ApplicationRequestBean; @@ -195,4 +196,20 @@ public class ApplicationServiceImpl implements ApplicationService { UserEntity userEntity = validator.validateUser(request); applicationDao.uploadCompanyDocumentsToApplication(applicationId,companyDocumentIds,userEntity); } + + @Override + @Transactional(rollbackFor = Exception.class) + public ApplicationResponse updateApplicationRankingAction(HttpServletRequest request, Long applicationId, + ApplicationRankingActionTypeEnum rankingActionType, Long manualRanking) { + return applicationDao.updateApplicationRankingAction(request, applicationId, rankingActionType, manualRanking); + } + + @Override + @Transactional(readOnly = true) + public CallRankingSummaryResponse getApplicationRanking(HttpServletRequest request, Long callId, + List rankingActionTypes) { + validator.validateUser(request); + validator.validateSuperAdminOrDirector(); + return applicationDao.getApplicationRanking(callId, rankingActionTypes); + } } diff --git a/src/main/java/net/gepafin/tendermanagement/service/impl/CallServiceImpl.java b/src/main/java/net/gepafin/tendermanagement/service/impl/CallServiceImpl.java index 0036cbb7..e6a71750 100644 --- a/src/main/java/net/gepafin/tendermanagement/service/impl/CallServiceImpl.java +++ b/src/main/java/net/gepafin/tendermanagement/service/impl/CallServiceImpl.java @@ -1,9 +1,12 @@ package net.gepafin.tendermanagement.service.impl; import jakarta.servlet.http.HttpServletRequest; +import net.gepafin.tendermanagement.config.Translator; +import net.gepafin.tendermanagement.constants.GepafinConstant; import net.gepafin.tendermanagement.dao.CallDao; import net.gepafin.tendermanagement.entities.CallEntity; import net.gepafin.tendermanagement.entities.UserEntity; +import net.gepafin.tendermanagement.enums.CallRankingTypeEnum; import net.gepafin.tendermanagement.enums.CallStatusEnum; import net.gepafin.tendermanagement.enums.EvaluationVersionEnum; import net.gepafin.tendermanagement.model.request.*; @@ -12,6 +15,8 @@ import net.gepafin.tendermanagement.model.response.CallResponse; import net.gepafin.tendermanagement.model.response.PageableResponseBean; import net.gepafin.tendermanagement.service.CallService; import net.gepafin.tendermanagement.util.Validator; +import net.gepafin.tendermanagement.web.rest.api.errors.ForbiddenAccessException; +import net.gepafin.tendermanagement.web.rest.api.errors.Status; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -116,4 +121,15 @@ public class CallServiceImpl implements CallService { CallEntity call = validator.validateUserWithCall(user, callId); return callDao.createCallStep2EvaluationV2(call, createCallRequest, user); } + + @Override + @Transactional(rollbackFor = Exception.class) + public CallResponse updateCallRankingType(HttpServletRequest request, Long callId, CallRankingTypeEnum rankingType) { + validator.validateUser(request); + if (Boolean.FALSE.equals(validator.checkIsSuperAdmin())) { + throw new ForbiddenAccessException(Status.FORBIDDEN, Translator.toLocale(GepafinConstant.PERMISSION_DENIED)); + } + CallEntity call = callDao.validateCall(callId); + return callDao.updateCallRankingType(request, call, rankingType); + } } diff --git a/src/main/java/net/gepafin/tendermanagement/web/rest/api/RankingApi.java b/src/main/java/net/gepafin/tendermanagement/web/rest/api/RankingApi.java new file mode 100644 index 00000000..44ab7031 --- /dev/null +++ b/src/main/java/net/gepafin/tendermanagement/web/rest/api/RankingApi.java @@ -0,0 +1,84 @@ +package net.gepafin.tendermanagement.web.rest.api; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.Valid; +import net.gepafin.tendermanagement.enums.ApplicationRankingActionTypeEnum; +import net.gepafin.tendermanagement.enums.CallRankingTypeEnum; +import net.gepafin.tendermanagement.model.request.ApplicationRankingActionRequest; +import net.gepafin.tendermanagement.model.response.CallRankingSummaryResponse; +import net.gepafin.tendermanagement.model.response.ApplicationResponse; +import net.gepafin.tendermanagement.model.response.CallResponse; +import net.gepafin.tendermanagement.model.util.Response; +import net.gepafin.tendermanagement.web.rest.api.errors.ErrorConstants; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; + +import java.util.List; + +@Validated +public interface RankingApi { + + @Operation(summary = "API to update call ranking type", + responses = { + @ApiResponse(responseCode = "200", description = "OK"), + @ApiResponse(responseCode = "404", description = "Not Found", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, examples = { + @io.swagger.v3.oas.annotations.media.ExampleObject(value = ErrorConstants.NOTFOUND_ERROR_EXAMPLE)})), + @ApiResponse(responseCode = "401", description = "Unauthorized", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, examples = { + @io.swagger.v3.oas.annotations.media.ExampleObject(value = ErrorConstants.UNAUTHORIZED_ERROR_EXAMPLE)})), + @ApiResponse(responseCode = "400", description = "Bad Request", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, examples = { + @io.swagger.v3.oas.annotations.media.ExampleObject(value = ErrorConstants.BADREQUEST_ERROR_EXAMPLE)})) + }) + @PutMapping(value = "/call/{callId}/type", produces = MediaType.APPLICATION_JSON_VALUE) + @PreAuthorize("hasRole('ROLE_SUPER_ADMIN')") + ResponseEntity> updateCallRankingType(HttpServletRequest request, + @PathVariable("callId") Long callId, + @RequestParam("rankingType") CallRankingTypeEnum rankingType); + + @Operation(summary = "API to update application ranking action type in application", + responses = { + @ApiResponse(responseCode = "200", description = "OK"), + @ApiResponse(responseCode = "404", description = "Not Found", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, examples = { + @io.swagger.v3.oas.annotations.media.ExampleObject(value = ErrorConstants.NOTFOUND_ERROR_EXAMPLE)})), + @ApiResponse(responseCode = "401", description = "Unauthorized", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, examples = { + @io.swagger.v3.oas.annotations.media.ExampleObject(value = ErrorConstants.UNAUTHORIZED_ERROR_EXAMPLE)})), + @ApiResponse(responseCode = "400", description = "Bad Request", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, examples = { + @io.swagger.v3.oas.annotations.media.ExampleObject(value = ErrorConstants.BADREQUEST_ERROR_EXAMPLE)})) + }) + @PatchMapping(value = "/application/{applicationId}/action", produces = MediaType.APPLICATION_JSON_VALUE, + consumes = MediaType.APPLICATION_JSON_VALUE) + @PreAuthorize("hasRole('ROLE_SUPER_ADMIN')") + ResponseEntity> updateApplicationRankingAction(HttpServletRequest request, + @PathVariable("applicationId") Long applicationId, + @Valid @RequestBody ApplicationRankingActionRequest rankingActionRequest); + + @Operation(summary = "API to get ranking list with callId", + responses = { + @ApiResponse(responseCode = "200", description = "OK"), + @ApiResponse(responseCode = "404", description = "Not Found", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, examples = { + @io.swagger.v3.oas.annotations.media.ExampleObject(value = ErrorConstants.NOTFOUND_ERROR_EXAMPLE)})), + @ApiResponse(responseCode = "401", description = "Unauthorized", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, examples = { + @io.swagger.v3.oas.annotations.media.ExampleObject(value = ErrorConstants.UNAUTHORIZED_ERROR_EXAMPLE)})) + }) + @GetMapping(produces = MediaType.APPLICATION_JSON_VALUE) + @PreAuthorize("hasRole('ROLE_SUPER_ADMIN') || hasRole('ROLE_DIRECTOR')") + ResponseEntity> getApplicationRanking(HttpServletRequest request, + @Parameter(description = "Call id", required = true) + @RequestParam("callId") Long callId, + @Parameter(description = "ranking action types", required = false, + array = @ArraySchema(schema = @Schema(implementation = ApplicationRankingActionTypeEnum.class))) + @RequestParam(value = "rankingActionType", required = false) List rankingActionTypes); +} diff --git a/src/main/java/net/gepafin/tendermanagement/web/rest/api/impl/RankingApiController.java b/src/main/java/net/gepafin/tendermanagement/web/rest/api/impl/RankingApiController.java new file mode 100644 index 00000000..8d2b794f --- /dev/null +++ b/src/main/java/net/gepafin/tendermanagement/web/rest/api/impl/RankingApiController.java @@ -0,0 +1,74 @@ +package net.gepafin.tendermanagement.web.rest.api.impl; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.Valid; +import net.gepafin.tendermanagement.config.Translator; +import net.gepafin.tendermanagement.constants.GepafinConstant; +import net.gepafin.tendermanagement.enums.ApplicationRankingActionTypeEnum; +import net.gepafin.tendermanagement.enums.CallRankingTypeEnum; +import net.gepafin.tendermanagement.enums.UserActionContextEnum; +import net.gepafin.tendermanagement.enums.UserActionLogsEnum; +import net.gepafin.tendermanagement.model.request.ApplicationRankingActionRequest; +import net.gepafin.tendermanagement.model.request.UserActionRequest; +import net.gepafin.tendermanagement.model.response.CallRankingSummaryResponse; +import net.gepafin.tendermanagement.model.response.ApplicationResponse; +import net.gepafin.tendermanagement.model.response.CallResponse; +import net.gepafin.tendermanagement.model.util.Response; +import net.gepafin.tendermanagement.service.ApplicationService; +import net.gepafin.tendermanagement.service.CallService; +import net.gepafin.tendermanagement.util.LoggingUtil; +import net.gepafin.tendermanagement.web.rest.api.RankingApi; +import net.gepafin.tendermanagement.web.rest.api.errors.Status; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@RestController +@RequestMapping("/v1/ranking") +public class RankingApiController implements RankingApi { + + @Autowired + private CallService callService; + + @Autowired + private ApplicationService applicationService; + + @Autowired + private LoggingUtil loggingUtil; + + @Override + public ResponseEntity> updateCallRankingType(HttpServletRequest request, Long callId, + CallRankingTypeEnum rankingType) { + loggingUtil.logUserAction(UserActionRequest.builder().request(request).actionType(UserActionLogsEnum.UPDATE) + .actionContext(UserActionContextEnum.UPDATE_CALL_RANKING_TYPE).build()); + CallResponse updated = callService.updateCallRankingType(request, callId, rankingType); + return ResponseEntity.status(HttpStatus.OK) + .body(new Response<>(updated, Status.SUCCESS, Translator.toLocale(GepafinConstant.CALL_RANKING_TYPE_UPDATED_SUCCESSFULLY))); + } + + @Override + public ResponseEntity> updateApplicationRankingAction(HttpServletRequest request, + Long applicationId, @Valid @RequestBody ApplicationRankingActionRequest rankingActionRequest) { + loggingUtil.logUserAction(UserActionRequest.builder().request(request).actionType(UserActionLogsEnum.UPDATE) + .actionContext(UserActionContextEnum.UPDATE_APPLICATION_RANKING_ACTION).build()); + ApplicationResponse body = applicationService.updateApplicationRankingAction(request, applicationId, + rankingActionRequest.getRankingActionType(), rankingActionRequest.getManualRanking()); + return ResponseEntity.status(HttpStatus.OK) + .body(new Response<>(body, Status.SUCCESS, Translator.toLocale(GepafinConstant.APPLICATION_RANKING_ACTION_UPDATED_SUCCESSFULLY))); + } + + @Override + public ResponseEntity> getApplicationRanking(HttpServletRequest request, + Long callId, List rankingActionTypes) { + loggingUtil.logUserAction(UserActionRequest.builder().request(request).actionType(UserActionLogsEnum.VIEW) + .actionContext(UserActionContextEnum.GET_APPLICATION_RANKING).build()); + CallRankingSummaryResponse ranking = applicationService.getApplicationRanking(request, callId, rankingActionTypes); + return ResponseEntity.status(HttpStatus.OK) + .body(new Response<>(ranking, Status.SUCCESS, Translator.toLocale(GepafinConstant.APPLICATION_RANKING_FETCHED_SUCCESSFULLY))); + } +} diff --git a/src/main/resources/db/changelog/db.changelog-1.0.0.xml b/src/main/resources/db/changelog/db.changelog-1.0.0.xml index 407ba720..3bba5cbd 100644 --- a/src/main/resources/db/changelog/db.changelog-1.0.0.xml +++ b/src/main/resources/db/changelog/db.changelog-1.0.0.xml @@ -3223,4 +3223,22 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/db/dump/create_application_ranking_view_07_04_2026.sql b/src/main/resources/db/dump/create_application_ranking_view_07_04_2026.sql new file mode 100644 index 00000000..afa68d8f --- /dev/null +++ b/src/main/resources/db/dump/create_application_ranking_view_07_04_2026.sql @@ -0,0 +1,201 @@ +DROP VIEW IF EXISTS application_ranking_view; + +CREATE OR REPLACE VIEW application_ranking_view AS +WITH evaluation_scores AS ( + SELECT + ae.application_id, + COALESCE( + SUM( + COALESCE(NULLIF(TRIM(score_item ->> 'score'), ''), '0')::numeric + ), + 0 + ) AS total_score + FROM application_evaluation ae + LEFT JOIN LATERAL jsonb_array_elements( + CASE + WHEN ae.criteria IS NULL OR BTRIM(ae.criteria) = '' THEN '[]'::jsonb + ELSE ae.criteria::jsonb + END + ) score_item ON TRUE + WHERE ae.is_deleted = false + GROUP BY ae.application_id +), +approved_applications AS ( + SELECT + a.id AS application_id, + a.call_id, + a.company_id, + a.user_id, + a.status AS status, + a.submission_date, + a.amount_requested, + a.amount_accepted, + a.manual_ranking, + a.ranking_action_type, + a.ndg, + a.pec_email, + c.ranking_type, + p.created_date AS protocol_datetime, + p.protocol_number AS protocol_number, + COALESCE(a.amount_accepted, a.amount_requested, 0) AS amount, + COALESCE(es.total_score, 0) AS total_score + FROM application a + JOIN call c ON c.id = a.call_id + AND (c.is_deleted = false OR c.is_deleted IS NULL) + AND NULLIF(BTRIM(c.ranking_type), '') IS NOT NULL + LEFT JOIN protocol p ON p.id = a.protocol_number + LEFT JOIN evaluation_scores es ON es.application_id = a.id + WHERE (a.is_deleted = false OR a.is_deleted IS NULL) + AND a.status = 'APPROVED' + AND COALESCE(a.ranking_action_type, '') <> 'EXCLUDE' +), +ranking_core AS ( + SELECT * + FROM approved_applications + WHERE COALESCE(ranking_action_type, '') <> 'REMOVE' +), +remove_apps AS ( + SELECT * + FROM approved_applications + WHERE COALESCE(ranking_action_type, '') = 'REMOVE' +), +natural_ranked AS ( + SELECT + rc.*, + ROW_NUMBER() OVER ( + PARTITION BY rc.call_id + ORDER BY + CASE WHEN rc.ranking_type = 'SCORE' THEN rc.total_score END DESC NULLS LAST, + CASE WHEN rc.ranking_type = 'PROTOCOL_DATE_TIME' THEN rc.protocol_datetime END ASC NULLS LAST, + rc.protocol_datetime ASC NULLS LAST, + rc.application_id ASC + ) AS natural_rank + FROM ranking_core rc +), +manual_repositioned AS ( + SELECT * + FROM natural_ranked + WHERE COALESCE(ranking_action_type, '') = 'REPOSITION' + AND manual_ranking IS NOT NULL +), +non_manual AS ( + SELECT * + FROM natural_ranked + WHERE COALESCE(ranking_action_type, '') <> 'REPOSITION' + OR manual_ranking IS NULL +), +manual_slots AS ( + SELECT + mr.call_id, + mr.application_id, + mr.company_id, + mr.user_id, + mr.status, + mr.submission_date, + mr.amount_requested, + mr.amount_accepted, + mr.amount, + mr.ranking_type, + mr.protocol_datetime, + mr.protocol_number, + mr.total_score, + mr.ranking_action_type, + mr.manual_ranking, + mr.ndg, + mr.pec_email, + mr.natural_rank, + mr.manual_ranking AS effective_rank_slot + FROM manual_repositioned mr +), +non_manual_shifted AS ( + SELECT + nm.call_id, + nm.application_id, + nm.company_id, + nm.user_id, + nm.status, + nm.submission_date, + nm.amount_requested, + nm.amount_accepted, + nm.amount, + nm.ranking_type, + nm.protocol_datetime, + nm.protocol_number, + nm.total_score, + nm.ranking_action_type, + nm.manual_ranking, + nm.ndg, + nm.pec_email, + nm.natural_rank, + nm.natural_rank + + COALESCE(( + SELECT COUNT(*)::bigint + FROM manual_repositioned mr + WHERE mr.call_id = nm.call_id + AND mr.manual_ranking <= nm.natural_rank + ), 0) AS effective_rank_slot + FROM non_manual nm +), +combined AS ( + SELECT * FROM manual_slots + UNION ALL + SELECT * FROM non_manual_shifted +), +max_slot_by_call AS ( + SELECT call_id, MAX(effective_rank_slot) AS max_slot + FROM combined + GROUP BY call_id +), +remove_placed AS ( + SELECT + ra.call_id, + ra.application_id, + ra.company_id, + ra.user_id, + ra.status, + ra.submission_date, + ra.amount_requested, + ra.amount_accepted, + ra.amount, + ra.ranking_type, + ra.protocol_datetime, + ra.protocol_number, + ra.total_score, + ra.ranking_action_type, + ra.manual_ranking, + ra.ndg, + ra.pec_email, + NULL::bigint AS natural_rank, + COALESCE(ms.max_slot, 0) + + ROW_NUMBER() OVER ( + PARTITION BY ra.call_id + ORDER BY ra.protocol_datetime ASC NULLS LAST, ra.application_id + ) AS effective_rank_slot + FROM remove_apps ra + LEFT JOIN max_slot_by_call ms ON ms.call_id = ra.call_id +), +final_rows AS ( + SELECT * FROM combined + UNION ALL + SELECT * FROM remove_placed +) +SELECT + ROW_NUMBER() OVER ( + PARTITION BY fr.call_id + ORDER BY fr.effective_rank_slot, fr.protocol_datetime ASC NULLS LAST, fr.application_id + ) AS listing_rank, + fr.application_id, + fr.call_id, + fr.ranking_action_type, + fr.total_score, + fr.user_id, + fr.status, + fr.submission_date, + fr.protocol_datetime, + fr.protocol_number, + fr.ndg, + fr.amount_accepted, + fr.pec_email, + fr.manual_ranking, + fr.ranking_type +FROM final_rows fr; diff --git a/src/main/resources/message_en.properties b/src/main/resources/message_en.properties index 4d8bb42b..6d19f024 100644 --- a/src/main/resources/message_en.properties +++ b/src/main/resources/message_en.properties @@ -61,6 +61,12 @@ status.same.error=Status is already set. invalid.status.change.from.draft=Status cannot be changed to READY_TO_PUBLISH or PUBLISH from DRAFT. status.cannot.be.changed=Status cannot be changed. published.call.not.update=Published call cannot be updated. +published.call.step1.only.ranking.type.allowed=For a published call, this step accepts only ranking type. +call.ranking.type.updated.successfully=Call ranking type updated successfully. +application.ranking.action.updated.successfully=Application ranking action updated successfully. +application.ranking.fetched.successfully=Application ranking fetched successfully. +call.must.be.closed.for.ranking.action=Ranking action is allowed only when the call is closed. +application.ranking.action.invalid=Invalid application ranking action request. invalid.status.change.from.publish=Status cannot be changed to READY_TO_PUBLISH from PUBLISH. invalid.status.change.from.publish.to.draft=Status cannot be changed to DRAFT from PUBLISH as Applications are already created for this CALL. validation.table.message=Data for field {0} is not present. diff --git a/src/main/resources/message_it.properties b/src/main/resources/message_it.properties index 75c0488e..1462b30f 100644 --- a/src/main/resources/message_it.properties +++ b/src/main/resources/message_it.properties @@ -61,6 +61,12 @@ status.same.error=Lo stato ? gi? impostato. invalid.status.change.from.draft=Lo stato non pu? essere cambiato in READY_TO_PUBLISH o PUBLISH da DRAFT. status.cannot.be.changed=Lo stato non pu? essere cambiato. published.call.not.update=Il bando pubblicato non pu? essere aggiornato. +published.call.step1.only.ranking.type.allowed=Per un bando pubblicato, questo passaggio accetta solo il tipo di graduatoria; omettere tutti gli altri campi. +call.ranking.type.updated.successfully=Tipo di graduatoria del bando aggiornato correttamente. +application.ranking.action.updated.successfully=Azione di graduatoria sulla domanda aggiornata correttamente. +application.ranking.fetched.successfully=Graduatoria recuperata correttamente. +call.must.be.closed.for.ranking.action=L'azione di graduatoria ďż˝ consentita solo se il bando ďż˝ chiuso (scaduto). +application.ranking.action.invalid=Richiesta di azione sulla graduatoria non valida. invalid.status.change.from.publish=Lo stato non pu? essere modificato in READY_TO_PUBLISH da PUBLISH. invalid.status.change.from.publish.to.draft=Lo stato non pu essere modificato da PUBLISH a DRAFT poich sono gi state create applicazioni per questa CALL. @@ -71,7 +77,7 @@ email.already.exists=Esiste gi? un utente con questa email. invalid_user=Validazione utente fallita. Controlla le informazioni, lo stato dell'account e la scadenza del token. #Global messages -common_message=Qualcosa č andato storto. Riprova. +common_message=Qualcosa ďż˝ andato storto. Riprova. invalid_signature=Gettone non valido. invalid_login=Nome utente o password errati req_validation_er=Errore di convalida @@ -425,7 +431,7 @@ amendment.must.be.approved.first=L'emendamento deve essere approvato dal diretto upload.company.document.to.application=Documento aziendale caricato correttamente nell'applicazione. company.document.not.found.with.ids=Documento aziendale non trovato. ID mancanti: {0} amount.field.not.provided= Si prega di fornire i campi obbligatori per l'importo. -vat.or.tax.code.required=Č obbligatorio il numero di partita IVA o il codice fiscale. +vat.or.tax.code.required=ďż˝ obbligatorio il numero di partita IVA o il codice fiscale. provide.valid.vat.number=Inserisci un numero di partita IVA valido per procedere con NDG. -application.status.transition.restricted=Questa modifica di stato non č disponibile tramite questa operazione. +application.status.transition.restricted=Questa modifica di stato non ďż˝ disponibile tramite questa operazione. application.registry.segment.invalid=Valore del segmento non valido. \ No newline at end of file From 34a17e4f5251ab31db2d9404e24e46421725bd91 Mon Sep 17 00:00:00 2001 From: rajesh Date: Wed, 8 Apr 2026 17:33:20 +0530 Subject: [PATCH 40/42] Updated code --- src/main/resources/db/changelog/db.changelog-1.0.0.xml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/main/resources/db/changelog/db.changelog-1.0.0.xml b/src/main/resources/db/changelog/db.changelog-1.0.0.xml index 3bba5cbd..ce4d8146 100644 --- a/src/main/resources/db/changelog/db.changelog-1.0.0.xml +++ b/src/main/resources/db/changelog/db.changelog-1.0.0.xml @@ -3225,11 +3225,6 @@ - - - - - From b934665570427afbd111c90fb7d1e00286c264f0 Mon Sep 17 00:00:00 2001 From: rajesh Date: Thu, 9 Apr 2026 12:14:59 +0530 Subject: [PATCH 41/42] Updated mailgun credentials --- src/main/resources/application.properties | 2 +- src/main/resources/db/changelog/db.changelog-1.0.0.xml | 4 ++++ .../db/dump/update_email_config_for_mailgun_09_04_2026.sql | 4 ++++ 3 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 src/main/resources/db/dump/update_email_config_for_mailgun_09_04_2026.sql diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 95d1d9b7..9469ec99 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -57,7 +57,7 @@ active.profile.folder=dev # MailGun API mailGun_base_url=https://api.eu.mailgun.net/ #Below credentials are only for sending mail to rinaldo -mailGun_apiKey= 398e3dea1911fe941af261906ec99362-07e2c238-8094421f +mailGun_apiKey= 541d407d4a8e2cd943d00cb4529f0fba-415ae7fb-7aded414 mailGun_user=comunicazione@paghiamoci.ai mailGun_domainName=paghiamoci.ai diff --git a/src/main/resources/db/changelog/db.changelog-1.0.0.xml b/src/main/resources/db/changelog/db.changelog-1.0.0.xml index ce4d8146..a98b9a58 100644 --- a/src/main/resources/db/changelog/db.changelog-1.0.0.xml +++ b/src/main/resources/db/changelog/db.changelog-1.0.0.xml @@ -3236,4 +3236,8 @@ + + + + diff --git a/src/main/resources/db/dump/update_email_config_for_mailgun_09_04_2026.sql b/src/main/resources/db/dump/update_email_config_for_mailgun_09_04_2026.sql new file mode 100644 index 00000000..bebf92a1 --- /dev/null +++ b/src/main/resources/db/dump/update_email_config_for_mailgun_09_04_2026.sql @@ -0,0 +1,4 @@ +UPDATE hub +SET + email_service_config = 'FgMmDkXJGSWaZIKaR/NyYcilihMOy7gDL/wbeTqYbJeNyUpj4J6ew2qAznFimoTePqoOriC9cyX9Jjlvr2QKsDCL6N2OmozX9uIhqmgvTRS2QLC6oVg4wvwvWJZig3i70YWQQrkXXZQ6f2pg9xzMxYJZ75KpnqFzqudjQ7BSnrzRyK1USODyN7rczkcDSVGn+3cbj0/mWMPWxF1St+DzFt27Sos0pTtoxvXqHorGe6PQauN540vXlUJNA4ouZAh+R3TL4G/vXu46LpqhvqTEDZ24XI8x6rhBHUMKKBLxMe3+ZfIbyCZ9COm5zvEaJ5cB' +WHERE UNIQUE_UUID = 't7jh5wfg9QXylNaTZkPoE'; \ No newline at end of file From a5606c101865b069662ec13537aeaefbafd47427 Mon Sep 17 00:00:00 2001 From: rajesh Date: Thu, 9 Apr 2026 18:07:57 +0530 Subject: [PATCH 42/42] Updated system email templates and role permissions --- .../db/changelog/db.changelog-1.0.0.xml | 42 ++++ ...pdate_system_email_template_09_04_2026.sql | 202 ++++++++++++++++++ 2 files changed, 244 insertions(+) create mode 100644 src/main/resources/db/dump/update_system_email_template_09_04_2026.sql diff --git a/src/main/resources/db/changelog/db.changelog-1.0.0.xml b/src/main/resources/db/changelog/db.changelog-1.0.0.xml index a98b9a58..1722b34b 100644 --- a/src/main/resources/db/changelog/db.changelog-1.0.0.xml +++ b/src/main/resources/db/changelog/db.changelog-1.0.0.xml @@ -3240,4 +3240,46 @@ + + + + + + + + + role_type='ROLE_GEPAFIN_OPERATOR' + + + + + role_type='ROLE_BENEFICIARY' + + + + + role_type='ROLE_PRE_INSTRUCTOR' + + + + + role_type='ROLE_INSTRUCTOR_MANAGER' + + + + + role_type='ROLE_CONFIDI' + + + + + role_type='ROLE_DIRECTOR' + + + + + role_type='ROLE_SUPER_ADMIN' + + + diff --git a/src/main/resources/db/dump/update_system_email_template_09_04_2026.sql b/src/main/resources/db/dump/update_system_email_template_09_04_2026.sql new file mode 100644 index 00000000..d8550728 --- /dev/null +++ b/src/main/resources/db/dump/update_system_email_template_09_04_2026.sql @@ -0,0 +1,202 @@ +-- 1 +UPDATE gepafin_schema.system_email_template +SET template_name = 'Application submission template to beneficiary and company', + "type" = 'APPLICATION_SUBMISSION_TO_USER_AND_COMPANY', + html_content = ' + +
+

Buongiorno,

+

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

+

Distinti Saluti,

+

+ {{email_signature}} +

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

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

+

Distinti Saluti,

+

+ {{email_signature}} +

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

RICHIESTA INTEGRAZIONE DOCUMENTALE

+

Buongiorno,

+

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

+
    + {{form_dataInput}} +
+

{{note}}

+

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

+

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

+

Distinti Saluti,

+

{{email_signature}}

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

Buongiorno,

+

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

+

+ {{tipo_inammissibilita}}.

+

Le motivazioni sono le seguenti: {{form_text}}

+

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

+

Distinti Saluti,

+

{{email_signature}}

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

PROMEMORIA PER LA PRESENTAZIONE DELL''Soccorso Istruttorio

+

Buongiorno,

+

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

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

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

+

Distinti saluti,

+

{{email_signature}}

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

RICHIESTA INTEGRAZIONE DOCUMENTALE

+

Buongiorno,

+

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

+ {{note}} +

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

+

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

+

Distinti Saluti,

+

{{email_signature}}

+
+ + ' +WHERE email_scenario='APPLICATION_AMENDMENT_REQUESTED' AND hub_id=2; + +-- 11 (hub specific reject) +UPDATE gepafin_schema.system_email_template +SET subject='BANDO {{call_name}} – Esito negativo istruttoria di ammissibilità {{company_name}}' +WHERE email_scenario='APPLICATION_REJECTED' AND hub_id=2; + +-- 12 +UPDATE gepafin_schema.system_email_template +SET template_name='Welcome Email for New Bandi User' +WHERE email_scenario='USER_CREATION' AND type='USER_ONBOARDING_BANDI'; + +-- 13 +UPDATE gepafin_schema.system_email_template +SET template_name='Application submission failure notification' +WHERE email_scenario='APPLICATION_SUBMISSION_FAILURE'; + +-- 14 +UPDATE gepafin_schema.system_email_template +SET subject='BANDO – "{{call_name}}" – Esito negativo della valutazione tecnica – {{company_name}}' +WHERE email_scenario='APPLICATION_TECHNICAL_EVALUATION_REJECTED'; + +-- 15 +UPDATE gepafin_schema.system_email_template +SET subject='Comunicazione esito valutazione tecnica ed economico-finanziaria – Avviso {{call_name}} ' +WHERE email_scenario='SPECIAL_APPLICATION_AMENDMENT_REQUESTED'; + +-- 16 +UPDATE gepafin_schema.system_email_template +SET subject='Comunicazione esito valutazione tecnica ed economico-finanziaria – Avviso {{call_name}} ' +WHERE email_scenario='SPECIAL_APPLICATION_AMENDMENT_REQUESTED_BLUE_TONGUE'; + +