diff --git a/pom.xml b/pom.xml
index f3424e44..93a592ed 100644
--- a/pom.xml
+++ b/pom.xml
@@ -88,8 +88,8 @@
com.amazonaws
- aws-java-sdk-s3
- 1.12.312
+ aws-java-sdk
+ 1.12.563
diff --git a/src/main/java/net/gepafin/tendermanagement/constants/AppointmentApiConstant.java b/src/main/java/net/gepafin/tendermanagement/constants/AppointmentApiConstant.java
new file mode 100644
index 00000000..15a3444e
--- /dev/null
+++ b/src/main/java/net/gepafin/tendermanagement/constants/AppointmentApiConstant.java
@@ -0,0 +1,27 @@
+package net.gepafin.tendermanagement.constants;
+
+public class AppointmentApiConstant {
+
+ public static final String ODESSA_LOGIN = "/WSGatewayLogin.apiLogin";
+ public static final String GET_NDG_BY_VAT_NUMBER = "/WSAnagrafica.getListaNdg";
+ public static final String CREATE_VISURA = "/WSAnagrafica.createVisura";
+ public static final String GET_VISURA_LIST = "/WSAnagrafica.getVisuraList";
+ public static final String GET_APPOINTMENT_TEMPLATE = "/WSCrmConsulenza.getAppuntamentoTemplate?idAppuntamentoTemplate=7";
+ public static final String CREATE_APPOINTMENT_FROM_TEMPLATE = "/WSCrmConsulenza.createAppointmentFromTemplate";
+ public static final String UPLOAD_APOOINTMENT_DOCUMENT = "/WSDocumentDetail.createStream";
+
+ //get ndg number
+ public static final int TARGET_PAGE_SIZE = 1;
+ public static final int RECORD_PER_PAGE_SIZE = 10;
+
+//create visura request Body constant
+ public static final boolean CREA_ANAGRAFICA = Boolean.TRUE;
+ public static final boolean SALVA_DOCUMENTI = Boolean.TRUE;
+ public static final String VISURA_PROVIDER = "cerved";
+ public static final String VISURA_TYPE = "StandardReport";
+ public static final String VISURA_MODE = "visure";
+ public static final String COD_AGENTE = "UtenzaAPIPortal";
+ public static final boolean IS_FROM_RATING = Boolean.FALSE;
+ public static final boolean IS_ANAGRAFICA_LEGAME = Boolean.FALSE;
+
+}
diff --git a/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java b/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java
index d60b0da7..63be4b25 100644
--- a/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java
+++ b/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java
@@ -306,5 +306,37 @@ public class GepafinConstant {
public static final String LOGIN_ATTEMPT_ID = "loginAttemptId";
public static final String USER_ACTION_ID = "userActionId";
public static final String ATLEAST_ONE_ID_REQUIRED="atleast.one.id.required";
+
+ //Appointment
+ public static final String NDG_IN_PROGRESS = "IN_PROGRESS";
+ public static final String NDG_AVAILABLE = "ndg.available";
+ public static final String NDG_GENERATION_IS_IN_PROGRESS = "ndg.generation.in.progress";
+ public static final String NDG_GENERATED = "NDG_GENERATED";
+ public static final String NDG_NOT_FOUND_FOR_APPLICATION = "ndg.not.found.for.this.application.or.invalid";
+ public static final String APPOINTMENT_ALREADY_CREATED = "appointment.already.created";
+ public static final String EXTERNAL_DOCUMENT_UPLOAD_FAILURE_MSG = "document.not.uploaded.to.external.system.please.try.again";
+ public static final String PROVIDE_VALID_APPLICATION_DOC_ID = "provide.valid.application.document.id";
+ public static final String DOCUMENT_UPLOADED_SUCCESSFULLY_TO_EXTERNAL_SYSTEM = "document.uploaded.successfully.to.external.system";
+ public static final String ERROR_UPLOADING_DOCUMENT = "error.in.uploading.document.check.input";
+ public static final String DOCUMENT_ALREADY_UPLOADED = "document.already.uploaded";
+ public static final String NDG_NOT_MATCHED_OR_NOT_FOUND = "ndg.not.found.or.not.matched";
+ public static final String NO_NDG_FOR_ANOTHER_HUB = "ndg.generation.is.only.for.gepafin";
+ public static final String NO_APPOINTMENT_FOR_ANOTHER_HUB = "appointment.creation.is.only.for.gepafin";
+ public static final String NO_DOCUMENT_UPLOAD_FOR_ANOTHER_HUB = "upload.document.is.only.for.gepafin";
+ public static final String APPOINTMENT_CREATED = "appointment.created.successfully";
+ public static final String DATA_STRING = "data";
+ public static final String DOCUMENT_ATTACHMENT_ID_STRING = "documentAttachmentId";
+ public static final String TEMP_FILE_PATH = "/tmp/";
+ public static final String RICHIESTA_CLIENTE_STRING = "richiestaCliente";
+ public static final String ID_STRING = "id";
+ public static final String NULL_STRING = "null";
+ public static final String NDG_STRING = "ndg";
+ public static final String ID_VISURA_STRING = "idVisura";
+ public static final String NDG_FETCH_SUCCESSFULLY = "ndg.fetch.successfully";
+ public static final String AUTH_JWT_SECRET_KEY = "hTa5qe$af/4',BFs";
+ public static final String JWT_ALGO_HEADER = "{\"alg\":\"HS256\",\"typ\":\"JWT\"}";
+ public static final String HMAC_ALGO = "HmacSHA256";
+ public static final String ERROR_IN_GENERATING_NDG_TRY_AGAIN = "error.try.again";
+ public static final String POLLING_THREAD_NAME = "Ndg-Polling-Thread-";
}
diff --git a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java
index 1240aa90..dbccf3b3 100644
--- a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java
+++ b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java
@@ -830,6 +830,9 @@ public class ApplicationDao {
if (status.equals(ApplicationStatusTypeEnum.DRAFT) && Boolean.TRUE.equals(applicationEntity.getStatus().equals(ApplicationStatusTypeEnum.AWAITING.getValue()))) {
applicationEntity.setStatus(status.getValue());
}
+ if(status.equals(ApplicationStatusTypeEnum.ADMISSIBLE) && Boolean.TRUE.equals(applicationEntity.getStatus().equals(ApplicationStatusTypeEnum.APPOINTMENT.getValue()))){
+ applicationEntity.setStatus(status.getValue());
+ }
applicationEntity = applicationRepository.save(applicationEntity);
if (!status.equals(ApplicationStatusTypeEnum.SUBMIT)) {
diff --git a/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java b/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java
new file mode 100644
index 00000000..1a443313
--- /dev/null
+++ b/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java
@@ -0,0 +1,889 @@
+package net.gepafin.tendermanagement.dao;
+
+import com.amazonaws.services.s3.AmazonS3Client;
+import com.amazonaws.services.s3.model.GetObjectRequest;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import feign.FeignException;
+import jakarta.servlet.http.HttpServletRequest;
+import lombok.extern.slf4j.Slf4j;
+import net.gepafin.tendermanagement.config.Translator;
+import net.gepafin.tendermanagement.constants.AppointmentApiConstant;
+import net.gepafin.tendermanagement.constants.GepafinConstant;
+import net.gepafin.tendermanagement.entities.ApplicationEntity;
+import net.gepafin.tendermanagement.entities.CompanyEntity;
+import net.gepafin.tendermanagement.entities.DocumentEntity;
+import net.gepafin.tendermanagement.entities.HubEntity;
+import net.gepafin.tendermanagement.enums.ApplicationStatusTypeEnum;
+import net.gepafin.tendermanagement.enums.DocumentSourceTypeEnum;
+import net.gepafin.tendermanagement.enums.VersionActionTypeEnum;
+import net.gepafin.tendermanagement.model.request.AppointmentCreationRequest;
+import net.gepafin.tendermanagement.model.request.AppointmentNdgRequest;
+import net.gepafin.tendermanagement.model.request.AppointmentVisuraListRequest;
+import net.gepafin.tendermanagement.model.request.AppointmentVisuraRequest;
+import net.gepafin.tendermanagement.model.request.CreateAppointmentRequest;
+import net.gepafin.tendermanagement.model.request.UploadDocToExternalSystemRequest;
+import net.gepafin.tendermanagement.model.request.VersionHistoryRequest;
+import net.gepafin.tendermanagement.model.response.AppointmentCreationResponse;
+import net.gepafin.tendermanagement.model.response.AppointmentLoginResponse;
+import net.gepafin.tendermanagement.model.response.DocumentUploadResponse;
+import net.gepafin.tendermanagement.model.response.NdgResponse;
+import net.gepafin.tendermanagement.repositories.ApplicationRepository;
+import net.gepafin.tendermanagement.repositories.CompanyRepository;
+import net.gepafin.tendermanagement.repositories.DocumentRepository;
+import net.gepafin.tendermanagement.repositories.HubRepository;
+import net.gepafin.tendermanagement.service.ApplicationService;
+import net.gepafin.tendermanagement.service.CompanyService;
+import net.gepafin.tendermanagement.service.feignClient.AppointmentApiService;
+import net.gepafin.tendermanagement.util.LoggingUtil;
+import net.gepafin.tendermanagement.util.Utils;
+import net.gepafin.tendermanagement.web.rest.api.errors.CustomValidationException;
+import net.gepafin.tendermanagement.web.rest.api.errors.Status;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.mock.web.MockMultipartFile;
+import org.springframework.stereotype.Component;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+@Slf4j
+@Component
+public class AppointmentDao {
+
+ @Value("${appointment.portal.user}")
+ private String user;
+
+ @Value("${appointment.portal.password}")
+ private String password;
+
+ @Value("${appointment.portal.source}")
+ private String source;
+
+ @Value("${appointment.portal.context}")
+ private String context;
+
+ @Value("${default.hub.uuid}")
+ private String defaultHubUuid;
+
+ @Value("${aws.s3.url}")
+ private String s3Url;
+
+ @Value("${aws.s3.bucket.name}")
+ private String OLD_BUCKET;
+
+ @Value("${flagDaFirmare}")
+ private Boolean flagDaFirmare;
+
+ @Autowired
+ private HubRepository hubRepository;
+
+ @Autowired
+ private AppointmentApiService appointmentApiService;
+
+ @Autowired
+ private ApplicationService applicationService;
+
+ @Autowired
+ private CompanyService companyService;
+
+ @Autowired
+ private ApplicationRepository applicationRepository;
+
+ @Autowired
+ private CompanyRepository companyRepository;
+
+ @Autowired
+ private DocumentDao documentDao;
+
+ @Autowired
+ private AmazonS3Client s3Client;
+
+ @Autowired
+ private DocumentRepository documentRepository;
+
+ @Autowired
+ private HttpServletRequest request;
+
+ @Autowired
+ private LoggingUtil loggingUtil;
+
+ private final Map executorMap = new ConcurrentHashMap<>();
+
+ public NdgResponse checkNdgForAppointment(Long applicationId) {
+
+ try {
+ NdgResponse ndgResponseToReturn = new NdgResponse();
+ // Validate application, company, and hub
+ ApplicationEntity application = applicationService.validateApplication(applicationId);
+ if (application.getNdgStatus() != null && application.getNdgStatus().equalsIgnoreCase(GepafinConstant.NDG_IN_PROGRESS)) {
+ throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.NDG_GENERATION_IS_IN_PROGRESS));
+ }
+ //cloned for old application data
+ ApplicationEntity oldApplicationData = Utils.getClonedEntityForData(application);
+
+ CompanyEntity company = companyService.validateCompany(application.getCompanyId());
+ HubEntity hub = hubRepository.findByHubId(application.getHubId());
+
+ if (!hub.getUniqueUuid().equals(defaultHubUuid)) {
+ log.info("Ndg cannot be created for another Hub, it is default for Gepafin.");
+ throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.NO_NDG_FOR_ANOTHER_HUB));
+ }
+
+ // Check if NDG and idVisura are already present
+ if (isNdgAndIdVisuraPresent(application)) {
+ log.info("NDG already exist for applicationId: {}", applicationId);
+ ndgResponseToReturn.setNdg(application.getNdg());
+ return ndgResponseToReturn;
+ }
+
+ // Authenticate and fetch token if required
+ if (hub.getAppointmentAuthTokenId() == null && hub.getAreaCode() == null) {
+ hub = authenticateAndSaveToken(hub);
+ }
+
+ String authorizationToken = getBearerToken(hub);
+
+ // Try retrieving NDG by VAT number
+ AppointmentLoginResponse ndgResponse = retrieveNdgByVatNumber(company.getVatNumber(), authorizationToken, hub, application);
+ //For testing purpose Commenting it
+ if (isNdgValid(ndgResponse.getNdg())) {
+ saveNdgAndIdVisura(application, company, ndgResponse.getNdg(), null);
+ ndgResponseToReturn.setNdg(application.getNdg());
+ return ndgResponseToReturn;
+ }
+
+ return getNdgResponse(company, authorizationToken, hub, application, ndgResponseToReturn, oldApplicationData);
+ } catch (FeignException e) {
+ log.error("Error in feign client call during NDG handling: {}", e.getMessage(), e);
+ Utils.callException(e.status(), e);
+ } catch (CustomValidationException e) {
+ log.info("Custom validation exception: {}", e.getMessage());
+ throw e;
+ } catch (Exception e) {
+ log.error("Error during NDG handling: {}", e.getMessage(), e);
+ throw new RuntimeException("Error during fetching NDG.");
+ }
+ throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.ERROR_IN_GENERATING_NDG_TRY_AGAIN));
+ }
+
+ private NdgResponse getNdgResponse(CompanyEntity company, String authorizationToken, HubEntity hub, ApplicationEntity application, NdgResponse ndgResponseToReturn,
+ ApplicationEntity oldApplicationData) {
+ // Create Visura if NDG is not found
+ AppointmentLoginResponse visuraResponse = createVisura(company, authorizationToken, hub);
+ if (isNdgValid(visuraResponse.getNdg())) {
+ saveNdgAndIdVisura(application, company, visuraResponse.getNdg(), visuraResponse.getIdVisura());
+ ndgResponseToReturn.setNdg(application.getNdg());
+ } else if (visuraResponse.getIdVisura() != null) {
+ application.setNdgStatus(GepafinConstant.NDG_IN_PROGRESS);
+ application.setStatus(ApplicationStatusTypeEnum.NDG.getValue());
+ applicationRepository.save(application);
+
+ /** This code is responsible for adding a version history log for the "Updating ndg status in application" operation. **/
+ loggingUtil.addVersionHistory(
+ VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldApplicationData).newData(application).build());
+
+ startNdgPollingTask(application, company, hub);
+ throw new CustomValidationException(Status.SUCCESS, Translator.toLocale(GepafinConstant.NDG_GENERATION_IS_IN_PROGRESS));
+ }
+
+ return ndgResponseToReturn;
+ }
+
+ private static String getBearerToken(HubEntity hub) {
+
+ return "Bearer " + hub.getAppointmentAuthTokenId();
+ }
+
+ private void startNdgPollingTask(ApplicationEntity application, CompanyEntity company, HubEntity hub) {
+ //cloned for all data
+ ApplicationEntity oldApplicationData = Utils.getClonedEntityForData(application);
+ CompanyEntity oldCompanyData = Utils.getClonedEntityForData(company);
+
+ // Check if a thread is already running for this application
+ if (executorMap.containsKey(application.getId())) {
+ log.warn("Polling task already running for applicationId: {}", application.getId());
+ return;
+ }
+
+ // Create a dedicated thread (single-threaded executor) for this application
+ ExecutorService executor = Executors.newSingleThreadExecutor(runnable -> {
+ Thread thread = new Thread(runnable);
+ thread.setName(GepafinConstant.POLLING_THREAD_NAME + application.getId());
+ return thread;
+ });
+ executorMap.put(application.getId(), executor);
+
+ // Submit polling task to this thread
+ executor.submit(() -> {
+ try {
+ log.info("Polling task started for applicationId: {} on thread: {}", application.getId(), Thread.currentThread().getName());
+ long startTime = System.currentTimeMillis();
+
+ while (true) {
+ if (application.getNdg() != null)
+ break;
+ try {
+ String visuraListJson = getVisuraList(application.getIdVisura(), hub.getAppointmentAuthTokenId(), application, hub);
+ String ndg = parseNdgFromVisuraListResponse(visuraListJson);
+
+ if (isNdgValid(ndg)) {
+ company.setNdg(ndg);
+ application.setNdgStatus(GepafinConstant.NDG_GENERATED);
+ application.setStatus(ApplicationStatusTypeEnum.NDG.getValue());
+ application.setNdg(ndg);
+ applicationRepository.save(application);
+ companyRepository.save(company);
+
+ log.info("NDG obtained for applicationId: {} and saved successfully.", application.getId());
+ break; // Exit the loop after successful NDG retrieval
+ } else {
+ log.warn("NDG not found for applicationId: {} in Visura List API response.", application.getId());
+ }
+
+ // Check if polling time has exceeded the limit
+ if (System.currentTimeMillis() - startTime > TimeUnit.HOURS.toMillis(2)) {
+ log.warn("Polling timed out for applicationId: {}", application.getId());
+ break;
+ }
+
+ // Wait before the next polling attempt
+ Thread.sleep(TimeUnit.MINUTES.toMillis(15));
+
+ } catch (InterruptedException e) {
+ log.warn("Polling task interrupted for applicationId: {}", application.getId());
+ Thread.currentThread().interrupt();
+ break;
+ } catch (Exception e) {
+ log.error("Error during NDG polling for applicationId: {}", application.getId(), e);
+ }
+ }
+ } finally {
+ // Cleanup: Shut down the thread for this application
+ executor.shutdown();
+ executorMap.remove(application.getId());
+ log.info("Polling task completed and thread shut down for applicationId: {}", application.getId());
+ }
+ });
+ /** This code is responsible for adding a version history log for the "Update application ndgCode and ndgStatus" operation. **/
+ loggingUtil.addVersionHistory(
+ VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldApplicationData).newData(application).build());
+
+ /** This code is responsible for adding a version history log for the "Update company ndgCode" operation. **/
+ loggingUtil.addVersionHistory(VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldCompanyData).newData(company).build());
+ }
+
+ private boolean isNdgAndIdVisuraPresent(ApplicationEntity application) {
+
+ String ndg = application.getNdg();
+ String idVisura = application.getIdVisura();
+
+ if (ndg != null && idVisura == null) {
+ return true;
+ } else if (ndg == null && idVisura != null) {
+ return false;
+ } else
+ return ndg != null;
+ }
+
+ private boolean isNdgValid(String ndg) {
+
+ return ndg != null && !ndg.isEmpty();
+ }
+
+ private void saveNdgAndIdVisura(ApplicationEntity application, CompanyEntity company, String ndg, String idVisura) {
+
+ //cloned for old application and company data
+ ApplicationEntity oldApplicationData = Utils.getClonedEntityForData(application);
+ CompanyEntity oldCompanyData = Utils.getClonedEntityForData(company);
+
+ application.setNdg(ndg);
+ application.setIdVisura(idVisura);
+ application.setNdgStatus(GepafinConstant.NDG_GENERATED);
+ application.setStatus(ApplicationStatusTypeEnum.NDG.getValue());
+ company.setNdg(ndg);
+ companyRepository.save(company);
+ applicationRepository.save(application);
+
+ /** This code is responsible for adding a version history log for the "update application ndg code, status, and Id visura" operation. **/
+ loggingUtil.addVersionHistory(
+ VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldApplicationData).newData(application).build());
+
+ /** This code is responsible for adding a version history log for the "update company ndg code" operation. **/
+ loggingUtil.addVersionHistory(VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldCompanyData).newData(company).build());
+
+ log.info("NDG saved for applicationId: {}, {}", application.getId(), application.getNdg());
+ }
+
+ private String getVisuraList(String idVisura, String authorizationToken, ApplicationEntity application, HubEntity hub) {
+
+ AppointmentVisuraListRequest visuraListRequest = new AppointmentVisuraListRequest();
+ AppointmentVisuraListRequest.VisuraFilter filter = new AppointmentVisuraListRequest.VisuraFilter();
+ filter.setIdVisura(idVisura);
+ visuraListRequest.setFilter(filter);
+
+ try {
+ String requestJson = Utils.convertObjectToJson(visuraListRequest);
+ ResponseEntity