From 3b015252e22b6274c6055fa3e1cc40fa7cab65ad Mon Sep 17 00:00:00 2001 From: rajesh Date: Thu, 5 Jun 2025 19:40:31 +0530 Subject: [PATCH 1/6] Done ticket GEPAFINBE-228 --- .../constants/GepafinConstant.java | 2 +- .../tendermanagement/dao/AppointmentDao.java | 277 +++++++++++------- .../enums/HttpMethodEnum.java | 18 ++ .../gepafin/tendermanagement/util/Utils.java | 11 + 4 files changed, 203 insertions(+), 105 deletions(-) create mode 100644 src/main/java/net/gepafin/tendermanagement/enums/HttpMethodEnum.java diff --git a/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java b/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java index 57139e52..90988320 100644 --- a/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java +++ b/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java @@ -571,7 +571,7 @@ public class GepafinConstant { public static final String PROTOCOL_EXTERNAL_NUMBER="NUM_PG"; public static final String PROTOCOL_EXTERNAL_DATE="DATA_PG_DT"; public static final String PROTOCOL_DOC_SUFFIX="#noauth"; - + public static final String CREATE_NDG="CHECK_OR_CREATE_NDG_CODE"; } diff --git a/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java b/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java index b8507b3a..f9a1532b 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java @@ -18,10 +18,7 @@ import net.gepafin.tendermanagement.entities.ApplicationEvaluationEntity; 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.NotificationTypeEnum; -import net.gepafin.tendermanagement.enums.VersionActionTypeEnum; +import net.gepafin.tendermanagement.enums.*; import net.gepafin.tendermanagement.model.request.AppointmentCreationRequest; import net.gepafin.tendermanagement.model.request.AppointmentNdgRequest; import net.gepafin.tendermanagement.model.request.AppointmentVisuraListRequest; @@ -66,7 +63,10 @@ import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.ScheduledExecutorService; @Slf4j @Component @@ -150,8 +150,9 @@ public class AppointmentDao { @Autowired private ApplicationEvaluationDao applicationEvaluationDao; - private final Map executorMap = new ConcurrentHashMap<>(); + private final Map executorMap = new ConcurrentHashMap<>(); + private final ConcurrentHashMap threadForDocumentMap = new ConcurrentHashMap<>(); private static final ThreadLocal threadLocalHubId = new ThreadLocal<>(); @@ -432,35 +433,89 @@ public class AppointmentDao { } private void startAsyncNdgProcessing(Long applicationId) { - // Check if a thread is already running for this application + // If already polling for this applicationId, do nothing: if (executorMap.containsKey(applicationId)) { log.warn("Async processing already running for applicationId: {}", applicationId); return; } - // Create a dedicated thread for asynchronous processing - ExecutorService executor = Executors.newSingleThreadExecutor(runnable -> { - Thread thread = new Thread(runnable); - thread.setName("AsyncNdgProcessing-" + applicationId); - return thread; + ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(runnable -> { + Thread t = new Thread(runnable); + t.setName("AsyncNdgProcessing-" + applicationId); + return t; }); - executorMap.put(applicationId, executor); + executorMap.put(applicationId, scheduler); - executor.submit(() -> { + // Record the start time so we can stop after 2 hours: + long startTime = System.currentTimeMillis(); + long twoHoursMs = TimeUnit.HOURS.toMillis(2); + long fifteenMin = 15; // in MINUTES + + // We need a reference to cancel the scheduled task from inside itself when we're done: + AtomicReference> futureRef = new AtomicReference<>(); + + Runnable pollingTask = () -> { try { - log.info("Starting async processing for applicationId: {}", applicationId); - processNdgGeneration(applicationId); - } catch (Exception e) { - log.error("Error in async NDG processing for applicationId: {}", applicationId, e); - } finally { - // Cleanup resources - ExecutorService executorToShutdown = executorMap.remove(applicationId); - if (executorToShutdown != null) { - executorToShutdown.shutdown(); + // 1) If 2 hours have already passed, mark as FAILED and shut down: + if (System.currentTimeMillis() - startTime > twoHoursMs) { + ApplicationEntity app = applicationService.validateApplication(applicationId); + log.warn("2-hour timeout reached for applicationId {}. Marking NDG_FAILED.", applicationId); + app.setNdgStatus(GepafinConstant.NDG_FAILED); + applicationRepository.save(app); + + futureRef.get().cancel(false); + shutdownScheduler(applicationId); + return; + } + + // 2) Otherwise, call processNdgGeneration once: + processNdgGeneration(applicationId); + + // 3) After return, check if NDG is now set or timed out. If so, cancel & shut down: + ApplicationEntity updated = applicationService.validateApplication(applicationId); + if (isNdgValid(updated.getNdg())) { + log.info("NDG found for applicationId {}. Shutting down scheduler.", applicationId); + futureRef.get().cancel(false); + shutdownScheduler(applicationId); + } else if (updated.getNdgStatus() != null && updated.getNdgStatus().equals(GepafinConstant.NDG_FAILED)) { + log.info("NDG status is NDG_FAILED for applicationId {}. Shutting down scheduler.", applicationId); + futureRef.get().cancel(false); + shutdownScheduler(applicationId); + } + // Otherwise: no NDG yet, not timed out → next run happens in 15 minutes automatically. + } catch (Exception ex) { + log.error("Unexpected error during scheduled polling for applicationId {}: {}", applicationId, ex.getMessage(), ex); + try { + ApplicationEntity checkApp = applicationService.validateApplication(applicationId); + if (System.currentTimeMillis() - startTime > twoHoursMs) { + log.warn("After exception, 2-hour window passed for applicationId {}. Marking NDG_FAILED.", applicationId); + checkApp.setNdgStatus(GepafinConstant.NDG_FAILED); + applicationRepository.save(checkApp); + + futureRef.get().cancel(false); + shutdownScheduler(applicationId); + } + } catch (Exception ignore) { + futureRef.get().cancel(false); + shutdownScheduler(applicationId); } - log.info("Async processing completed for applicationId: {}", applicationId); } - }); + }; + + // Schedule pollingTask: run now (delay=0), then every fifteen minutes: + ScheduledFuture future = scheduler.scheduleWithFixedDelay(pollingTask, 0, // initial delay = 0 min → run immediately + fifteenMin, // subsequent runs every 15 minutes + TimeUnit.MINUTES); + futureRef.set(future); + } + + private void shutdownScheduler(Long applicationId) { + + ScheduledExecutorService shed = executorMap.remove(applicationId); + if (shed != null) { + shed.shutdownNow(); + } + log.info("Scheduler shut down for applicationId: {}", applicationId); } private void processNdgGeneration(Long applicationId) { @@ -489,97 +544,108 @@ public class AppointmentDao { saveNdgAndIdVisura(application, company, ndgResponse.getNdg()); log.info("NDG successfully generated for applicationId: {}", applicationId); } else { - // If NDG isn't immediately available, start polling + log.info("Polling for NDG for applicationId: {}", applicationId); handleNdgPolling(application, company, hub, authorizationToken); } } catch (Exception e) { - log.error("Exception occurred during NDG generation. ApplicationId: {}, CompanyId: {}, HubId: {}, Error: {}", - applicationId, company.getId(), hub.getId(), e.getMessage(), e); + log.error("Exception occurred during NDG generation. ApplicationId: {}, CompanyId: {}, HubId: {}, Error: {}", applicationId, company.getId(), hub.getId(), + e.getMessage(), e); } } private void handleNdgPolling(ApplicationEntity application, CompanyEntity company, HubEntity hub, String authorizationToken) { + log.info("Starting single‐shot NDG polling attempt for applicationId: {}, CompanyId: {}, HubId: {}", application.getId(), company.getId(), hub.getId()); + + long startTime = System.currentTimeMillis(); + long twoHoursMs = TimeUnit.HOURS.toMillis(2); + try { - log.info("Starting NDG polling for applicationId: {}, CompanyId: {}, HubId: {}", application.getId(),company.getId(), hub.getId()); - long startTime = System.currentTimeMillis(); - - while (true) { - if (application.getNdg() != null) { - log.info("NDG retrieved for applicationId: {}", application.getId()); - break; - } - - try { - // Fetch Ndg via creating visura - AppointmentLoginResponse visuraResponse = createVisura(company, authorizationToken, hub); - if (isNdgValid(visuraResponse.getNdg())) { - log.info("Valid NDG retrieved from create visura api response: {} | ApplicationId: {}", visuraResponse.getNdg(), application.getId()); - company.setNdg(visuraResponse.getNdg()); - application.setNdg(visuraResponse.getNdg()); - application.setNdgStatus(GepafinConstant.NDG_GENERATED); - application.setStatus(ApplicationStatusTypeEnum.NDG.getValue()); - application.setIdVisura(visuraResponse.getIdVisura()); - applicationRepository.save(application); - companyRepository.save(company); - ApplicationEvaluationEntity applicationEvaluationEntity = applicationEvaluationService.validateApplicationEvaluation( - application.getApplicationEvaluationId()); - Map placeHolders = new HashMap<>(); - placeHolders.put("{{call_name}}", application.getCall().getName()); - placeHolders.put("{{protocol_number}}", String.valueOf(application.getProtocol().getProtocolNumber())); - notificationDao.sendNotificationToInstructor(placeHolders, applicationEvaluationEntity, NotificationTypeEnum.NDG_GENERATION); - notificationDao.sendNotificationToSuperUser(application, placeHolders, NotificationTypeEnum.NDG_GENERATION); - log.info("Got NDG and saved successfully for applicationId: {}", application.getId()); - break; - } else { - // Fetch Visura list and attempt to parse NDG - String visuraListJson = getVisuraList(visuraResponse.getIdVisura(), authorizationToken, application, hub); - log.debug("Parsing NDG from visura list response | ApplicationId: {}", application.getId()); - String ndg = parseNdgFromVisuraListResponse(visuraListJson); - if (isNdgValid(ndg)) { - // CompanyEntity oldCompanyData = Utils.getClonedEntityForData(company); - // ApplicationEntity oldApplicationData = Utils.getClonedEntityForData(application); - - log.info("Valid NDG retrieved: {} | ApplicationId: {}", ndg, application.getId()); - company.setNdg(ndg); - application.setNdg(ndg); - application.setNdgStatus(GepafinConstant.NDG_GENERATED); - application.setStatus(ApplicationStatusTypeEnum.NDG.getValue()); - application.setIdVisura(visuraResponse.getIdVisura()); - applicationRepository.save(application); - companyRepository.save(company); - ApplicationEvaluationEntity applicationEvaluationEntity = applicationEvaluationService.validateApplicationEvaluation( - application.getApplicationEvaluationId()); - Map placeHolders = new HashMap<>(); - placeHolders.put("{{call_name}}", application.getCall().getName()); - placeHolders.put("{{protocol_number}}", String.valueOf(application.getProtocol().getProtocolNumber())); - notificationDao.sendNotificationToInstructor(placeHolders, applicationEvaluationEntity, NotificationTypeEnum.NDG_GENERATION); - notificationDao.sendNotificationToSuperUser(application, placeHolders, NotificationTypeEnum.NDG_GENERATION); - log.info("NDG saved successfully for applicationId: {}", application.getId()); - break; - } - } - // Check if polling has timed out - if (System.currentTimeMillis() - startTime > TimeUnit.HOURS.toMillis(2)) { - log.warn("NDG polling timed out for applicationId: {}", application.getId()); - application.setNdgStatus(GepafinConstant.NDG_FAILED); - applicationRepository.save(application); - break; - } - - // Wait before the next polling attempt - Thread.sleep(TimeUnit.MINUTES.toMillis(15)); - } catch (InterruptedException e) { - log.warn("NDG polling interrupted for applicationId: {}", application.getId()); - Thread.currentThread().interrupt(); - break; - } catch (Exception e) { - log.error("Error during NDG polling for applicationId: {}", application.getId(), e); - } + // 1) If NDG was already populated (perhaps by another thread), skip polling. + if (application.getNdg() != null) { + log.info("NDG already present for applicationId {}. Exiting single‐shot polling.", application.getId()); + return; + } + + // 2) Attempt to create Visura (this may immediately return a valid NDG) + AppointmentLoginResponse visuraResponse = createVisura(company, authorizationToken, hub); + + // 2a) If createVisura gave us a valid NDG, persist & exit + String fetchedNdg = visuraResponse.getNdg(); + if (isNdgValid(fetchedNdg)) { + log.info("Valid NDG retrieved from createVisura(): {} | applicationId: {}", fetchedNdg, application.getId()); + + company.setNdg(fetchedNdg); + application.setNdg(fetchedNdg); + application.setNdgStatus(GepafinConstant.NDG_GENERATED); + application.setStatus(ApplicationStatusTypeEnum.NDG.getValue()); + application.setIdVisura(visuraResponse.getIdVisura()); + + companyRepository.save(company); + applicationRepository.save(application); + + ApplicationEvaluationEntity eval = applicationEvaluationService.validateApplicationEvaluation(application.getApplicationEvaluationId()); + Map placeholders = new HashMap<>(); + placeholders.put("{{call_name}}", application.getCall().getName()); + placeholders.put("{{protocol_number}}", String.valueOf(application.getProtocol().getProtocolNumber())); + notificationDao.sendNotificationToInstructor(placeholders, eval, NotificationTypeEnum.NDG_GENERATION); + notificationDao.sendNotificationToSuperUser(application, placeholders, NotificationTypeEnum.NDG_GENERATION); + + log.info("NDG saved successfully for applicationId: {}", application.getId()); + return; + } + + // 2b) If no immediate NDG, fetch the Visura list JSON and parse out NDG + String visuraListJson = getVisuraList(visuraResponse.getIdVisura(), authorizationToken, application, hub); + log.debug("Parsing NDG from VisuraList JSON for applicationId: {}", application.getId()); + String parsedNdg = parseNdgFromVisuraListResponse(visuraListJson); + + if (isNdgValid(parsedNdg)) { + log.info("Valid NDG parsed from VisuraList: {} | applicationId: {}", parsedNdg, application.getId()); + + company.setNdg(parsedNdg); + application.setNdg(parsedNdg); + application.setNdgStatus(GepafinConstant.NDG_GENERATED); + application.setStatus(ApplicationStatusTypeEnum.NDG.getValue()); + application.setIdVisura(visuraResponse.getIdVisura()); + + companyRepository.save(company); + applicationRepository.save(application); + + ApplicationEvaluationEntity eval = applicationEvaluationService.validateApplicationEvaluation(application.getApplicationEvaluationId()); + Map placeholders = new HashMap<>(); + placeholders.put("{{call_name}}", application.getCall().getName()); + placeholders.put("{{protocol_number}}", String.valueOf(application.getProtocol().getProtocolNumber())); + notificationDao.sendNotificationToInstructor(placeholders, eval, NotificationTypeEnum.NDG_GENERATION); + notificationDao.sendNotificationToSuperUser(application, placeholders, NotificationTypeEnum.NDG_GENERATION); + + log.info("NDG saved successfully for applicationId: {}", application.getId()); + return; + } + + // 3) Neither direct API nor parsing yielded a valid NDG. Check timeout. + if (System.currentTimeMillis() - startTime > twoHoursMs) { + log.warn("NDG polling timed out after 2 hours for applicationId: {}", application.getId()); + application.setNdgStatus(GepafinConstant.NDG_FAILED); + applicationRepository.save(application); + return; + } + + // 4) No NDG yet—just return. The scheduler will retry in 15 minutes. + log.info("No valid NDG yet for applicationId {}. Returning to scheduler—next attempt in 15 minutes.", application.getId()); + } catch (Exception e) { + log.error("Exception during NDG polling for applicationId: {}", application.getId(), e); + // If timeout after exception, mark NDG_FAILED + if (System.currentTimeMillis() - startTime > twoHoursMs) { + log.warn("Example: exiting single‐shot polling due to timeout after exception for applicationId: {}", application.getId()); + application.setNdgStatus(GepafinConstant.NDG_FAILED); + applicationRepository.save(application); + } else { + log.info("Exception occurred but not timed out for applicationId {}. Returning to scheduler for next attempt in 15 minutes.", application.getId()); } - } finally { - log.info("NDG polling completed for applicationId: {}", application.getId()); } + + log.info("NDG polling completed for applicationId: {}", application.getId()); } private static String getBearerToken(HubEntity hub) { @@ -594,12 +660,15 @@ public class AppointmentDao { private void saveNdgAndIdVisura(ApplicationEntity application, CompanyEntity company, String ndg) { + ApplicationEntity oldApplication = Utils.getClonedEntityForData(application); application.setNdg(ndg); application.setNdgStatus(GepafinConstant.NDG_GENERATED); application.setStatus(ApplicationStatusTypeEnum.NDG.getValue()); company.setNdg(ndg); companyRepository.save(company); applicationRepository.save(application); + loggingUtil.addVersionHistory( + VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldApplication).newData(application).build()); ApplicationEvaluationEntity applicationEvaluationEntity = applicationEvaluationService.validateApplicationEvaluation(application.getApplicationEvaluationId()); // Map placeHolders = notificationDao.sendNotificationToBeneficiary(application, NotificationTypeEnum.NDG_GENERATION); Map placeHolders = new HashMap<>(); diff --git a/src/main/java/net/gepafin/tendermanagement/enums/HttpMethodEnum.java b/src/main/java/net/gepafin/tendermanagement/enums/HttpMethodEnum.java new file mode 100644 index 00000000..f71842fb --- /dev/null +++ b/src/main/java/net/gepafin/tendermanagement/enums/HttpMethodEnum.java @@ -0,0 +1,18 @@ +package net.gepafin.tendermanagement.enums; + +import com.fasterxml.jackson.annotation.JsonValue; + +public enum HttpMethodEnum { + POST("POST"), PUT("PUT"), GET("GET"), DELETE("DELETE"); + + private String value; + + HttpMethodEnum(String value) { + this.value = value; + } + + @JsonValue + public String getValue() { + return value; + } +} \ No newline at end of file diff --git a/src/main/java/net/gepafin/tendermanagement/util/Utils.java b/src/main/java/net/gepafin/tendermanagement/util/Utils.java index 469d93ba..13cf488e 100644 --- a/src/main/java/net/gepafin/tendermanagement/util/Utils.java +++ b/src/main/java/net/gepafin/tendermanagement/util/Utils.java @@ -1052,5 +1052,16 @@ public class Utils { headers.add(org.apache.http.HttpHeaders.USER_AGENT, "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0"); return headers; } + + public static void setHttpServletRequestForThread(String redirectURI, String methodType,HttpServletRequest request,String remoteUser) { + MockHttpServletRequest mockRequest = new MockHttpServletRequest(); + mockRequest.setRequestURI(redirectURI); + mockRequest.setMethod(methodType); + mockRequest.setRemoteUser(remoteUser); + Long userActionId = (Long) request.getAttribute(GepafinConstant.USER_ACTION_ID); + mockRequest.setAttribute(GepafinConstant.USER_ACTION_ID,userActionId ); + ServletRequestAttributes attributes = new ServletRequestAttributes(mockRequest); + RequestContextHolder.setRequestAttributes(attributes, true); + } } From bfc850b3b3375804efceab5683020afd2977ea75 Mon Sep 17 00:00:00 2001 From: rajesh Date: Fri, 6 Jun 2025 14:28:21 +0530 Subject: [PATCH 2/6] Logging for status EVALUATION to NDG --- .../tendermanagement/dao/AppointmentDao.java | 2636 +++++++++-------- .../gepafin/tendermanagement/util/Utils.java | 11 +- 2 files changed, 1336 insertions(+), 1311 deletions(-) diff --git a/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java b/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java index 15d9ef73..f0fd43cb 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java @@ -1,1307 +1,1331 @@ -package net.gepafin.tendermanagement.dao; - -import com.amazonaws.services.s3.AmazonS3Client; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import feign.FeignException; -import io.jsonwebtoken.Claims; -import jakarta.servlet.http.HttpServletRequest; -import lombok.extern.slf4j.Slf4j; -import net.gepafin.tendermanagement.config.Translator; -import net.gepafin.tendermanagement.config.jwt.TokenProvider; -import net.gepafin.tendermanagement.constants.AppointmentApiConstant; -import net.gepafin.tendermanagement.constants.GepafinConstant; -import net.gepafin.tendermanagement.entities.ApplicationAmendmentRequestEntity; -import net.gepafin.tendermanagement.entities.ApplicationEntity; -import net.gepafin.tendermanagement.entities.ApplicationEvaluationEntity; -import net.gepafin.tendermanagement.entities.CompanyEntity; -import net.gepafin.tendermanagement.entities.DocumentEntity; -import net.gepafin.tendermanagement.entities.HubEntity; -import net.gepafin.tendermanagement.enums.*; -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.repositories.UserRepository; -import net.gepafin.tendermanagement.service.AmazonS3Service; -import net.gepafin.tendermanagement.service.ApplicationService; -import net.gepafin.tendermanagement.service.CompanyService; -import net.gepafin.tendermanagement.service.feignClient.AppointmentApiService; -import net.gepafin.tendermanagement.service.impl.ApplicationEvaluationServiceImpl; -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.*; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicReference; -import java.util.concurrent.ScheduledExecutorService; - -@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; - - @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; - - @Autowired - private TokenProvider tokenProvider; - - @Autowired - private NotificationDao notificationDao; - - @Autowired - private UserRepository userRepository; - - @Autowired - private ApplicationEvaluationServiceImpl applicationEvaluationService; - - @Autowired - private AmazonS3Service amazonS3Service; - - @Autowired - private ApplicationDao applicationDao; - - @Autowired - private ApplicationAmendmentRequestDao applicationAmendmentRequestDao; - - @Autowired - private ApplicationEvaluationDao applicationEvaluationDao; - - private final Map executorMap = new ConcurrentHashMap<>(); - - - private final ConcurrentHashMap threadForDocumentMap = new ConcurrentHashMap<>(); - - private static final ThreadLocal threadLocalHubId = new ThreadLocal<>(); - - public NdgResponse checkNdgForAppointment(Long applicationId) { - log.info("Starting NDG check for appointment. applicationId: {}", applicationId); - ApplicationEntity application = applicationService.validateApplication(applicationId); - NdgResponse ndgResponse = new NdgResponse(); - if (application.getNdgStatus() != null && application.getNdgStatus().equalsIgnoreCase(GepafinConstant.NDG_IN_PROGRESS)) { - log.warn("NDG generation already in progress. applicationId: {}", applicationId); - throw new CustomValidationException(Status.SUCCESS, Translator.toLocale(GepafinConstant.NDG_GENERATION_IS_IN_PROGRESS)); - } - - if (application.getNdgStatus() != null && application.getNdgStatus().equalsIgnoreCase(GepafinConstant.NDG_GENERATED) && application.getNdg() != null) { - ndgResponse.setNdg(application.getNdg()); - return ndgResponse; - } - - // Update application status - log.info("Updating NDG status to IN_PROGRESS. applicationId: {}", applicationId); - application.setNdgStatus(GepafinConstant.NDG_IN_PROGRESS); - applicationRepository.save(application); - - // Start async processing - HubEntity hub = hubRepository.findByHubId(application.getHubId()); - loginToOdessa(hub, application); - startAsyncNdgProcessing(applicationId); - log.info("NDG check initiation completed. applicationId: {}", applicationId); - return ndgResponse; - } - - // private HubEntity loginToOdessa(HubEntity hub, ApplicationEntity application) { - // - // int maxRetries = 3; - // int attempt = 0; - // boolean success = false; - // while (attempt < maxRetries && !success) { - // attempt++; - // try { - // //code to generate token with payload having "iat" epoch timestamp and secret key with no expiry and send in below method call - // String authJwtToken = Utils.generateAuthTokenForLoginToOdessa(); - // log.info("Got the auth for login to odessa {}", authJwtToken); - // hub.setAuthToken(authJwtToken); - // hubRepository.save(hub); - // Map body = Collections.emptyMap(); - // ResponseEntity responseLogin = appointmentApiService.loginWithOdessa(authJwtToken, source, context, user, password, body); - // if (responseLogin.getStatusCode() == HttpStatus.OK) { - // log.info("Login successful to odessa. Parsing response."); - // String loginResponseJson = Utils.convertObjectToJson(responseLogin.getBody()); - // AppointmentLoginResponse parsedResponse = parseLoginResponse(loginResponseJson); - // - // // Validate and save token - // if (parsedResponse.getTokenId() != null) { - // hub.setAppointmentAuthTokenId(parsedResponse.getTokenId()); - // hub.setAreaCode(parsedResponse.getAreaCode()); - // hubRepository.save(hub); - // log.info("Saved new authToken and areaCode for Hub."); - // success = true; - // return hub; - // } else { - // throw new RuntimeException("Login response is missing a valid tokenId for login to odessa system, please try again."); - // } - // } - // throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.ERROR_IN_GENERATING_NDG_TRY_AGAIN)); - // } catch (FeignException.Forbidden forbiddenException) { - // log.error("Failed to login to odessa due to some error"); - // - // // Extract raw response body - // String responseBody = forbiddenException.contentUTF8(); // Extract raw JSON response - // - // // Parse JSON to check for "PasswordExpired" - // try { - // ObjectMapper objectMapper = new ObjectMapper(); - // JsonNode rootNode = objectMapper.readTree(responseBody); - // JsonNode errorsNode = rootNode.path("errors"); - // - // if (errorsNode.isArray()) { - // for (JsonNode error : errorsNode) { - // // Check the main errorCode - // if (GepafinConstant.PASSWORD_EXPIRED.equals(error.path("errorCode").asText())) { - // application.setNdgStatus(GepafinConstant.NDG_FAILED); - // applicationRepository.save(application); - // throw new CustomValidationException(Status.FORBIDDEN, Translator.toLocale(GepafinConstant.PASSWORD_EXPIRED_LOGIN_TO_ODESSA)); - // } - // - // // Check inside "subErrors" - // JsonNode subErrorsNode = error.path("subErrors"); - // if (subErrorsNode.isArray()) { - // for (JsonNode subError : subErrorsNode) { - // if (GepafinConstant.PASSWORD_EXPIRED.equals(subError.path("errorCode").asText())) { - // application.setNdgStatus(GepafinConstant.NDG_FAILED); - // applicationRepository.save(application); - // throw new CustomValidationException(Status.FORBIDDEN, Translator.toLocale(GepafinConstant.PASSWORD_EXPIRED_LOGIN_TO_ODESSA)); - // } - // } - // } - // } - // } - // } catch (IOException e) { - // log.error("Error parsing JSON response: {}", e.getMessage()); - // } - // } catch (Exception e) { - // log.error("Failed to authenticate user on Odessa : {}", e.getMessage(), e); - // throw new RuntimeException("Authentication failed on Odessa. try again", e); - // } - // } - // return null; - // } - // - // - // private HubEntity authenticateAndSaveToken(HubEntity hub) { - // - // int maxRetries = 3; - // int attempt = 0; - // boolean success = false; - // while (attempt < maxRetries && !success) { - // attempt++; - // try { - // //code to generate token with payload having "iat" epoch timestamp and secret key with no expiry and send in below method call - // String authJwtToken = Utils.generateAuthTokenForLoginToOdessa(); - // log.info("Got the auth for login to odessa {}", authJwtToken); - // hub.setAuthToken(authJwtToken); - // hubRepository.save(hub); - // // Prepare the request body (adjust if necessary for login API) - // Map body = Collections.emptyMap(); - // // Perform login API call - // ResponseEntity responseLogin = appointmentApiService.loginWithOdessa(authJwtToken, source, context, user, password, body); - // - // // Handle successful login - // if (responseLogin.getStatusCode() == HttpStatus.OK) { - // log.info("Login successful to odessa. Parsing response."); - // String loginResponseJson = Utils.convertObjectToJson(responseLogin.getBody()); - // AppointmentLoginResponse parsedResponse = parseLoginResponse(loginResponseJson); - // - // // Validate and save token - // if (parsedResponse.getTokenId() != null) { - // hub.setAppointmentAuthTokenId(parsedResponse.getTokenId()); - // hub.setAreaCode(parsedResponse.getAreaCode()); - // hubRepository.save(hub); - // - // log.info("Saved new authToken and areaCode for Hub."); - // success = true; - // return hub; - // } else { - // throw new RuntimeException("Login response is missing a valid tokenId for login to odessa system, please try again."); - // } - // } - // // Handle non-OK response - // throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.ERROR_IN_GENERATING_NDG_TRY_AGAIN)); - // } catch (FeignException.Forbidden forbiddenException) { - // log.error("Failed to login to odessa due to some error occurred."); - // - // // Extract raw response body - // String responseBody = forbiddenException.contentUTF8(); // Extract raw JSON response - // - // // Parse JSON to check for "PasswordExpired" - // try { - // ObjectMapper objectMapper = new ObjectMapper(); - // JsonNode rootNode = objectMapper.readTree(responseBody); - // JsonNode errorsNode = rootNode.path("errors"); - // - // if (errorsNode.isArray()) { - // for (JsonNode error : errorsNode) { - // // Check the main errorCode - // if (GepafinConstant.PASSWORD_EXPIRED.equals(error.path("errorCode").asText())) { - // throw new CustomValidationException(Status.FORBIDDEN, Translator.toLocale(GepafinConstant.PASSWORD_EXPIRED_LOGIN_TO_ODESSA)); - // } - // - // // Check inside "subErrors" - // JsonNode subErrorsNode = error.path("subErrors"); - // if (subErrorsNode.isArray()) { - // for (JsonNode subError : subErrorsNode) { - // if (GepafinConstant.PASSWORD_EXPIRED.equals(subError.path("errorCode").asText())) { - // throw new CustomValidationException(Status.FORBIDDEN, Translator.toLocale(GepafinConstant.PASSWORD_EXPIRED_LOGIN_TO_ODESSA)); - // } - // } - // } - // } - // } - // } catch (IOException e) { - // log.error("Error parsing JSON response: {}", e.getMessage()); - // } - // } catch (Exception e) { - // log.error("Failed to authenticate user on Odessa : {}", e.getMessage(), e); - // throw new RuntimeException("Authentication failed on Odessa. try again", e); - // } - // } - // return null; - // } - - private void loginToOdessa(HubEntity hub, ApplicationEntity application) { - log.info("Starting login to Odessa. HubId: {}, ApplicationId: {}", hub.getId(), application.getId()); - performOdessaLogin(hub, application); - } - - private HubEntity authenticateAndSaveToken(HubEntity hub, ApplicationEntity application) { - - return performOdessaLogin(hub, application); - } - - private HubEntity performOdessaLogin(HubEntity hub, ApplicationEntity application) { - - int maxRetries = 3; - int attempt = 0; - while (attempt < maxRetries) { - attempt++; - try { - String authJwtToken = Utils.generateAuthTokenForLoginToOdessa(); - log.info("Got the auth for login to odessa {}", authJwtToken); - hub.setAuthToken(authJwtToken); - hubRepository.save(hub); - Map body = Collections.emptyMap(); - ResponseEntity responseLogin = appointmentApiService.loginWithOdessa(authJwtToken, source, context, user, password, body); - if (responseLogin.getStatusCode() == HttpStatus.OK) { - log.info("Login to Odessa successful. Parsing response. HubId: {}", hub.getId()); - String loginResponseJson = Utils.convertObjectToJson(responseLogin.getBody()); - AppointmentLoginResponse parsedResponse = parseLoginResponse(loginResponseJson); - - if (parsedResponse.getTokenId() != null) { - hub.setAppointmentAuthTokenId(parsedResponse.getTokenId()); - hub.setAreaCode(parsedResponse.getAreaCode()); - hubRepository.save(hub); - log.info("Saved new authToken and areaCode for Hub."); - return hub; - } else { - log.error("Login response from Odessa missing tokenId. HubId: {}", hub.getId()); - throw new RuntimeException("Login response is missing a valid tokenId for login to odessa system, please try again."); - } - } - throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.ERROR_IN_GENERATING_NDG_TRY_AGAIN)); - } catch (FeignException.Forbidden forbiddenException) { - log.error("Failed to login to odessa due to forbidden error."); - - CheckPasswordExpiredOrErrorInResponse(application, forbiddenException); - } catch (Exception e) { - log.error("Failed to authenticate user on Odessa (Attempt {}): {}", attempt, e.getMessage(), e); - } - } - throw new RuntimeException("Max retries exceeded. Failed to login to Odessa."); - } - private void CheckPasswordExpiredOrErrorInResponse(ApplicationEntity application, FeignException.Forbidden forbiddenException) { - - String responseBody = forbiddenException.contentUTF8(); - - try { - ObjectMapper objectMapper = new ObjectMapper(); - JsonNode rootNode = objectMapper.readTree(responseBody); - JsonNode errorsNode = rootNode.path("errors"); - - if (errorsNode.isArray()) { - for (JsonNode error : errorsNode) { - if (GepafinConstant.PASSWORD_EXPIRED.equals(error.path("errorCode").asText())) { - if (application != null) { - application.setNdgStatus(GepafinConstant.NDG_FAILED); - applicationRepository.save(application); - } - log.warn("Detected PASSWORD_EXPIRED error during Odessa login. ApplicationId: {}", application.getId()); - throw new CustomValidationException(Status.FORBIDDEN, Translator.toLocale(GepafinConstant.PASSWORD_EXPIRED_LOGIN_TO_ODESSA)); - } - - JsonNode subErrorsNode = error.path("subErrors"); - if (subErrorsNode.isArray()) { - for (JsonNode subError : subErrorsNode) { - if (GepafinConstant.PASSWORD_EXPIRED.equals(subError.path("errorCode").asText())) { - if (application != null) { - application.setNdgStatus(GepafinConstant.NDG_FAILED); - applicationRepository.save(application); - } - throw new CustomValidationException(Status.FORBIDDEN, Translator.toLocale(GepafinConstant.PASSWORD_EXPIRED_LOGIN_TO_ODESSA)); - } - } - } - } - } - } catch (IOException e) { - log.error("Unexpected exception during Odessa login.Error: {}",e.getMessage(), e); - } - } - - private void startAsyncNdgProcessing(Long applicationId) { - // If already polling for this applicationId, do nothing: - if (executorMap.containsKey(applicationId)) { - log.warn("Async processing already running for applicationId: {}", applicationId); - return; - } - - ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(runnable -> { - Thread t = new Thread(runnable); - t.setName("AsyncNdgProcessing-" + applicationId); - return t; - }); - executorMap.put(applicationId, scheduler); - - // Record the start time so we can stop after 2 hours: - long startTime = System.currentTimeMillis(); - long twoHoursMs = TimeUnit.HOURS.toMillis(2); - long fifteenMin = 15; // in MINUTES - - // We need a reference to cancel the scheduled task from inside itself when we're done: - AtomicReference> futureRef = new AtomicReference<>(); - - Runnable pollingTask = () -> { - try { - // 1) If 2 hours have already passed, mark as FAILED and shut down: - if (System.currentTimeMillis() - startTime > twoHoursMs) { - ApplicationEntity app = applicationService.validateApplication(applicationId); - log.warn("2-hour timeout reached for applicationId {}. Marking NDG_FAILED.", applicationId); - app.setNdgStatus(GepafinConstant.NDG_FAILED); - applicationRepository.save(app); - - futureRef.get().cancel(false); - shutdownScheduler(applicationId); - return; - } - - // 2) Otherwise, call processNdgGeneration once: - processNdgGeneration(applicationId); - - // 3) After return, check if NDG is now set or timed out. If so, cancel & shut down: - ApplicationEntity updated = applicationService.validateApplication(applicationId); - if (isNdgValid(updated.getNdg())) { - log.info("NDG found for applicationId {}. Shutting down scheduler.", applicationId); - futureRef.get().cancel(false); - shutdownScheduler(applicationId); - } else if (updated.getNdgStatus() != null && updated.getNdgStatus().equals(GepafinConstant.NDG_FAILED)) { - log.info("NDG status is NDG_FAILED for applicationId {}. Shutting down scheduler.", applicationId); - futureRef.get().cancel(false); - shutdownScheduler(applicationId); - } - // Otherwise: no NDG yet, not timed out → next run happens in 15 minutes automatically. - } catch (Exception ex) { - log.error("Unexpected error during scheduled polling for applicationId {}: {}", applicationId, ex.getMessage(), ex); - try { - ApplicationEntity checkApp = applicationService.validateApplication(applicationId); - if (System.currentTimeMillis() - startTime > twoHoursMs) { - log.warn("After exception, 2-hour window passed for applicationId {}. Marking NDG_FAILED.", applicationId); - checkApp.setNdgStatus(GepafinConstant.NDG_FAILED); - applicationRepository.save(checkApp); - - futureRef.get().cancel(false); - shutdownScheduler(applicationId); - } - } catch (Exception ignore) { - futureRef.get().cancel(false); - shutdownScheduler(applicationId); - } - } - }; - - // Schedule pollingTask: run now (delay=0), then every fifteen minutes: - ScheduledFuture future = scheduler.scheduleWithFixedDelay(pollingTask, 0, // initial delay = 0 min → run immediately - fifteenMin, // subsequent runs every 15 minutes - TimeUnit.MINUTES); - futureRef.set(future); - } - - private void shutdownScheduler(Long applicationId) { - - ScheduledExecutorService shed = executorMap.remove(applicationId); - if (shed != null) { - shed.shutdownNow(); - } - log.info("Scheduler shut down for applicationId: {}", applicationId); - } - - private void processNdgGeneration(Long applicationId) { - // Validate application, company, and hub - log.info("Starting NDG generation process for applicationId: {}", applicationId); - ApplicationEntity application = applicationService.validateApplication(applicationId); - 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)); - } - - try { - // Authenticate and fetch token if required - if (hub.getAppointmentAuthTokenId() == null || hub.getAreaCode() == null) { - authenticateAndSaveToken(hub, application); - } - - String authorizationToken = getBearerToken(hub); - - // Try retrieving NDG by VAT number - AppointmentLoginResponse ndgResponse = retrieveNdgByVatNumber(company.getVatNumber(), authorizationToken, hub, application); - if (isNdgValid(ndgResponse.getNdg())) { - saveNdgAndIdVisura(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); - } - } catch (Exception e) { - log.error("Exception occurred during NDG generation. ApplicationId: {}, CompanyId: {}, HubId: {}, Error: {}", applicationId, company.getId(), hub.getId(), - e.getMessage(), e); - } - } - - private void handleNdgPolling(ApplicationEntity application, CompanyEntity company, HubEntity hub, String authorizationToken) { - - log.info("Starting single‐shot NDG polling attempt for applicationId: {}, CompanyId: {}, HubId: {}", application.getId(), company.getId(), hub.getId()); - - long startTime = System.currentTimeMillis(); - long twoHoursMs = TimeUnit.HOURS.toMillis(2); - - try { - // 1) If NDG was already populated (perhaps by another thread), skip polling. - if (application.getNdg() != null) { - log.info("NDG already present for applicationId {}. Exiting single‐shot polling.", application.getId()); - return; - } - - // 2) Attempt to create Visura (this may immediately return a valid NDG) - AppointmentLoginResponse visuraResponse = createVisura(company, authorizationToken, hub); - - // 2a) If createVisura gave us a valid NDG, persist & exit - String fetchedNdg = visuraResponse.getNdg(); - if (isNdgValid(fetchedNdg)) { - log.info("Valid NDG retrieved from createVisura(): {} | applicationId: {}", fetchedNdg, application.getId()); - - company.setNdg(fetchedNdg); - application.setNdg(fetchedNdg); - application.setNdgStatus(GepafinConstant.NDG_GENERATED); - application.setStatus(ApplicationStatusTypeEnum.NDG.getValue()); - application.setIdVisura(visuraResponse.getIdVisura()); - - companyRepository.save(company); - applicationRepository.save(application); - - ApplicationEvaluationEntity eval = applicationEvaluationService.validateApplicationEvaluation(application.getApplicationEvaluationId()); - Map placeholders = new HashMap<>(); - placeholders.put("{{call_name}}", application.getCall().getName()); - placeholders.put("{{protocol_number}}", String.valueOf(application.getProtocol().getProtocolNumber())); - notificationDao.sendNotificationToInstructor(placeholders, eval, NotificationTypeEnum.NDG_GENERATION); - notificationDao.sendNotificationToSuperUser(application, placeholders, NotificationTypeEnum.NDG_GENERATION); - - log.info("NDG saved successfully for applicationId: {}", application.getId()); - return; - } - - // 2b) If no immediate NDG, fetch the Visura list JSON and parse out NDG - String visuraListJson = getVisuraList(visuraResponse.getIdVisura(), authorizationToken, application, hub); - log.debug("Parsing NDG from VisuraList JSON for applicationId: {}", application.getId()); - String parsedNdg = parseNdgFromVisuraListResponse(visuraListJson); - - if (isNdgValid(parsedNdg)) { - log.info("Valid NDG parsed from VisuraList: {} | applicationId: {}", parsedNdg, application.getId()); - - company.setNdg(parsedNdg); - application.setNdg(parsedNdg); - application.setNdgStatus(GepafinConstant.NDG_GENERATED); - application.setStatus(ApplicationStatusTypeEnum.NDG.getValue()); - application.setIdVisura(visuraResponse.getIdVisura()); - - companyRepository.save(company); - applicationRepository.save(application); - - ApplicationEvaluationEntity eval = applicationEvaluationService.validateApplicationEvaluation(application.getApplicationEvaluationId()); - Map placeholders = new HashMap<>(); - placeholders.put("{{call_name}}", application.getCall().getName()); - placeholders.put("{{protocol_number}}", String.valueOf(application.getProtocol().getProtocolNumber())); - notificationDao.sendNotificationToInstructor(placeholders, eval, NotificationTypeEnum.NDG_GENERATION); - notificationDao.sendNotificationToSuperUser(application, placeholders, NotificationTypeEnum.NDG_GENERATION); - - log.info("NDG saved successfully for applicationId: {}", application.getId()); - return; - } - - // 3) Neither direct API nor parsing yielded a valid NDG. Check timeout. - if (System.currentTimeMillis() - startTime > twoHoursMs) { - log.warn("NDG polling timed out after 2 hours for applicationId: {}", application.getId()); - application.setNdgStatus(GepafinConstant.NDG_FAILED); - applicationRepository.save(application); - return; - } - - // 4) No NDG yet—just return. The scheduler will retry in 15 minutes. - log.info("No valid NDG yet for applicationId {}. Returning to scheduler—next attempt in 15 minutes.", application.getId()); - } catch (Exception e) { - log.error("Exception during NDG polling for applicationId: {}", application.getId(), e); - // If timeout after exception, mark NDG_FAILED - if (System.currentTimeMillis() - startTime > twoHoursMs) { - log.warn("Example: exiting single‐shot polling due to timeout after exception for applicationId: {}", application.getId()); - application.setNdgStatus(GepafinConstant.NDG_FAILED); - applicationRepository.save(application); - } else { - log.info("Exception occurred but not timed out for applicationId {}. Returning to scheduler for next attempt in 15 minutes.", application.getId()); - } - } - - log.info("NDG polling completed for applicationId: {}", application.getId()); - } - - private static String getBearerToken(HubEntity hub) { - - return "Bearer " + hub.getAppointmentAuthTokenId(); - } - - private boolean isNdgValid(String ndg) { - - return ndg != null && !ndg.isEmpty(); - } - - private void saveNdgAndIdVisura(ApplicationEntity application, CompanyEntity company, String ndg) { - - ApplicationEntity oldApplication = Utils.getClonedEntityForData(application); - application.setNdg(ndg); - application.setNdgStatus(GepafinConstant.NDG_GENERATED); - application.setStatus(ApplicationStatusTypeEnum.NDG.getValue()); - company.setNdg(ndg); - companyRepository.save(company); - applicationRepository.save(application); - loggingUtil.addVersionHistory( - VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldApplication).newData(application).build()); - ApplicationEvaluationEntity applicationEvaluationEntity = applicationEvaluationService.validateApplicationEvaluation(application.getApplicationEvaluationId()); -// Map placeHolders = notificationDao.sendNotificationToBeneficiary(application, NotificationTypeEnum.NDG_GENERATION); - Map placeHolders = new HashMap<>(); - placeHolders.put("{{call_name}}", application.getCall().getName()); - placeHolders.put("{{protocol_number}}", String.valueOf(application.getProtocol().getProtocolNumber())); - notificationDao.sendNotificationToInstructor(placeHolders, applicationEvaluationEntity, NotificationTypeEnum.NDG_GENERATION); - notificationDao.sendNotificationToSuperUser(application, placeHolders, NotificationTypeEnum.NDG_GENERATION); - log.info("NDG saved for applicationId: {}, {}", application.getId(), application.getNdg()); - } - - private String getVisuraList(String idVisura, String authorizationToken, ApplicationEntity application, HubEntity hub) { - - log.info("Initiating Visura list retrieval | ApplicationId: {}, HubId: {}, IdVisura: {}", application.getId(), hub.getId(), idVisura); - AppointmentVisuraListRequest visuraListRequest = new AppointmentVisuraListRequest(); - AppointmentVisuraListRequest.VisuraFilter filter = new AppointmentVisuraListRequest.VisuraFilter(); - filter.setIdVisura(idVisura); - visuraListRequest.setFilter(filter); - - try { - String requestJson = Utils.convertObjectToJson(visuraListRequest); - ResponseEntity response = appointmentApiService.getVisuraList(requestJson, authorizationToken); - return Utils.convertObjectToJson(response.getBody()); - } catch (FeignException.Forbidden forbiddenException) { - log.warn("403 Forbidden while fetching Visura list. Attempting token regeneration | ApplicationId: {}, HubId: {}", application.getId(), hub.getId()); - // Regenerate the token and retry - String newAuthorizationToken = regenerateTokenAndSave(hub, application); - return getVisuraList(idVisura, newAuthorizationToken, application, hub); - } catch (Exception e) { - log.error("Error while fetching Visura list | ApplicationId: {}, HubId: {}, Error: {}", application.getId(), hub.getId(), e.getMessage(), e); - throw new RuntimeException("Error fetching Ndg List", e); - } - } - - private AppointmentLoginResponse retrieveNdgByVatNumber(String vatNumber, String authorizationToken, HubEntity hub, ApplicationEntity application) { - - try { - log.info("Initiating NDG retrieval by VAT number | ApplicationId: {}, HubId: {}, VAT: {}", application.getId(), hub.getId(), vatNumber); - // Prepare the NDG request - AppointmentNdgRequest ndgRequest = getAppointmentNdgRequest(vatNumber); - // Call the API to retrieve NDG - ResponseEntity response = appointmentApiService.getNdgByVatNumber(ndgRequest, authorizationToken); - String responseJson = Utils.convertObjectToJson(response.getBody()); - // Parse and return the NDG response - return parseNdgResponse(responseJson); - } catch (FeignException.Forbidden forbiddenException) { - log.error("403 Forbidden during NDG retrieval | ApplicationId: {}, HubId: {}", application.getId(), hub.getId()); - logForbiddenError(); - // Regenerate the token and retry - String newAuthorizationToken = regenerateTokenAndSave(hub, application); - return retrieveNdgByVatNumber(vatNumber, newAuthorizationToken, hub, application); - } catch (Exception e) { - log.error("Error during NDG retrieval | ApplicationId: {}, HubId: {}, Message: {}", application.getId(), hub.getId(), e.getMessage(), e); - throw new RuntimeException("NDG retrieval failed.", e); - } - } - - private String regenerateTokenAndSave(HubEntity hub, ApplicationEntity application) { - - hub = authenticateAndSaveToken(hub, application); - return "Bearer " + hub.getAppointmentAuthTokenId(); - } - - private AppointmentLoginResponse createVisura(CompanyEntity company, String authorizationToken, HubEntity hub) { - - try { - String visuraRequest = getAppointmentVisuraRequest(company, hub.getAreaCode()); - ResponseEntity response = appointmentApiService.createVisura(visuraRequest, authorizationToken); - String responseJson = Utils.convertObjectToJson(response.getBody()); - return parseVisuraResponse(responseJson); - } catch (FeignException.Forbidden forbiddenException) { - logForbiddenError(); - // Regenerate the token and retry - String newAuthorizationToken = regenerateTokenAndSave(hub, null); - return createVisura(company, newAuthorizationToken, hub); - } catch (Exception e) { - log.error("Failed to create Visura for Ndg : {}", e.getMessage()); - throw new RuntimeException("Visura creation failed for Ndg.", e); - } - } - - private static void logForbiddenError() { - - log.error("403 Forbidden received while retrieving NDG. Regenerating token..."); - } - - private static AppointmentNdgRequest getAppointmentNdgRequest(String vatNumber) { - - log.info("Creating Appointment NDG Request | VAT Number: {}", vatNumber); - AppointmentNdgRequest request = new AppointmentNdgRequest(); - AppointmentNdgRequest.Filter filter = new AppointmentNdgRequest.Filter(); - filter.setPartitaIva(vatNumber); - - AppointmentNdgRequest.Pagination pagination = new AppointmentNdgRequest.Pagination(); - pagination.setTargetPage(AppointmentApiConstant.TARGET_PAGE_SIZE); - pagination.setRecordsPerPage(AppointmentApiConstant.RECORD_PER_PAGE_SIZE); - - request.setFilter(filter); - request.setPagination(pagination); - return request; - } - - private static String getAppointmentVisuraRequest(CompanyEntity company, String areaCode) { - - AppointmentVisuraRequest visuraRequest = new AppointmentVisuraRequest(); - AppointmentVisuraRequest.VisuraInput input = new AppointmentVisuraRequest.VisuraInput(); - input.setPartitaIva(company.getVatNumber()); - input.setCodiceFiscale(company.getCodiceFiscale()); - input.setCodArea(areaCode); - input.setVisuraMode(AppointmentApiConstant.VISURA_MODE); - input.setVisuraProvider(AppointmentApiConstant.VISURA_PROVIDER); - input.setCodAgente(AppointmentApiConstant.COD_AGENTE); - input.setAnagraficaLegame(AppointmentApiConstant.IS_ANAGRAFICA_LEGAME); - input.setCreaAnagrafica(AppointmentApiConstant.CREA_ANAGRAFICA); - input.setFromRating(AppointmentApiConstant.IS_FROM_RATING); - input.setSalvaDocumenti(AppointmentApiConstant.SALVA_DOCUMENTI); - input.setVisuraType(AppointmentApiConstant.VISURA_TYPE); - visuraRequest.setInput(input); - return Utils.convertObjectToJson(visuraRequest); - } - - private String parseNdgFromVisuraListResponse(String jsonResponse) { - - try { - ObjectMapper objectMapper = new ObjectMapper(); - JsonNode rootNode = objectMapper.readTree(jsonResponse); - JsonNode dataNode = rootNode.get(GepafinConstant.DATA_STRING); - - if (dataNode != null && dataNode.isArray() && dataNode.size() > 0) { - JsonNode firstEntry = dataNode.get(0); - JsonNode ndgClienteNode = firstEntry.get("ndgCliente"); - if (ndgClienteNode != null && ndgClienteNode.get("code") != null) { - String code = ndgClienteNode.get("code").asText(); - return normalizeNullValue(code); - } - } - log.warn("NDG not found in Visura List API response."); - return null; - } catch (Exception e) { - log.error("Failed to parse NDG from Visura List API response: {}", e.getMessage(), e); - throw new RuntimeException("Error parsing NDG from Visura List API response", e); - } - } - - public AppointmentLoginResponse parseLoginResponse(String jsonResponse) { - - try { - ObjectMapper objectMapper = new ObjectMapper(); - JsonNode rootNode = objectMapper.readTree(jsonResponse); - JsonNode dataNode = rootNode.get(GepafinConstant.DATA_STRING); - - if (dataNode != null) { - AppointmentLoginResponse response = new AppointmentLoginResponse(); - response.setTokenId(dataNode.get("tokenId").asText()); - JsonNode areasNode = dataNode.get("areas"); - if (areasNode != null && areasNode.isArray() && areasNode.size() > 0) { - response.setAreaCode(areasNode.get(0).get("code").asText()); - } - response.setCompanyId(dataNode.get("companyId").asLong()); - return response; - } else { - throw new RuntimeException("Invalid JSON structure: Missing 'data' node."); - } - } catch (Exception e) { - throw new RuntimeException("Failed to parse response from loginApi for odessa: " + e.getMessage(), e); - } - } - - public AppointmentLoginResponse parseVisuraResponse(String jsonResponse) { - - try { - // Log full raw JSON for debug purposes - log.info("Raw Visura JSON Response: {}", jsonResponse); - ObjectMapper objectMapper = new ObjectMapper(); - JsonNode rootNode = objectMapper.readTree(jsonResponse); - JsonNode dataNode = rootNode.get(GepafinConstant.DATA_STRING); - - if (dataNode != null && dataNode.isObject()) { - AppointmentLoginResponse response = new AppointmentLoginResponse(); - JsonNode idVisuraNode = dataNode.get(GepafinConstant.ID_VISURA_STRING); - JsonNode ndgNode = dataNode.get(GepafinConstant.NDG_STRING); - if (idVisuraNode == null || ndgNode == null) { - log.error("Missing expected fields in 'data' node. JSON: {}", dataNode); - } - response.setIdVisura(normalizeNullValue(idVisuraNode != null ? idVisuraNode.asText() : null)); - response.setNdg(normalizeNullValue(ndgNode != null ? ndgNode.asText() : null)); - return response; - } else { - System.err.println("Invalid JSON: 'data' node is missing or not an object."); - throw new RuntimeException("Invalid JSON structure: Missing or malformed 'data' node."); - } - } catch (Exception e) { - System.err.println("Exception while parsing Visura response: " + e.getMessage()); - throw new RuntimeException("Failed to parse response: " + e.getMessage(), e); - } - } - - public AppointmentLoginResponse parseNdgResponse(String jsonResponse) { - - try { - ObjectMapper objectMapper = new ObjectMapper(); - JsonNode rootNode = objectMapper.readTree(jsonResponse); - JsonNode dataArray = rootNode.get(GepafinConstant.DATA_STRING); - if (dataArray == null || !dataArray.isArray() || dataArray.isEmpty()) { - log.info("NDG data is empty or missing in the response."); - AppointmentLoginResponse emptyResponse = new AppointmentLoginResponse(); - emptyResponse.setNdg(null); - return emptyResponse; - } - JsonNode firstDataEntry = dataArray.get(0); - AppointmentLoginResponse response = new AppointmentLoginResponse(); - if (firstDataEntry.has(GepafinConstant.NDG_STRING)) { - response.setNdg(normalizeNullValue(firstDataEntry.get(GepafinConstant.NDG_STRING).asText())); - } - return response; - } catch (Exception e) { - log.error("Failed to parse response: {}", e.getMessage(), e); - throw new RuntimeException("Failed to parse NDG response.", e); - } - } - - private String normalizeNullValue(String value) { - - return (value == null || GepafinConstant.NULL_STRING.equalsIgnoreCase(value.trim())) ? null : value; - } - - public AppointmentCreationResponse createAppointment(Long applicationId, CreateAppointmentRequest createAppointmentRequest) { - // Validate the application - log.info("Starting appointment creation for applicationId: {}", applicationId); - ApplicationEntity application = applicationService.validateApplication(applicationId); - - AppointmentCreationResponse appointmentCreationResponse = new AppointmentCreationResponse(); - - ApplicationEntity oldApplicationData = Utils.getClonedEntityForData(application); - HubEntity hub = hubRepository.findByHubId(application.getHubId()); - - // Check hub UUID and enforce constraints - if (!hub.getUniqueUuid().equals(defaultHubUuid)) { - log.info("Appointment cannot be created for another Hub; default is required for Gepafin."); - throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.NO_APPOINTMENT_FOR_ANOTHER_HUB)); - } - - try { - // Pre-check conditions for appointment creation - if (application.getNdg() != null && !Objects.equals(application.getNdgStatus(), GepafinConstant.NDG_IN_PROGRESS) && application.getAppointmentId() != null) { - appointmentCreationResponse.setAppointmentId(application.getAppointmentId()); - throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.APPOINTMENT_ALREADY_CREATED)); - // return appointmentCreationResponse; - } - - if (application.getNdg() == null && Objects.equals(application.getNdgStatus(), GepafinConstant.NDG_IN_PROGRESS)) { - log.warn("NDG in progress but not available for applicationId: {}", applicationId); - throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.NDG_NOT_FOUND_FOR_APPLICATION)); - } - - // Generate authorization token and fetch template data - String authorizationToken = regenerateTokenAndSave(hub, application); - Long appointmentTemplateId = application.getCall().getAppointmentTemplateId(); - if (appointmentTemplateId == null) { - log.error("Missing appointment template ID for applicationId: {}", applicationId); - throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.APPOINTMENT_CANNOT_BE_CREATED)); - } - ResponseEntity response = appointmentApiService.getAppointmentTemplateForTemplateCreation(authorizationToken, appointmentTemplateId); - - if (response.getStatusCode() != HttpStatus.OK) { - log.error("Failed to retrieve appointment template for appointment creation. Status: {}", response.getStatusCode()); - throw new IllegalStateException("Failed to retrieve appointment template for appointment creation"); - } - - // Parse template data - String responseDataForTemplate = Utils.convertObjectToJson(response.getBody()); - AppointmentCreationRequest templateRichiestaData = parseTemplateResponseData(responseDataForTemplate); - - // Build the appointment request body - AppointmentCreationRequest appointmentCreationRequest = buildAppointmentCreationRequest(applicationId, createAppointmentRequest, appointmentTemplateId, - templateRichiestaData); - log.info("AppointmentCreationRequest : {}", appointmentCreationRequest); - String appointmentRequestBody = Utils.convertObjectToJson(appointmentCreationRequest); - - // Make API call to create the appointment - log.info("Context:{}, Authorization Token : {}, RequestBody : {}", context, authorizationToken, appointmentRequestBody); - ResponseEntity appointmentResponse = appointmentApiService.createAppointment(authorizationToken, context, appointmentRequestBody); - String appointmentId = extractAppointmentIdFromResponse(appointmentResponse); - - if (appointmentId == null) { - log.error("Failed to extract appointment ID from response for applicationId: {}", applicationId); - throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.APPOINTMENT_NOT_CREATED)); - } - // Update application with the appointment ID - application.setAppointmentId(appointmentId); - application.setStatus(ApplicationStatusTypeEnum.APPOINTMENT.getValue()); - applicationRepository.save(application); - - // Log version history - loggingUtil.addVersionHistory( - VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldApplicationData).newData(application).build()); - - appointmentCreationResponse.setAppointmentId(appointmentId); - return appointmentCreationResponse; - - } catch (FeignException.Forbidden forbiddenException) { - log.error("403 Forbidden received while retrieving template. Attempting to regenerate token and retry. Application ID: {}", applicationId); - regenerateTokenAndSave(hub, application); - return createAppointment(applicationId, createAppointmentRequest); - } - } - - private String extractAppointmentIdFromResponse(ResponseEntity appointmentResponse) { - - if (appointmentResponse.getBody() != null) { - log.info("Appointment API Response : {}", appointmentResponse.getBody()); - try { - Map responseBody = (Map) appointmentResponse.getBody(); - // 1. Try to get appointment ID from data.id - if (responseBody.containsKey(GepafinConstant.DATA_STRING)) { - Map data = (Map) responseBody.get(GepafinConstant.DATA_STRING); - if (data != null && data.containsKey(GepafinConstant.ID_STRING)) { - return data.get(GepafinConstant.ID_STRING).toString(); - } - } - // 2. If ID not present, check errors[0].cause.errorDescription - if (responseBody.containsKey(GepafinConstant.ERROR_STRING)) { - List> errors = (List>) responseBody.get(GepafinConstant.ERROR_STRING); - if (errors != null && !errors.isEmpty()) { - Map firstError = errors.get(0); - if (firstError.containsKey(GepafinConstant.CAUSE_STRING)) { - Map cause = (Map) firstError.get(GepafinConstant.CAUSE_STRING); - if (cause != null && cause.containsKey(GepafinConstant.ERROR_DESCRIPTION_STRING)) { - String errorDescription = cause.get(GepafinConstant.ERROR_DESCRIPTION_STRING).toString(); - log.warn("Appointment creation failed: {}", errorDescription); - } - } - } - } - } catch (Exception e) { - log.error("Error while extracting appointment ID or parsing error message", e); - } - } - return null; - } - - public AppointmentCreationRequest parseTemplateResponseData(String jsonResponse) { - - try { - - ObjectMapper objectMapper = new ObjectMapper(); - JsonNode rootNode = objectMapper.readTree(jsonResponse); - JsonNode richiesteClienteArray = rootNode.path(GepafinConstant.DATA_STRING).path(GepafinConstant.RICHIESTE_CLIENTE_STRING); - - // Initialize the result object - AppointmentCreationRequest appointmentCreationRequest = new AppointmentCreationRequest(); - AppointmentCreationRequest.Input input = new AppointmentCreationRequest.Input(); - List richiestaClienteList = new ArrayList<>(); - if (!richiesteClienteArray.isArray()) { - log.warn("richiesteCliente array is missing or not an array."); - return new AppointmentCreationRequest(); - } - for (JsonNode richiestaNode : richiesteClienteArray) { - if (richiestaNode.isNull()) - continue; - - AppointmentCreationRequest.RichiestaCliente richiestaCliente = new AppointmentCreationRequest.RichiestaCliente(); - JsonNode prodottoNode = richiestaNode.path(AppointmentApiConstant.PRODOTTO); - String prodottoCode = prodottoNode.path(AppointmentApiConstant.PRODOTTO_CODE).asText(); - - richiestaCliente.setCodProdotto(prodottoCode); - richiestaCliente.setIdMotivazione(getIntValue(richiestaNode)); - richiestaCliente.setCodAbi(getTextValue(richiestaNode, AppointmentApiConstant.COD_ABI)); - richiestaCliente.setCodCab(getTextValue(richiestaNode, AppointmentApiConstant.COD_CAB)); - richiestaCliente.setIdNota(getTextValue(richiestaNode, AppointmentApiConstant.ID_NOTA)); - richiestaCliente.setImportoAgevolato(getTextValue(richiestaNode, AppointmentApiConstant.IMPORTO_AGEVOLATO)); - richiestaCliente.setImportoMedioLungoTermine(getTextValue(richiestaNode, AppointmentApiConstant.IMPORTO_MEDIOLUNGO_TERMINE)); - richiestaCliente.setCodTipoProdotto(getTextValue(richiestaNode, AppointmentApiConstant.COD_TIPO_PRODOTTO)); - richiestaCliente.setCodCategoriaProdotto(getTextValue(richiestaNode, AppointmentApiConstant.COD_CATEGORIA_PRODOTTO)); - richiestaCliente.setCodFormaTecnica(getTextValue(richiestaNode, AppointmentApiConstant.COD_FORMATECNICA)); - richiestaCliente.setCodOperazione(getTextValue(richiestaNode, AppointmentApiConstant.COD_OPERAZIONE)); - - richiestaClienteList.add(richiestaCliente); - } - - input.setRichiestaCliente(richiestaClienteList); - appointmentCreationRequest.setInput(input); - - return appointmentCreationRequest; - - } catch (JsonProcessingException e) { - log.error("JSON processing error: {}", e.getMessage(), e); - throw new IllegalStateException("Invalid JSON structure in template response", e); - } - } - - private String getTextValue(JsonNode node, String fieldName) { - - return node.path(fieldName).isTextual() ? node.path(fieldName).asText() : null; - } - - private int getIntValue(JsonNode node) { - - return node.path(AppointmentApiConstant.MOTIVAZIONE).path(AppointmentApiConstant.MOTIVAZIONE_ID).asInt(); - } - - public AppointmentCreationRequest buildAppointmentCreationRequest(Long applicationId, CreateAppointmentRequest createAppointmentRequest, Long areaCode, - AppointmentCreationRequest templateRichiestaData) { - - ApplicationEntity application = applicationService.validateApplication(applicationId); - CreateAppointmentRequest.Nota nota = createAppointmentRequest.getNota(); - - AppointmentCreationRequest appointmentCreationRequest = new AppointmentCreationRequest(); - AppointmentCreationRequest.Input input = new AppointmentCreationRequest.Input(); - - // Set Input Fields - input.setId(areaCode); - input.setNdg(application.getNdg()); - - // Populate richiestaCliente from template data - List richiestaClienteList = new ArrayList<>(); - for (AppointmentCreationRequest.RichiestaCliente templateRichiesta : templateRichiestaData.getInput().getRichiestaCliente()) { - AppointmentCreationRequest.RichiestaCliente richiestaCliente = new AppointmentCreationRequest.RichiestaCliente(); - BeanUtils.copyProperties(templateRichiesta, richiestaCliente); - - // Add specific `nota` - AppointmentCreationRequest.Nota requestNota = new AppointmentCreationRequest.Nota(); - requestNota.setTitolo(nota.getTitolo()); - requestNota.setTesto(nota.getTesto()); - richiestaCliente.setNota(requestNota); - richiestaCliente.setDurataMesiFinanziamento(createAppointmentRequest.getDurataMesiFinanziamento()); - richiestaCliente.setImportoBreveTermine(createAppointmentRequest.getImportoBreveTermine()); - richiestaClienteList.add(richiestaCliente); - } - - input.setRichiestaCliente(richiestaClienteList); - appointmentCreationRequest.setInput(input); - return appointmentCreationRequest; - } - - public DocumentUploadResponse uploadDocumentToExternalSystem(Long documentId, UploadDocToExternalSystemRequest docToExternalSystemRequest) { - log.info("Initiating upload to external system for documentId: {}", documentId); - // Check if the document is already being processed - DocumentEntity systemDoc = documentDao.validateDocument(documentId); - - ApplicationEntity application = null; - - if (systemDoc != null) { - DocumentSourceTypeEnum sourceType = DocumentSourceTypeEnum.valueOf(systemDoc.getSource()); - - switch (sourceType) { - case APPLICATION: - application = applicationDao.validateApplication(systemDoc.getSourceId()); - break; - case AMENDMENT: - ApplicationAmendmentRequestEntity applicationAmendmentEntity = applicationAmendmentRequestDao.validateApplicationAmendmentRequest(systemDoc.getSourceId()); - application = applicationDao.validateApplication(applicationAmendmentEntity.getApplicationId()); - break; - case EVALUATION: - ApplicationEvaluationEntity applicationEvaluationEntity = applicationEvaluationDao.validateApplicationEvaluation(systemDoc.getSourceId()); - application = applicationDao.validateApplication(applicationEvaluationEntity.getApplicationId()); - break; - - case CALL: - break; - - default: - log.warn("Unhandled document source type: {}", sourceType); - break; - } - } - - Claims claims = tokenProvider.getClaimsFromToken(tokenProvider.extractTokenFromRequest(request)); - Long hubId = Utils.extractHubIdFromPayload(claims.getSubject()); - - // Authenticate the hub before proceeding - HubEntity hub = hubRepository.findByHubId(hubId); - authenticateAndSaveToken(hub, application); - if (systemDoc != null && systemDoc.getDocumentAttachmentId() != null) { - // If the documentAttachmentId is already set, return the response - log.info("Document already uploaded with documentAttachmentId: {}", systemDoc.getDocumentAttachmentId()); - DocumentUploadResponse response = new DocumentUploadResponse(); - response.setDocumentAttachmentId(systemDoc.getDocumentAttachmentId()); - return response; - } - // Check if a thread is already running for this document upload - if (threadForDocumentMap.containsKey(documentId)) { - log.warn("Document upload already running for documentId: {}", documentId); - throw new CustomValidationException(Status.SUCCESS, Translator.toLocale(GepafinConstant.DOCUMENT_UPLOADING_IN_PROGRESS)); - } - // Start the upload process in the background - ExecutorService executor = Executors.newSingleThreadExecutor(runnable -> { - Thread thread = new Thread(runnable); - thread.setName(GepafinConstant.ASYNC_DOCUMENT_UPLOAD_NAME + documentId); - return thread; - }); - threadForDocumentMap.put(documentId, executor); - - ApplicationEntity finalApplication = application; - executor.submit(() -> { - threadLocalHubId.set(hubId); - try { - log.info("Starting async document upload for documentId: {}", documentId); - uploadDocumentToExternalSystemSync(documentId, docToExternalSystemRequest, finalApplication); - } catch (Exception e) { - log.error("Error in async document upload for documentId: {}", documentId, e); - } finally { - // Cleanup resources - ExecutorService executorToShutdown = threadForDocumentMap.remove(documentId); - if (executorToShutdown != null) { - executorToShutdown.shutdown(); - threadLocalHubId.remove(); - } - log.info("Async document upload completed for documentId: {}", documentId); - } - }); - return null; - } - - private void uploadDocumentToExternalSystemSync(Long documentId, UploadDocToExternalSystemRequest docToExternalSystemRequest, ApplicationEntity application) { - log.info("Starting sync document upload for documentId: {}", documentId); - // Synchronous upload logic - DocumentEntity systemDoc = documentDao.validateDocument(documentId); - - Long hubId = threadLocalHubId.get(); - HubEntity hub = hubRepository.findByHubId(hubId); - - if (!hub.getUniqueUuid().equals(defaultHubUuid)) { - log.info("Document cannot be uploaded for another Hub, it is default for Gepafin."); - throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.NO_DOCUMENT_UPLOAD_FOR_ANOTHER_HUB)); - } - - log.info("Got Document in system: {}", systemDoc); - String oldUrl = systemDoc.getFilePath(); - String authorizationToken = getBearerToken(hub); - - try { - File localFile = downloadFileFromS3(oldUrl); - MultipartFile multipartFile = convertFileToMultipartFile(localFile); - - UploadDocToExternalSystemRequest externalSystemRequest = new UploadDocToExternalSystemRequest(); - externalSystemRequest.setInput(getUploadDocumentInput(docToExternalSystemRequest)); - - String uploadDocRequest = Utils.convertObjectToJson(externalSystemRequest); - ResponseEntity uploadedDocumentData = appointmentApiService.uploadDocumentToExternalSystemForAppointment(authorizationToken, context, uploadDocRequest, - multipartFile); - - String responseData = Utils.convertObjectToJson(uploadedDocumentData.getBody()); - DocumentUploadResponse parsedResponse = parseDocumentUploadResponse(responseData); - - if (parsedResponse == null) { - log.error("Upload failed: parsed response is null for documentId: {}", documentId); - throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.ERROR_UPLOADING_DOCUMENT)); - } - - // Save the documentAttachmentId to the database - systemDoc.setDocumentAttachmentId(parsedResponse.getDocumentAttachmentId()); - documentRepository.save(systemDoc); - - log.info("Document uploaded successfully to external system: {}", parsedResponse); - } catch (FeignException.Forbidden forbiddenException) { - log.error("403 Forbidden from external system during upload for documentId: {}. Retrying with new token...", documentId); - regenerateTokenAndSave(hub, application); - uploadDocumentToExternalSystemSync(documentId, docToExternalSystemRequest, application); - } catch (Exception e) { - log.error("Exception during document upload: {}", e.getMessage(), e); - throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.EXTERNAL_DOCUMENT_UPLOAD_FAILURE_MSG)); - } - } - - private UploadDocToExternalSystemRequest.Input getUploadDocumentInput(UploadDocToExternalSystemRequest docToExternalSystemRequest) { - - UploadDocToExternalSystemRequest.Input input = new UploadDocToExternalSystemRequest.Input(); - input.setIdTipoProtocollo(docToExternalSystemRequest.getInput().getIdTipoProtocollo()); - input.setIdClassificazione(docToExternalSystemRequest.getInput().getIdClassificazione()); - input.setFlagDaFirmare(docToExternalSystemRequest.getInput().getFlagDaFirmare()); - input.setDescrizione(docToExternalSystemRequest.getInput().getDescrizione()); - - UploadDocToExternalSystemRequest.Input.Attributes attributes = new UploadDocToExternalSystemRequest.Input.Attributes(); - attributes.setNdg(docToExternalSystemRequest.getInput().getAttributes().getNdg()); - attributes.setEmail(docToExternalSystemRequest.getInput().getAttributes().getEmail()); - - input.setAttributes(attributes); - return input; - } - - public static 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 { - - String key = amazonS3Service.extractS3KeyFromUrl(fileUrl); - String fileName = extractFileName(key); - String folderPath = key.substring(0, key.lastIndexOf("/")); - File localFile = new File(GepafinConstant.TEMP_FILE_PATH + fileName); - - try (InputStream s3Stream = amazonS3Service.getFile(folderPath, key); FileOutputStream outputStream = new FileOutputStream(localFile)) { - s3Stream.transferTo(outputStream); - } - - log.info("Downloaded file from old S3 bucket: {}", key); - return localFile; - } - - private String extractFileName(String filePath) { - - String[] parts = filePath.split("/"); - return parts[parts.length - 1]; - - } - - public DocumentUploadResponse parseDocumentUploadResponse(String jsonResponse) { - - try { - ObjectMapper objectMapper = new ObjectMapper(); - JsonNode rootNode = objectMapper.readTree(jsonResponse); - - // Navigate to the "data" node - JsonNode dataNode = rootNode.get(GepafinConstant.DATA_STRING); - if (dataNode != null) { - DocumentUploadResponse response = new DocumentUploadResponse(); - - // Extract "documentAttachmentId" - JsonNode documentAttachmentIdNode = dataNode.get(GepafinConstant.DOCUMENT_ATTACHMENT_ID_STRING); - if (documentAttachmentIdNode != null) { - response.setDocumentAttachmentId(documentAttachmentIdNode.asText()); - } else { - throw new RuntimeException("Invalid JSON structure: Missing 'documentAttachmentId' node."); - } - - return response; - } else { - return null; - } - } catch (Exception e) { - throw new RuntimeException("Failed to parse response: " + e.getMessage(), e); - } - } +package net.gepafin.tendermanagement.dao; + +import com.amazonaws.services.s3.AmazonS3Client; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import feign.FeignException; +import io.jsonwebtoken.Claims; +import jakarta.servlet.http.HttpServletRequest; +import lombok.extern.slf4j.Slf4j; +import net.gepafin.tendermanagement.config.Translator; +import net.gepafin.tendermanagement.config.jwt.TokenProvider; +import net.gepafin.tendermanagement.constants.AppointmentApiConstant; +import net.gepafin.tendermanagement.constants.GepafinConstant; +import net.gepafin.tendermanagement.entities.ApplicationAmendmentRequestEntity; +import net.gepafin.tendermanagement.entities.ApplicationEntity; +import net.gepafin.tendermanagement.entities.ApplicationEvaluationEntity; +import net.gepafin.tendermanagement.entities.CompanyEntity; +import net.gepafin.tendermanagement.entities.DocumentEntity; +import net.gepafin.tendermanagement.entities.HubEntity; +import net.gepafin.tendermanagement.enums.*; +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.repositories.UserRepository; +import net.gepafin.tendermanagement.service.AmazonS3Service; +import net.gepafin.tendermanagement.service.ApplicationService; +import net.gepafin.tendermanagement.service.CompanyService; +import net.gepafin.tendermanagement.service.feignClient.AppointmentApiService; +import net.gepafin.tendermanagement.service.impl.ApplicationEvaluationServiceImpl; +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.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; +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.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.ScheduledExecutorService; + +@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; + + @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; + + @Autowired + private TokenProvider tokenProvider; + + @Autowired + private NotificationDao notificationDao; + + @Autowired + private UserRepository userRepository; + + @Autowired + private ApplicationEvaluationServiceImpl applicationEvaluationService; + + @Autowired + private AmazonS3Service amazonS3Service; + + @Autowired + private ApplicationDao applicationDao; + + @Autowired + private ApplicationAmendmentRequestDao applicationAmendmentRequestDao; + + @Autowired + private ApplicationEvaluationDao applicationEvaluationDao; + + private final Map executorMap = new ConcurrentHashMap<>(); + + + private final ConcurrentHashMap threadForDocumentMap = new ConcurrentHashMap<>(); + + private static final ThreadLocal threadLocalHubId = new ThreadLocal<>(); + + public NdgResponse checkNdgForAppointment(Long applicationId) { + log.info("Starting NDG check for appointment. applicationId: {}", applicationId); + ApplicationEntity application = applicationService.validateApplication(applicationId); + ApplicationEntity oldApplication = Utils.getClonedEntityForData(application); + + NdgResponse ndgResponse = new NdgResponse(); + if (application.getNdgStatus() != null && application.getNdgStatus().equalsIgnoreCase(GepafinConstant.NDG_IN_PROGRESS)) { + log.warn("NDG generation already in progress. applicationId: {}", applicationId); + throw new CustomValidationException(Status.SUCCESS, Translator.toLocale(GepafinConstant.NDG_GENERATION_IS_IN_PROGRESS)); + } + + if (application.getNdgStatus() != null && application.getNdgStatus().equalsIgnoreCase(GepafinConstant.NDG_GENERATED) && application.getNdg() != null) { + ndgResponse.setNdg(application.getNdg()); + return ndgResponse; + } + + // Update application status + log.info("Updating NDG status to IN_PROGRESS. applicationId: {}", applicationId); + application.setNdgStatus(GepafinConstant.NDG_IN_PROGRESS); + applicationRepository.save(application); + + loggingUtil.addVersionHistory( + VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldApplication).newData(application).build()); + + + // Start async processing + HubEntity hub = hubRepository.findByHubId(application.getHubId()); + loginToOdessa(hub, application); + startAsyncNdgProcessing(applicationId); + log.info("NDG check initiation completed. applicationId: {}", applicationId); + return ndgResponse; + } + + // private HubEntity loginToOdessa(HubEntity hub, ApplicationEntity application) { + // + // int maxRetries = 3; + // int attempt = 0; + // boolean success = false; + // while (attempt < maxRetries && !success) { + // attempt++; + // try { + // //code to generate token with payload having "iat" epoch timestamp and secret key with no expiry and send in below method call + // String authJwtToken = Utils.generateAuthTokenForLoginToOdessa(); + // log.info("Got the auth for login to odessa {}", authJwtToken); + // hub.setAuthToken(authJwtToken); + // hubRepository.save(hub); + // Map body = Collections.emptyMap(); + // ResponseEntity responseLogin = appointmentApiService.loginWithOdessa(authJwtToken, source, context, user, password, body); + // if (responseLogin.getStatusCode() == HttpStatus.OK) { + // log.info("Login successful to odessa. Parsing response."); + // String loginResponseJson = Utils.convertObjectToJson(responseLogin.getBody()); + // AppointmentLoginResponse parsedResponse = parseLoginResponse(loginResponseJson); + // + // // Validate and save token + // if (parsedResponse.getTokenId() != null) { + // hub.setAppointmentAuthTokenId(parsedResponse.getTokenId()); + // hub.setAreaCode(parsedResponse.getAreaCode()); + // hubRepository.save(hub); + // log.info("Saved new authToken and areaCode for Hub."); + // success = true; + // return hub; + // } else { + // throw new RuntimeException("Login response is missing a valid tokenId for login to odessa system, please try again."); + // } + // } + // throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.ERROR_IN_GENERATING_NDG_TRY_AGAIN)); + // } catch (FeignException.Forbidden forbiddenException) { + // log.error("Failed to login to odessa due to some error"); + // + // // Extract raw response body + // String responseBody = forbiddenException.contentUTF8(); // Extract raw JSON response + // + // // Parse JSON to check for "PasswordExpired" + // try { + // ObjectMapper objectMapper = new ObjectMapper(); + // JsonNode rootNode = objectMapper.readTree(responseBody); + // JsonNode errorsNode = rootNode.path("errors"); + // + // if (errorsNode.isArray()) { + // for (JsonNode error : errorsNode) { + // // Check the main errorCode + // if (GepafinConstant.PASSWORD_EXPIRED.equals(error.path("errorCode").asText())) { + // application.setNdgStatus(GepafinConstant.NDG_FAILED); + // applicationRepository.save(application); + // throw new CustomValidationException(Status.FORBIDDEN, Translator.toLocale(GepafinConstant.PASSWORD_EXPIRED_LOGIN_TO_ODESSA)); + // } + // + // // Check inside "subErrors" + // JsonNode subErrorsNode = error.path("subErrors"); + // if (subErrorsNode.isArray()) { + // for (JsonNode subError : subErrorsNode) { + // if (GepafinConstant.PASSWORD_EXPIRED.equals(subError.path("errorCode").asText())) { + // application.setNdgStatus(GepafinConstant.NDG_FAILED); + // applicationRepository.save(application); + // throw new CustomValidationException(Status.FORBIDDEN, Translator.toLocale(GepafinConstant.PASSWORD_EXPIRED_LOGIN_TO_ODESSA)); + // } + // } + // } + // } + // } + // } catch (IOException e) { + // log.error("Error parsing JSON response: {}", e.getMessage()); + // } + // } catch (Exception e) { + // log.error("Failed to authenticate user on Odessa : {}", e.getMessage(), e); + // throw new RuntimeException("Authentication failed on Odessa. try again", e); + // } + // } + // return null; + // } + // + // + // private HubEntity authenticateAndSaveToken(HubEntity hub) { + // + // int maxRetries = 3; + // int attempt = 0; + // boolean success = false; + // while (attempt < maxRetries && !success) { + // attempt++; + // try { + // //code to generate token with payload having "iat" epoch timestamp and secret key with no expiry and send in below method call + // String authJwtToken = Utils.generateAuthTokenForLoginToOdessa(); + // log.info("Got the auth for login to odessa {}", authJwtToken); + // hub.setAuthToken(authJwtToken); + // hubRepository.save(hub); + // // Prepare the request body (adjust if necessary for login API) + // Map body = Collections.emptyMap(); + // // Perform login API call + // ResponseEntity responseLogin = appointmentApiService.loginWithOdessa(authJwtToken, source, context, user, password, body); + // + // // Handle successful login + // if (responseLogin.getStatusCode() == HttpStatus.OK) { + // log.info("Login successful to odessa. Parsing response."); + // String loginResponseJson = Utils.convertObjectToJson(responseLogin.getBody()); + // AppointmentLoginResponse parsedResponse = parseLoginResponse(loginResponseJson); + // + // // Validate and save token + // if (parsedResponse.getTokenId() != null) { + // hub.setAppointmentAuthTokenId(parsedResponse.getTokenId()); + // hub.setAreaCode(parsedResponse.getAreaCode()); + // hubRepository.save(hub); + // + // log.info("Saved new authToken and areaCode for Hub."); + // success = true; + // return hub; + // } else { + // throw new RuntimeException("Login response is missing a valid tokenId for login to odessa system, please try again."); + // } + // } + // // Handle non-OK response + // throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.ERROR_IN_GENERATING_NDG_TRY_AGAIN)); + // } catch (FeignException.Forbidden forbiddenException) { + // log.error("Failed to login to odessa due to some error occurred."); + // + // // Extract raw response body + // String responseBody = forbiddenException.contentUTF8(); // Extract raw JSON response + // + // // Parse JSON to check for "PasswordExpired" + // try { + // ObjectMapper objectMapper = new ObjectMapper(); + // JsonNode rootNode = objectMapper.readTree(responseBody); + // JsonNode errorsNode = rootNode.path("errors"); + // + // if (errorsNode.isArray()) { + // for (JsonNode error : errorsNode) { + // // Check the main errorCode + // if (GepafinConstant.PASSWORD_EXPIRED.equals(error.path("errorCode").asText())) { + // throw new CustomValidationException(Status.FORBIDDEN, Translator.toLocale(GepafinConstant.PASSWORD_EXPIRED_LOGIN_TO_ODESSA)); + // } + // + // // Check inside "subErrors" + // JsonNode subErrorsNode = error.path("subErrors"); + // if (subErrorsNode.isArray()) { + // for (JsonNode subError : subErrorsNode) { + // if (GepafinConstant.PASSWORD_EXPIRED.equals(subError.path("errorCode").asText())) { + // throw new CustomValidationException(Status.FORBIDDEN, Translator.toLocale(GepafinConstant.PASSWORD_EXPIRED_LOGIN_TO_ODESSA)); + // } + // } + // } + // } + // } + // } catch (IOException e) { + // log.error("Error parsing JSON response: {}", e.getMessage()); + // } + // } catch (Exception e) { + // log.error("Failed to authenticate user on Odessa : {}", e.getMessage(), e); + // throw new RuntimeException("Authentication failed on Odessa. try again", e); + // } + // } + // return null; + // } + + private void loginToOdessa(HubEntity hub, ApplicationEntity application) { + log.info("Starting login to Odessa. HubId: {}, ApplicationId: {}", hub.getId(), application.getId()); + performOdessaLogin(hub, application); + } + + private HubEntity authenticateAndSaveToken(HubEntity hub, ApplicationEntity application) { + + return performOdessaLogin(hub, application); + } + + private HubEntity performOdessaLogin(HubEntity hub, ApplicationEntity application) { + + int maxRetries = 3; + int attempt = 0; + while (attempt < maxRetries) { + attempt++; + try { + String authJwtToken = Utils.generateAuthTokenForLoginToOdessa(); + log.info("Got the auth for login to odessa {}", authJwtToken); + hub.setAuthToken(authJwtToken); + hubRepository.save(hub); + Map body = Collections.emptyMap(); + ResponseEntity responseLogin = appointmentApiService.loginWithOdessa(authJwtToken, source, context, user, password, body); + if (responseLogin.getStatusCode() == HttpStatus.OK) { + log.info("Login to Odessa successful. Parsing response. HubId: {}", hub.getId()); + String loginResponseJson = Utils.convertObjectToJson(responseLogin.getBody()); + AppointmentLoginResponse parsedResponse = parseLoginResponse(loginResponseJson); + + if (parsedResponse.getTokenId() != null) { + hub.setAppointmentAuthTokenId(parsedResponse.getTokenId()); + hub.setAreaCode(parsedResponse.getAreaCode()); + hubRepository.save(hub); + log.info("Saved new authToken and areaCode for Hub."); + return hub; + } else { + log.error("Login response from Odessa missing tokenId. HubId: {}", hub.getId()); + throw new RuntimeException("Login response is missing a valid tokenId for login to odessa system, please try again."); + } + } + throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.ERROR_IN_GENERATING_NDG_TRY_AGAIN)); + } catch (FeignException.Forbidden forbiddenException) { + log.error("Failed to login to odessa due to forbidden error."); + + CheckPasswordExpiredOrErrorInResponse(application, forbiddenException); + } catch (Exception e) { + log.error("Failed to authenticate user on Odessa (Attempt {}): {}", attempt, e.getMessage(), e); + } + } + throw new RuntimeException("Max retries exceeded. Failed to login to Odessa."); + } + private void CheckPasswordExpiredOrErrorInResponse(ApplicationEntity application, FeignException.Forbidden forbiddenException) { + + String responseBody = forbiddenException.contentUTF8(); + + try { + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode rootNode = objectMapper.readTree(responseBody); + JsonNode errorsNode = rootNode.path("errors"); + + if (errorsNode.isArray()) { + for (JsonNode error : errorsNode) { + if (GepafinConstant.PASSWORD_EXPIRED.equals(error.path("errorCode").asText())) { + if (application != null) { + application.setNdgStatus(GepafinConstant.NDG_FAILED); + applicationRepository.save(application); + } + log.warn("Detected PASSWORD_EXPIRED error during Odessa login. ApplicationId: {}", application.getId()); + throw new CustomValidationException(Status.FORBIDDEN, Translator.toLocale(GepafinConstant.PASSWORD_EXPIRED_LOGIN_TO_ODESSA)); + } + + JsonNode subErrorsNode = error.path("subErrors"); + if (subErrorsNode.isArray()) { + for (JsonNode subError : subErrorsNode) { + if (GepafinConstant.PASSWORD_EXPIRED.equals(subError.path("errorCode").asText())) { + if (application != null) { + application.setNdgStatus(GepafinConstant.NDG_FAILED); + applicationRepository.save(application); + } + throw new CustomValidationException(Status.FORBIDDEN, Translator.toLocale(GepafinConstant.PASSWORD_EXPIRED_LOGIN_TO_ODESSA)); + } + } + } + } + } + } catch (IOException e) { + log.error("Unexpected exception during Odessa login.Error: {}",e.getMessage(), e); + } + } + + private void startAsyncNdgProcessing(Long applicationId) { + // If already polling for this applicationId, do nothing: + if (executorMap.containsKey(applicationId)) { + log.warn("Async processing already running for applicationId: {}", applicationId); + return; + } + ServletRequestAttributes requestAttributes = new ServletRequestAttributes(request); + + ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(runnable -> { + Thread t = new Thread(runnable); + t.setName("AsyncNdgProcessing-" + applicationId); + return t; + }); + executorMap.put(applicationId, scheduler); + + // Record the start time so we can stop after 2 hours: + long startTime = System.currentTimeMillis(); + long twoHoursMs = TimeUnit.HOURS.toMillis(2); + long fifteenMin = 15; // in MINUTES + + // We need a reference to cancel the scheduled task from inside itself when we're done: + AtomicReference> futureRef = new AtomicReference<>(); + + Runnable pollingTask = () -> { + RequestContextHolder.setRequestAttributes(requestAttributes, true); + Utils.setHttpServletRequestForThread(request,HttpMethodEnum.POST.getValue(),GepafinConstant.CREATE_NDG, (Long) request.getAttribute(GepafinConstant.USER_ACTION_ID)); + try { + // 1) If 2 hours have already passed, mark as FAILED and shut down: + if (System.currentTimeMillis() - startTime > twoHoursMs) { + ApplicationEntity app = applicationService.validateApplication(applicationId); + log.warn("2-hour timeout reached for applicationId {}. Marking NDG_FAILED.", applicationId); + app.setNdgStatus(GepafinConstant.NDG_FAILED); + applicationRepository.save(app); + + futureRef.get().cancel(false); + shutdownScheduler(applicationId); + return; + } + + // 2) Otherwise, call processNdgGeneration once: + processNdgGeneration(applicationId); + + // 3) After return, check if NDG is now set or timed out. If so, cancel & shut down: + ApplicationEntity updated = applicationService.validateApplication(applicationId); + if (isNdgValid(updated.getNdg())) { + log.info("NDG found for applicationId {}. Shutting down scheduler.", applicationId); + futureRef.get().cancel(false); + shutdownScheduler(applicationId); + } else if (updated.getNdgStatus() != null && updated.getNdgStatus().equals(GepafinConstant.NDG_FAILED)) { + log.info("NDG status is NDG_FAILED for applicationId {}. Shutting down scheduler.", applicationId); + futureRef.get().cancel(false); + shutdownScheduler(applicationId); + } + // Otherwise: no NDG yet, not timed out → next run happens in 15 minutes automatically. + } catch (Exception ex) { + log.error("Unexpected error during scheduled polling for applicationId {}: {}", applicationId, ex.getMessage(), ex); + try { + ApplicationEntity checkApp = applicationService.validateApplication(applicationId); + if (System.currentTimeMillis() - startTime > twoHoursMs) { + log.warn("After exception, 2-hour window passed for applicationId {}. Marking NDG_FAILED.", applicationId); + checkApp.setNdgStatus(GepafinConstant.NDG_FAILED); + applicationRepository.save(checkApp); + + futureRef.get().cancel(false); + shutdownScheduler(applicationId); + } + } catch (Exception ignore) { + futureRef.get().cancel(false); + shutdownScheduler(applicationId); + } + } + }; + + // Schedule pollingTask: run now (delay=0), then every fifteen minutes: + ScheduledFuture future = scheduler.scheduleWithFixedDelay(pollingTask, 0, // initial delay = 0 min → run immediately + fifteenMin, // subsequent runs every 15 minutes + TimeUnit.MINUTES); + futureRef.set(future); + } + + private void shutdownScheduler(Long applicationId) { + + ScheduledExecutorService shed = executorMap.remove(applicationId); + if (shed != null) { + shed.shutdownNow(); + } + log.info("Scheduler shut down for applicationId: {}", applicationId); + } + + private void processNdgGeneration(Long applicationId) { + // Validate application, company, and hub + log.info("Starting NDG generation process for applicationId: {}", applicationId); + ApplicationEntity application = applicationService.validateApplication(applicationId); + 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)); + } + + try { + // Authenticate and fetch token if required + if (hub.getAppointmentAuthTokenId() == null || hub.getAreaCode() == null) { + authenticateAndSaveToken(hub, application); + } + + String authorizationToken = getBearerToken(hub); + + // Try retrieving NDG by VAT number + AppointmentLoginResponse ndgResponse = retrieveNdgByVatNumber(company.getVatNumber(), authorizationToken, hub, application); + if (isNdgValid(ndgResponse.getNdg())) { + saveNdgAndIdVisura(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); + } + } catch (Exception e) { + log.error("Exception occurred during NDG generation. ApplicationId: {}, CompanyId: {}, HubId: {}, Error: {}", applicationId, company.getId(), hub.getId(), + e.getMessage(), e); + } + } + + private void handleNdgPolling(ApplicationEntity application, CompanyEntity company, HubEntity hub, String authorizationToken) { + + log.info("Starting single‐shot NDG polling attempt for applicationId: {}, CompanyId: {}, HubId: {}", application.getId(), company.getId(), hub.getId()); + ApplicationEntity oldApplication = Utils.getClonedEntityForData(application); + CompanyEntity oldCompanyEntity=Utils.getClonedEntityForData(company); + long startTime = System.currentTimeMillis(); + long twoHoursMs = TimeUnit.HOURS.toMillis(2); + + try { + // 1) If NDG was already populated (perhaps by another thread), skip polling. + if (application.getNdg() != null) { + log.info("NDG already present for applicationId {}. Exiting single‐shot polling.", application.getId()); + return; + } + + // 2) Attempt to create Visura (this may immediately return a valid NDG) + AppointmentLoginResponse visuraResponse = createVisura(company, authorizationToken, hub); + + // 2a) If createVisura gave us a valid NDG, persist & exit + String fetchedNdg = visuraResponse.getNdg(); + if (isNdgValid(fetchedNdg)) { + log.info("Valid NDG retrieved from createVisura(): {} | applicationId: {}", fetchedNdg, application.getId()); + + company.setNdg(fetchedNdg); + application.setNdg(fetchedNdg); + application.setNdgStatus(GepafinConstant.NDG_GENERATED); + application.setStatus(ApplicationStatusTypeEnum.NDG.getValue()); + application.setIdVisura(visuraResponse.getIdVisura()); + + companyRepository.save(company); + applicationRepository.save(application); + loggingUtil.addVersionHistory( + VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldApplication).newData(application).build()); + loggingUtil.addVersionHistory( + VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldCompanyEntity).newData(company).build()); + + ApplicationEvaluationEntity eval = applicationEvaluationService.validateApplicationEvaluation(application.getApplicationEvaluationId()); + Map placeholders = new HashMap<>(); + placeholders.put("{{call_name}}", application.getCall().getName()); + placeholders.put("{{protocol_number}}", String.valueOf(application.getProtocol().getProtocolNumber())); + notificationDao.sendNotificationToInstructor(placeholders, eval, NotificationTypeEnum.NDG_GENERATION); + notificationDao.sendNotificationToSuperUser(application, placeholders, NotificationTypeEnum.NDG_GENERATION); + + log.info("NDG saved successfully for applicationId: {}", application.getId()); + return; + } + + // 2b) If no immediate NDG, fetch the Visura list JSON and parse out NDG + String visuraListJson = getVisuraList(visuraResponse.getIdVisura(), authorizationToken, application, hub); + log.debug("Parsing NDG from VisuraList JSON for applicationId: {}", application.getId()); + String parsedNdg = parseNdgFromVisuraListResponse(visuraListJson); + + if (isNdgValid(parsedNdg)) { + log.info("Valid NDG parsed from VisuraList: {} | applicationId: {}", parsedNdg, application.getId()); + + company.setNdg(parsedNdg); + application.setNdg(parsedNdg); + application.setNdgStatus(GepafinConstant.NDG_GENERATED); + application.setStatus(ApplicationStatusTypeEnum.NDG.getValue()); + application.setIdVisura(visuraResponse.getIdVisura()); + + companyRepository.save(company); + applicationRepository.save(application); + loggingUtil.addVersionHistory( + VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldApplication).newData(application).build()); + loggingUtil.addVersionHistory( + VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldCompanyEntity).newData(company).build()); + + ApplicationEvaluationEntity eval = applicationEvaluationService.validateApplicationEvaluation(application.getApplicationEvaluationId()); + Map placeholders = new HashMap<>(); + placeholders.put("{{call_name}}", application.getCall().getName()); + placeholders.put("{{protocol_number}}", String.valueOf(application.getProtocol().getProtocolNumber())); + notificationDao.sendNotificationToInstructor(placeholders, eval, NotificationTypeEnum.NDG_GENERATION); + notificationDao.sendNotificationToSuperUser(application, placeholders, NotificationTypeEnum.NDG_GENERATION); + + log.info("NDG saved successfully for applicationId: {}", application.getId()); + return; + } + + // 3) Neither direct API nor parsing yielded a valid NDG. Check timeout. + if (System.currentTimeMillis() - startTime > twoHoursMs) { + log.warn("NDG polling timed out after 2 hours for applicationId: {}", application.getId()); + application.setNdgStatus(GepafinConstant.NDG_FAILED); + applicationRepository.save(application); + return; + } + + // 4) No NDG yet—just return. The scheduler will retry in 15 minutes. + log.info("No valid NDG yet for applicationId {}. Returning to scheduler—next attempt in 15 minutes.", application.getId()); + } catch (Exception e) { + log.error("Exception during NDG polling for applicationId: {}", application.getId(), e); + // If timeout after exception, mark NDG_FAILED + if (System.currentTimeMillis() - startTime > twoHoursMs) { + log.warn("Example: exiting single‐shot polling due to timeout after exception for applicationId: {}", application.getId()); + application.setNdgStatus(GepafinConstant.NDG_FAILED); + applicationRepository.save(application); + } else { + log.info("Exception occurred but not timed out for applicationId {}. Returning to scheduler for next attempt in 15 minutes.", application.getId()); + } + } + + log.info("NDG polling completed for applicationId: {}", application.getId()); + } + + private static String getBearerToken(HubEntity hub) { + + return "Bearer " + hub.getAppointmentAuthTokenId(); + } + + private boolean isNdgValid(String ndg) { + + return ndg != null && !ndg.isEmpty(); + } + + private void saveNdgAndIdVisura(ApplicationEntity application, CompanyEntity company, String ndg) { + + ApplicationEntity oldApplication = Utils.getClonedEntityForData(application); + CompanyEntity oldCompanyEntity=Utils.getClonedEntityForData(company); + application.setNdg(ndg); + application.setNdgStatus(GepafinConstant.NDG_GENERATED); + application.setStatus(ApplicationStatusTypeEnum.NDG.getValue()); + company.setNdg(ndg); + companyRepository.save(company); + applicationRepository.save(application); + loggingUtil.addVersionHistory( + VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldApplication).newData(application).build()); + loggingUtil.addVersionHistory( + VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldCompanyEntity).newData(company).build()); + + ApplicationEvaluationEntity applicationEvaluationEntity = applicationEvaluationService.validateApplicationEvaluation(application.getApplicationEvaluationId()); +// Map placeHolders = notificationDao.sendNotificationToBeneficiary(application, NotificationTypeEnum.NDG_GENERATION); + Map placeHolders = new HashMap<>(); + placeHolders.put("{{call_name}}", application.getCall().getName()); + placeHolders.put("{{protocol_number}}", String.valueOf(application.getProtocol().getProtocolNumber())); + notificationDao.sendNotificationToInstructor(placeHolders, applicationEvaluationEntity, NotificationTypeEnum.NDG_GENERATION); + notificationDao.sendNotificationToSuperUser(application, placeHolders, NotificationTypeEnum.NDG_GENERATION); + log.info("NDG saved for applicationId: {}, {}", application.getId(), application.getNdg()); + } + + private String getVisuraList(String idVisura, String authorizationToken, ApplicationEntity application, HubEntity hub) { + + log.info("Initiating Visura list retrieval | ApplicationId: {}, HubId: {}, IdVisura: {}", application.getId(), hub.getId(), idVisura); + AppointmentVisuraListRequest visuraListRequest = new AppointmentVisuraListRequest(); + AppointmentVisuraListRequest.VisuraFilter filter = new AppointmentVisuraListRequest.VisuraFilter(); + filter.setIdVisura(idVisura); + visuraListRequest.setFilter(filter); + + try { + String requestJson = Utils.convertObjectToJson(visuraListRequest); + ResponseEntity response = appointmentApiService.getVisuraList(requestJson, authorizationToken); + return Utils.convertObjectToJson(response.getBody()); + } catch (FeignException.Forbidden forbiddenException) { + log.warn("403 Forbidden while fetching Visura list. Attempting token regeneration | ApplicationId: {}, HubId: {}", application.getId(), hub.getId()); + // Regenerate the token and retry + String newAuthorizationToken = regenerateTokenAndSave(hub, application); + return getVisuraList(idVisura, newAuthorizationToken, application, hub); + } catch (Exception e) { + log.error("Error while fetching Visura list | ApplicationId: {}, HubId: {}, Error: {}", application.getId(), hub.getId(), e.getMessage(), e); + throw new RuntimeException("Error fetching Ndg List", e); + } + } + + private AppointmentLoginResponse retrieveNdgByVatNumber(String vatNumber, String authorizationToken, HubEntity hub, ApplicationEntity application) { + + try { + log.info("Initiating NDG retrieval by VAT number | ApplicationId: {}, HubId: {}, VAT: {}", application.getId(), hub.getId(), vatNumber); + // Prepare the NDG request + AppointmentNdgRequest ndgRequest = getAppointmentNdgRequest(vatNumber); + // Call the API to retrieve NDG + ResponseEntity response = appointmentApiService.getNdgByVatNumber(ndgRequest, authorizationToken); + String responseJson = Utils.convertObjectToJson(response.getBody()); + // Parse and return the NDG response + return parseNdgResponse(responseJson); + } catch (FeignException.Forbidden forbiddenException) { + log.error("403 Forbidden during NDG retrieval | ApplicationId: {}, HubId: {}", application.getId(), hub.getId()); + logForbiddenError(); + // Regenerate the token and retry + String newAuthorizationToken = regenerateTokenAndSave(hub, application); + return retrieveNdgByVatNumber(vatNumber, newAuthorizationToken, hub, application); + } catch (Exception e) { + log.error("Error during NDG retrieval | ApplicationId: {}, HubId: {}, Message: {}", application.getId(), hub.getId(), e.getMessage(), e); + throw new RuntimeException("NDG retrieval failed.", e); + } + } + + private String regenerateTokenAndSave(HubEntity hub, ApplicationEntity application) { + + hub = authenticateAndSaveToken(hub, application); + return "Bearer " + hub.getAppointmentAuthTokenId(); + } + + private AppointmentLoginResponse createVisura(CompanyEntity company, String authorizationToken, HubEntity hub) { + + try { + String visuraRequest = getAppointmentVisuraRequest(company, hub.getAreaCode()); + ResponseEntity response = appointmentApiService.createVisura(visuraRequest, authorizationToken); + String responseJson = Utils.convertObjectToJson(response.getBody()); + return parseVisuraResponse(responseJson); + } catch (FeignException.Forbidden forbiddenException) { + logForbiddenError(); + // Regenerate the token and retry + String newAuthorizationToken = regenerateTokenAndSave(hub, null); + return createVisura(company, newAuthorizationToken, hub); + } catch (Exception e) { + log.error("Failed to create Visura for Ndg : {}", e.getMessage()); + throw new RuntimeException("Visura creation failed for Ndg.", e); + } + } + + private static void logForbiddenError() { + + log.error("403 Forbidden received while retrieving NDG. Regenerating token..."); + } + + private static AppointmentNdgRequest getAppointmentNdgRequest(String vatNumber) { + + log.info("Creating Appointment NDG Request | VAT Number: {}", vatNumber); + AppointmentNdgRequest request = new AppointmentNdgRequest(); + AppointmentNdgRequest.Filter filter = new AppointmentNdgRequest.Filter(); + filter.setPartitaIva(vatNumber); + + AppointmentNdgRequest.Pagination pagination = new AppointmentNdgRequest.Pagination(); + pagination.setTargetPage(AppointmentApiConstant.TARGET_PAGE_SIZE); + pagination.setRecordsPerPage(AppointmentApiConstant.RECORD_PER_PAGE_SIZE); + + request.setFilter(filter); + request.setPagination(pagination); + return request; + } + + private static String getAppointmentVisuraRequest(CompanyEntity company, String areaCode) { + + AppointmentVisuraRequest visuraRequest = new AppointmentVisuraRequest(); + AppointmentVisuraRequest.VisuraInput input = new AppointmentVisuraRequest.VisuraInput(); + input.setPartitaIva(company.getVatNumber()); + input.setCodiceFiscale(company.getCodiceFiscale()); + input.setCodArea(areaCode); + input.setVisuraMode(AppointmentApiConstant.VISURA_MODE); + input.setVisuraProvider(AppointmentApiConstant.VISURA_PROVIDER); + input.setCodAgente(AppointmentApiConstant.COD_AGENTE); + input.setAnagraficaLegame(AppointmentApiConstant.IS_ANAGRAFICA_LEGAME); + input.setCreaAnagrafica(AppointmentApiConstant.CREA_ANAGRAFICA); + input.setFromRating(AppointmentApiConstant.IS_FROM_RATING); + input.setSalvaDocumenti(AppointmentApiConstant.SALVA_DOCUMENTI); + input.setVisuraType(AppointmentApiConstant.VISURA_TYPE); + visuraRequest.setInput(input); + return Utils.convertObjectToJson(visuraRequest); + } + + private String parseNdgFromVisuraListResponse(String jsonResponse) { + + try { + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode rootNode = objectMapper.readTree(jsonResponse); + JsonNode dataNode = rootNode.get(GepafinConstant.DATA_STRING); + + if (dataNode != null && dataNode.isArray() && dataNode.size() > 0) { + JsonNode firstEntry = dataNode.get(0); + JsonNode ndgClienteNode = firstEntry.get("ndgCliente"); + if (ndgClienteNode != null && ndgClienteNode.get("code") != null) { + String code = ndgClienteNode.get("code").asText(); + return normalizeNullValue(code); + } + } + log.warn("NDG not found in Visura List API response."); + return null; + } catch (Exception e) { + log.error("Failed to parse NDG from Visura List API response: {}", e.getMessage(), e); + throw new RuntimeException("Error parsing NDG from Visura List API response", e); + } + } + + public AppointmentLoginResponse parseLoginResponse(String jsonResponse) { + + try { + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode rootNode = objectMapper.readTree(jsonResponse); + JsonNode dataNode = rootNode.get(GepafinConstant.DATA_STRING); + + if (dataNode != null) { + AppointmentLoginResponse response = new AppointmentLoginResponse(); + response.setTokenId(dataNode.get("tokenId").asText()); + JsonNode areasNode = dataNode.get("areas"); + if (areasNode != null && areasNode.isArray() && areasNode.size() > 0) { + response.setAreaCode(areasNode.get(0).get("code").asText()); + } + response.setCompanyId(dataNode.get("companyId").asLong()); + return response; + } else { + throw new RuntimeException("Invalid JSON structure: Missing 'data' node."); + } + } catch (Exception e) { + throw new RuntimeException("Failed to parse response from loginApi for odessa: " + e.getMessage(), e); + } + } + + public AppointmentLoginResponse parseVisuraResponse(String jsonResponse) { + + try { + // Log full raw JSON for debug purposes + log.info("Raw Visura JSON Response: {}", jsonResponse); + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode rootNode = objectMapper.readTree(jsonResponse); + JsonNode dataNode = rootNode.get(GepafinConstant.DATA_STRING); + + if (dataNode != null && dataNode.isObject()) { + AppointmentLoginResponse response = new AppointmentLoginResponse(); + JsonNode idVisuraNode = dataNode.get(GepafinConstant.ID_VISURA_STRING); + JsonNode ndgNode = dataNode.get(GepafinConstant.NDG_STRING); + if (idVisuraNode == null || ndgNode == null) { + log.error("Missing expected fields in 'data' node. JSON: {}", dataNode); + } + response.setIdVisura(normalizeNullValue(idVisuraNode != null ? idVisuraNode.asText() : null)); + response.setNdg(normalizeNullValue(ndgNode != null ? ndgNode.asText() : null)); + return response; + } else { + System.err.println("Invalid JSON: 'data' node is missing or not an object."); + throw new RuntimeException("Invalid JSON structure: Missing or malformed 'data' node."); + } + } catch (Exception e) { + System.err.println("Exception while parsing Visura response: " + e.getMessage()); + throw new RuntimeException("Failed to parse response: " + e.getMessage(), e); + } + } + + public AppointmentLoginResponse parseNdgResponse(String jsonResponse) { + + try { + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode rootNode = objectMapper.readTree(jsonResponse); + JsonNode dataArray = rootNode.get(GepafinConstant.DATA_STRING); + if (dataArray == null || !dataArray.isArray() || dataArray.isEmpty()) { + log.info("NDG data is empty or missing in the response."); + AppointmentLoginResponse emptyResponse = new AppointmentLoginResponse(); + emptyResponse.setNdg(null); + return emptyResponse; + } + JsonNode firstDataEntry = dataArray.get(0); + AppointmentLoginResponse response = new AppointmentLoginResponse(); + if (firstDataEntry.has(GepafinConstant.NDG_STRING)) { + response.setNdg(normalizeNullValue(firstDataEntry.get(GepafinConstant.NDG_STRING).asText())); + } + return response; + } catch (Exception e) { + log.error("Failed to parse response: {}", e.getMessage(), e); + throw new RuntimeException("Failed to parse NDG response.", e); + } + } + + private String normalizeNullValue(String value) { + + return (value == null || GepafinConstant.NULL_STRING.equalsIgnoreCase(value.trim())) ? null : value; + } + + public AppointmentCreationResponse createAppointment(Long applicationId, CreateAppointmentRequest createAppointmentRequest) { + // Validate the application + log.info("Starting appointment creation for applicationId: {}", applicationId); + ApplicationEntity application = applicationService.validateApplication(applicationId); + + AppointmentCreationResponse appointmentCreationResponse = new AppointmentCreationResponse(); + + ApplicationEntity oldApplicationData = Utils.getClonedEntityForData(application); + HubEntity hub = hubRepository.findByHubId(application.getHubId()); + + // Check hub UUID and enforce constraints + if (!hub.getUniqueUuid().equals(defaultHubUuid)) { + log.info("Appointment cannot be created for another Hub; default is required for Gepafin."); + throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.NO_APPOINTMENT_FOR_ANOTHER_HUB)); + } + + try { + // Pre-check conditions for appointment creation + if (application.getNdg() != null && !Objects.equals(application.getNdgStatus(), GepafinConstant.NDG_IN_PROGRESS) && application.getAppointmentId() != null) { + appointmentCreationResponse.setAppointmentId(application.getAppointmentId()); + throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.APPOINTMENT_ALREADY_CREATED)); + // return appointmentCreationResponse; + } + + if (application.getNdg() == null && Objects.equals(application.getNdgStatus(), GepafinConstant.NDG_IN_PROGRESS)) { + log.warn("NDG in progress but not available for applicationId: {}", applicationId); + throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.NDG_NOT_FOUND_FOR_APPLICATION)); + } + + // Generate authorization token and fetch template data + String authorizationToken = regenerateTokenAndSave(hub, application); + Long appointmentTemplateId = application.getCall().getAppointmentTemplateId(); + if (appointmentTemplateId == null) { + log.error("Missing appointment template ID for applicationId: {}", applicationId); + throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.APPOINTMENT_CANNOT_BE_CREATED)); + } + ResponseEntity response = appointmentApiService.getAppointmentTemplateForTemplateCreation(authorizationToken, appointmentTemplateId); + + if (response.getStatusCode() != HttpStatus.OK) { + log.error("Failed to retrieve appointment template for appointment creation. Status: {}", response.getStatusCode()); + throw new IllegalStateException("Failed to retrieve appointment template for appointment creation"); + } + + // Parse template data + String responseDataForTemplate = Utils.convertObjectToJson(response.getBody()); + AppointmentCreationRequest templateRichiestaData = parseTemplateResponseData(responseDataForTemplate); + + // Build the appointment request body + AppointmentCreationRequest appointmentCreationRequest = buildAppointmentCreationRequest(applicationId, createAppointmentRequest, appointmentTemplateId, + templateRichiestaData); + log.info("AppointmentCreationRequest : {}", appointmentCreationRequest); + String appointmentRequestBody = Utils.convertObjectToJson(appointmentCreationRequest); + + // Make API call to create the appointment + log.info("Context:{}, Authorization Token : {}, RequestBody : {}", context, authorizationToken, appointmentRequestBody); + ResponseEntity appointmentResponse = appointmentApiService.createAppointment(authorizationToken, context, appointmentRequestBody); + String appointmentId = extractAppointmentIdFromResponse(appointmentResponse); + + if (appointmentId == null) { + log.error("Failed to extract appointment ID from response for applicationId: {}", applicationId); + throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.APPOINTMENT_NOT_CREATED)); + } + // Update application with the appointment ID + application.setAppointmentId(appointmentId); + application.setStatus(ApplicationStatusTypeEnum.APPOINTMENT.getValue()); + applicationRepository.save(application); + + // Log version history + loggingUtil.addVersionHistory( + VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldApplicationData).newData(application).build()); + + appointmentCreationResponse.setAppointmentId(appointmentId); + return appointmentCreationResponse; + + } catch (FeignException.Forbidden forbiddenException) { + log.error("403 Forbidden received while retrieving template. Attempting to regenerate token and retry. Application ID: {}", applicationId); + regenerateTokenAndSave(hub, application); + return createAppointment(applicationId, createAppointmentRequest); + } + } + + private String extractAppointmentIdFromResponse(ResponseEntity appointmentResponse) { + + if (appointmentResponse.getBody() != null) { + log.info("Appointment API Response : {}", appointmentResponse.getBody()); + try { + Map responseBody = (Map) appointmentResponse.getBody(); + // 1. Try to get appointment ID from data.id + if (responseBody.containsKey(GepafinConstant.DATA_STRING)) { + Map data = (Map) responseBody.get(GepafinConstant.DATA_STRING); + if (data != null && data.containsKey(GepafinConstant.ID_STRING)) { + return data.get(GepafinConstant.ID_STRING).toString(); + } + } + // 2. If ID not present, check errors[0].cause.errorDescription + if (responseBody.containsKey(GepafinConstant.ERROR_STRING)) { + List> errors = (List>) responseBody.get(GepafinConstant.ERROR_STRING); + if (errors != null && !errors.isEmpty()) { + Map firstError = errors.get(0); + if (firstError.containsKey(GepafinConstant.CAUSE_STRING)) { + Map cause = (Map) firstError.get(GepafinConstant.CAUSE_STRING); + if (cause != null && cause.containsKey(GepafinConstant.ERROR_DESCRIPTION_STRING)) { + String errorDescription = cause.get(GepafinConstant.ERROR_DESCRIPTION_STRING).toString(); + log.warn("Appointment creation failed: {}", errorDescription); + } + } + } + } + } catch (Exception e) { + log.error("Error while extracting appointment ID or parsing error message", e); + } + } + return null; + } + + public AppointmentCreationRequest parseTemplateResponseData(String jsonResponse) { + + try { + + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode rootNode = objectMapper.readTree(jsonResponse); + JsonNode richiesteClienteArray = rootNode.path(GepafinConstant.DATA_STRING).path(GepafinConstant.RICHIESTE_CLIENTE_STRING); + + // Initialize the result object + AppointmentCreationRequest appointmentCreationRequest = new AppointmentCreationRequest(); + AppointmentCreationRequest.Input input = new AppointmentCreationRequest.Input(); + List richiestaClienteList = new ArrayList<>(); + if (!richiesteClienteArray.isArray()) { + log.warn("richiesteCliente array is missing or not an array."); + return new AppointmentCreationRequest(); + } + for (JsonNode richiestaNode : richiesteClienteArray) { + if (richiestaNode.isNull()) + continue; + + AppointmentCreationRequest.RichiestaCliente richiestaCliente = new AppointmentCreationRequest.RichiestaCliente(); + JsonNode prodottoNode = richiestaNode.path(AppointmentApiConstant.PRODOTTO); + String prodottoCode = prodottoNode.path(AppointmentApiConstant.PRODOTTO_CODE).asText(); + + richiestaCliente.setCodProdotto(prodottoCode); + richiestaCliente.setIdMotivazione(getIntValue(richiestaNode)); + richiestaCliente.setCodAbi(getTextValue(richiestaNode, AppointmentApiConstant.COD_ABI)); + richiestaCliente.setCodCab(getTextValue(richiestaNode, AppointmentApiConstant.COD_CAB)); + richiestaCliente.setIdNota(getTextValue(richiestaNode, AppointmentApiConstant.ID_NOTA)); + richiestaCliente.setImportoAgevolato(getTextValue(richiestaNode, AppointmentApiConstant.IMPORTO_AGEVOLATO)); + richiestaCliente.setImportoMedioLungoTermine(getTextValue(richiestaNode, AppointmentApiConstant.IMPORTO_MEDIOLUNGO_TERMINE)); + richiestaCliente.setCodTipoProdotto(getTextValue(richiestaNode, AppointmentApiConstant.COD_TIPO_PRODOTTO)); + richiestaCliente.setCodCategoriaProdotto(getTextValue(richiestaNode, AppointmentApiConstant.COD_CATEGORIA_PRODOTTO)); + richiestaCliente.setCodFormaTecnica(getTextValue(richiestaNode, AppointmentApiConstant.COD_FORMATECNICA)); + richiestaCliente.setCodOperazione(getTextValue(richiestaNode, AppointmentApiConstant.COD_OPERAZIONE)); + + richiestaClienteList.add(richiestaCliente); + } + + input.setRichiestaCliente(richiestaClienteList); + appointmentCreationRequest.setInput(input); + + return appointmentCreationRequest; + + } catch (JsonProcessingException e) { + log.error("JSON processing error: {}", e.getMessage(), e); + throw new IllegalStateException("Invalid JSON structure in template response", e); + } + } + + private String getTextValue(JsonNode node, String fieldName) { + + return node.path(fieldName).isTextual() ? node.path(fieldName).asText() : null; + } + + private int getIntValue(JsonNode node) { + + return node.path(AppointmentApiConstant.MOTIVAZIONE).path(AppointmentApiConstant.MOTIVAZIONE_ID).asInt(); + } + + public AppointmentCreationRequest buildAppointmentCreationRequest(Long applicationId, CreateAppointmentRequest createAppointmentRequest, Long areaCode, + AppointmentCreationRequest templateRichiestaData) { + + ApplicationEntity application = applicationService.validateApplication(applicationId); + CreateAppointmentRequest.Nota nota = createAppointmentRequest.getNota(); + + AppointmentCreationRequest appointmentCreationRequest = new AppointmentCreationRequest(); + AppointmentCreationRequest.Input input = new AppointmentCreationRequest.Input(); + + // Set Input Fields + input.setId(areaCode); + input.setNdg(application.getNdg()); + + // Populate richiestaCliente from template data + List richiestaClienteList = new ArrayList<>(); + for (AppointmentCreationRequest.RichiestaCliente templateRichiesta : templateRichiestaData.getInput().getRichiestaCliente()) { + AppointmentCreationRequest.RichiestaCliente richiestaCliente = new AppointmentCreationRequest.RichiestaCliente(); + BeanUtils.copyProperties(templateRichiesta, richiestaCliente); + + // Add specific `nota` + AppointmentCreationRequest.Nota requestNota = new AppointmentCreationRequest.Nota(); + requestNota.setTitolo(nota.getTitolo()); + requestNota.setTesto(nota.getTesto()); + richiestaCliente.setNota(requestNota); + richiestaCliente.setDurataMesiFinanziamento(createAppointmentRequest.getDurataMesiFinanziamento()); + richiestaCliente.setImportoBreveTermine(createAppointmentRequest.getImportoBreveTermine()); + richiestaClienteList.add(richiestaCliente); + } + + input.setRichiestaCliente(richiestaClienteList); + appointmentCreationRequest.setInput(input); + return appointmentCreationRequest; + } + + public DocumentUploadResponse uploadDocumentToExternalSystem(Long documentId, UploadDocToExternalSystemRequest docToExternalSystemRequest) { + log.info("Initiating upload to external system for documentId: {}", documentId); + // Check if the document is already being processed + DocumentEntity systemDoc = documentDao.validateDocument(documentId); + + ApplicationEntity application = null; + + if (systemDoc != null) { + DocumentSourceTypeEnum sourceType = DocumentSourceTypeEnum.valueOf(systemDoc.getSource()); + + switch (sourceType) { + case APPLICATION: + application = applicationDao.validateApplication(systemDoc.getSourceId()); + break; + case AMENDMENT: + ApplicationAmendmentRequestEntity applicationAmendmentEntity = applicationAmendmentRequestDao.validateApplicationAmendmentRequest(systemDoc.getSourceId()); + application = applicationDao.validateApplication(applicationAmendmentEntity.getApplicationId()); + break; + case EVALUATION: + ApplicationEvaluationEntity applicationEvaluationEntity = applicationEvaluationDao.validateApplicationEvaluation(systemDoc.getSourceId()); + application = applicationDao.validateApplication(applicationEvaluationEntity.getApplicationId()); + break; + + case CALL: + break; + + default: + log.warn("Unhandled document source type: {}", sourceType); + break; + } + } + + Claims claims = tokenProvider.getClaimsFromToken(tokenProvider.extractTokenFromRequest(request)); + Long hubId = Utils.extractHubIdFromPayload(claims.getSubject()); + + // Authenticate the hub before proceeding + HubEntity hub = hubRepository.findByHubId(hubId); + authenticateAndSaveToken(hub, application); + if (systemDoc != null && systemDoc.getDocumentAttachmentId() != null) { + // If the documentAttachmentId is already set, return the response + log.info("Document already uploaded with documentAttachmentId: {}", systemDoc.getDocumentAttachmentId()); + DocumentUploadResponse response = new DocumentUploadResponse(); + response.setDocumentAttachmentId(systemDoc.getDocumentAttachmentId()); + return response; + } + // Check if a thread is already running for this document upload + if (threadForDocumentMap.containsKey(documentId)) { + log.warn("Document upload already running for documentId: {}", documentId); + throw new CustomValidationException(Status.SUCCESS, Translator.toLocale(GepafinConstant.DOCUMENT_UPLOADING_IN_PROGRESS)); + } + // Start the upload process in the background + ExecutorService executor = Executors.newSingleThreadExecutor(runnable -> { + Thread thread = new Thread(runnable); + thread.setName(GepafinConstant.ASYNC_DOCUMENT_UPLOAD_NAME + documentId); + return thread; + }); + threadForDocumentMap.put(documentId, executor); + + ApplicationEntity finalApplication = application; + executor.submit(() -> { + threadLocalHubId.set(hubId); + try { + log.info("Starting async document upload for documentId: {}", documentId); + uploadDocumentToExternalSystemSync(documentId, docToExternalSystemRequest, finalApplication); + } catch (Exception e) { + log.error("Error in async document upload for documentId: {}", documentId, e); + } finally { + // Cleanup resources + ExecutorService executorToShutdown = threadForDocumentMap.remove(documentId); + if (executorToShutdown != null) { + executorToShutdown.shutdown(); + threadLocalHubId.remove(); + } + log.info("Async document upload completed for documentId: {}", documentId); + } + }); + return null; + } + + private void uploadDocumentToExternalSystemSync(Long documentId, UploadDocToExternalSystemRequest docToExternalSystemRequest, ApplicationEntity application) { + log.info("Starting sync document upload for documentId: {}", documentId); + // Synchronous upload logic + DocumentEntity systemDoc = documentDao.validateDocument(documentId); + + Long hubId = threadLocalHubId.get(); + HubEntity hub = hubRepository.findByHubId(hubId); + + if (!hub.getUniqueUuid().equals(defaultHubUuid)) { + log.info("Document cannot be uploaded for another Hub, it is default for Gepafin."); + throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.NO_DOCUMENT_UPLOAD_FOR_ANOTHER_HUB)); + } + + log.info("Got Document in system: {}", systemDoc); + String oldUrl = systemDoc.getFilePath(); + String authorizationToken = getBearerToken(hub); + + try { + File localFile = downloadFileFromS3(oldUrl); + MultipartFile multipartFile = convertFileToMultipartFile(localFile); + + UploadDocToExternalSystemRequest externalSystemRequest = new UploadDocToExternalSystemRequest(); + externalSystemRequest.setInput(getUploadDocumentInput(docToExternalSystemRequest)); + + String uploadDocRequest = Utils.convertObjectToJson(externalSystemRequest); + ResponseEntity uploadedDocumentData = appointmentApiService.uploadDocumentToExternalSystemForAppointment(authorizationToken, context, uploadDocRequest, + multipartFile); + + String responseData = Utils.convertObjectToJson(uploadedDocumentData.getBody()); + DocumentUploadResponse parsedResponse = parseDocumentUploadResponse(responseData); + + if (parsedResponse == null) { + log.error("Upload failed: parsed response is null for documentId: {}", documentId); + throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.ERROR_UPLOADING_DOCUMENT)); + } + + // Save the documentAttachmentId to the database + systemDoc.setDocumentAttachmentId(parsedResponse.getDocumentAttachmentId()); + documentRepository.save(systemDoc); + + log.info("Document uploaded successfully to external system: {}", parsedResponse); + } catch (FeignException.Forbidden forbiddenException) { + log.error("403 Forbidden from external system during upload for documentId: {}. Retrying with new token...", documentId); + regenerateTokenAndSave(hub, application); + uploadDocumentToExternalSystemSync(documentId, docToExternalSystemRequest, application); + } catch (Exception e) { + log.error("Exception during document upload: {}", e.getMessage(), e); + throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.EXTERNAL_DOCUMENT_UPLOAD_FAILURE_MSG)); + } + } + + private UploadDocToExternalSystemRequest.Input getUploadDocumentInput(UploadDocToExternalSystemRequest docToExternalSystemRequest) { + + UploadDocToExternalSystemRequest.Input input = new UploadDocToExternalSystemRequest.Input(); + input.setIdTipoProtocollo(docToExternalSystemRequest.getInput().getIdTipoProtocollo()); + input.setIdClassificazione(docToExternalSystemRequest.getInput().getIdClassificazione()); + input.setFlagDaFirmare(docToExternalSystemRequest.getInput().getFlagDaFirmare()); + input.setDescrizione(docToExternalSystemRequest.getInput().getDescrizione()); + + UploadDocToExternalSystemRequest.Input.Attributes attributes = new UploadDocToExternalSystemRequest.Input.Attributes(); + attributes.setNdg(docToExternalSystemRequest.getInput().getAttributes().getNdg()); + attributes.setEmail(docToExternalSystemRequest.getInput().getAttributes().getEmail()); + + input.setAttributes(attributes); + return input; + } + + public static 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 { + + String key = amazonS3Service.extractS3KeyFromUrl(fileUrl); + String fileName = extractFileName(key); + String folderPath = key.substring(0, key.lastIndexOf("/")); + File localFile = new File(GepafinConstant.TEMP_FILE_PATH + fileName); + + try (InputStream s3Stream = amazonS3Service.getFile(folderPath, key); FileOutputStream outputStream = new FileOutputStream(localFile)) { + s3Stream.transferTo(outputStream); + } + + log.info("Downloaded file from old S3 bucket: {}", key); + return localFile; + } + + private String extractFileName(String filePath) { + + String[] parts = filePath.split("/"); + return parts[parts.length - 1]; + + } + + public DocumentUploadResponse parseDocumentUploadResponse(String jsonResponse) { + + try { + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode rootNode = objectMapper.readTree(jsonResponse); + + // Navigate to the "data" node + JsonNode dataNode = rootNode.get(GepafinConstant.DATA_STRING); + if (dataNode != null) { + DocumentUploadResponse response = new DocumentUploadResponse(); + + // Extract "documentAttachmentId" + JsonNode documentAttachmentIdNode = dataNode.get(GepafinConstant.DOCUMENT_ATTACHMENT_ID_STRING); + if (documentAttachmentIdNode != null) { + response.setDocumentAttachmentId(documentAttachmentIdNode.asText()); + } else { + throw new RuntimeException("Invalid JSON structure: Missing 'documentAttachmentId' node."); + } + + return response; + } else { + return null; + } + } catch (Exception e) { + throw new RuntimeException("Failed to parse response: " + e.getMessage(), e); + } + } } \ No newline at end of file diff --git a/src/main/java/net/gepafin/tendermanagement/util/Utils.java b/src/main/java/net/gepafin/tendermanagement/util/Utils.java index 13cf488e..19feb0e2 100644 --- a/src/main/java/net/gepafin/tendermanagement/util/Utils.java +++ b/src/main/java/net/gepafin/tendermanagement/util/Utils.java @@ -1053,15 +1053,16 @@ public class Utils { return headers; } - public static void setHttpServletRequestForThread(String redirectURI, String methodType,HttpServletRequest request,String remoteUser) { + public static void setHttpServletRequestForThread(HttpServletRequest request,String methodType, String remoteUser, Long userActionId) { MockHttpServletRequest mockRequest = new MockHttpServletRequest(); - mockRequest.setRequestURI(redirectURI); + mockRequest.setRequestURI(request.getRequestURI()); mockRequest.setMethod(methodType); - mockRequest.setRemoteUser(remoteUser); - Long userActionId = (Long) request.getAttribute(GepafinConstant.USER_ACTION_ID); - mockRequest.setAttribute(GepafinConstant.USER_ACTION_ID,userActionId ); + mockRequest.setRemoteUser(remoteUser); // or pass it in if needed + mockRequest.setAttribute(GepafinConstant.USER_ACTION_ID, userActionId); + ServletRequestAttributes attributes = new ServletRequestAttributes(mockRequest); RequestContextHolder.setRequestAttributes(attributes, true); } + } From d6dccf8cc2b221d65804788f3eaaa93323c41c9c Mon Sep 17 00:00:00 2001 From: rajesh Date: Fri, 6 Jun 2025 15:08:05 +0530 Subject: [PATCH 3/6] Done ticket GEPAFINBE-227 --- .../tendermanagement/dao/ApplicationDao.java | 32 ++++++++++++++--- .../dao/ApplicationEvaluationDao.java | 12 +++++-- .../tendermanagement/dao/AppointmentDao.java | 12 +++++-- .../dao/EmailNotificationDao.java | 36 +++++++++++++++---- .../tendermanagement/dao/NotificationDao.java | 6 +++- .../tendermanagement/dao/ProtocolDao.java | 8 ++--- .../ApplicationEvaluationScheduler.java | 6 +++- 7 files changed, 91 insertions(+), 21 deletions(-) diff --git a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java index c45171e8..eb3aa66f 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java @@ -1,5 +1,6 @@ package net.gepafin.tendermanagement.dao; +import com.amazonaws.services.dynamodbv2.xspec.S; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; @@ -1153,8 +1154,16 @@ public class ApplicationDao { // Create the map for body placeholders Map bodyPlaceholders = new HashMap<>(); bodyPlaceholders.put("{{call_name}}", call.getName()); - bodyPlaceholders.put("{{protocol_number}}", protocol.getProtocolNumber().toString()); - bodyPlaceholders.put("{{date}}", DateTimeUtil.formatLocalDateTime(protocol.getCreatedDate(), GepafinConstant.DD_MM_YYYY)); + String protocolNumber=applicationEntity.getProtocol().getExternalProtocolNumber(); + if(protocolNumber==null){ + protocolNumber= String.valueOf(applicationEntity.getProtocol().getProtocolNumber()); + } + bodyPlaceholders.put("{{protocol_number}}",protocolNumber); + String protocolDate= DateTimeUtil.formatLocalDateTime(protocol.getCreatedDate(), GepafinConstant.DD_MM_YYYY); + if(protocol.getExternalProtocolDate()!=null){ + protocolDate= DateTimeUtil.formatLocalDateTime(protocol.getExternalProtocolDate(), GepafinConstant.DD_MM_YYYY); + } + bodyPlaceholders.put("{{date}}", protocolDate); bodyPlaceholders.put("{{time}}", DateTimeUtil.parseLocalTimeToString(protocol.getTime(), GepafinConstant.HH_MM_SS)); // Replace placeholders in the subject and body @@ -1204,8 +1213,17 @@ public class ApplicationDao { // Create the map for body placeholders Map bodyPlaceholders = new HashMap<>(); bodyPlaceholders.put("{{call_name}}", call.getName()); - bodyPlaceholders.put("{{protocol_number}}", protocol.getProtocolNumber().toString()); - bodyPlaceholders.put("{{date}}", DateTimeUtil.formatLocalDateTime(protocol.getCreatedDate(), GepafinConstant.DD_MM_YYYY)); + String protocolNumber=applicationEntity.getProtocol().getExternalProtocolNumber(); + + if(protocolNumber==null){ + protocolNumber= String.valueOf(applicationEntity.getProtocol().getProtocolNumber()); + } + String protocolDate= DateTimeUtil.formatLocalDateTime(protocol.getCreatedDate(), GepafinConstant.DD_MM_YYYY); + if(protocol.getExternalProtocolDate()!=null){ + protocolDate= DateTimeUtil.formatLocalDateTime(protocol.getExternalProtocolDate(), GepafinConstant.DD_MM_YYYY); + } + bodyPlaceholders.put("{{protocol_number}}", protocolNumber); + bodyPlaceholders.put("{{date}}",protocolDate); bodyPlaceholders.put("{{time}}", DateTimeUtil.parseLocalTimeToString(protocol.getTime(), GepafinConstant.HH_MM_SS)); // Replace placeholders in the subject and body @@ -2323,7 +2341,11 @@ public class ApplicationDao { bodyPlaceholders.put("{{call_name}}", call.getName()); bodyPlaceholders.put("{{application_id}}", applicationEntity.getId().toString()); bodyPlaceholders.put("{{company_name}}", company.getCompanyName()); - bodyPlaceholders.put("{{protocol_number}}", applicationEntity.getProtocol().getProtocolNumber().toString()); + String protocolNumber=applicationEntity.getProtocol().getExternalProtocolNumber(); + if(protocolNumber==null){ + protocolNumber= String.valueOf(applicationEntity.getProtocol().getProtocolNumber()); + } + bodyPlaceholders.put("{{protocol_number}}", protocolNumber); bodyPlaceholders.put("{{user_action_id}}",emailLogRequest.getUserActionId().toString()); String subject = Utils.replacePlaceholders(systemEmailTemplateResponse.getSubject(), subjectPlaceholders); diff --git a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationEvaluationDao.java b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationEvaluationDao.java index 2daae6ae..864855c4 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationEvaluationDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationEvaluationDao.java @@ -710,7 +710,11 @@ public class ApplicationEvaluationDao { Map placeHolders = new HashMap<>(); placeHolders.put("{{call_name}}", application.getCall().getName()); - placeHolders.put("{{protocol_number}}", String.valueOf(application.getProtocol().getProtocolNumber())); + String protocolNumber=application.getProtocol().getExternalProtocolNumber(); + if(protocolNumber==null){ + protocolNumber= String.valueOf(application.getProtocol().getProtocolNumber()); + } + placeHolders.put("{{protocol_number}}", protocolNumber); notificationDao.sendNotificationToSuperUser(application,placeHolders,NotificationTypeEnum.EVALUATION_CREATION); notificationDao.sendNotificationToInstructor(placeHolders,entity,NotificationTypeEnum.EVALUATION_CREATION); @@ -1997,7 +2001,11 @@ public class ApplicationEvaluationDao { Map placeHolders = new HashMap<>(); placeHolders.put("{{call_name}}", application.getCall().getName()); - placeHolders.put("{{protocol_number}}", String.valueOf(application.getProtocol().getProtocolNumber())); + String protocolNumber=application.getProtocol().getExternalProtocolNumber(); + if(protocolNumber==null){ + protocolNumber= String.valueOf(application.getProtocol().getProtocolNumber()); + } + placeHolders.put("{{protocol_number}}", protocolNumber); notificationDao.sendNotificationToSuperUser(application,placeHolders,NotificationTypeEnum.EVALUATION_RESULT); notificationDao.sendNotificationToInstructor(placeHolders,existingEntity,NotificationTypeEnum.EVALUATION_RESULT); diff --git a/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java b/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java index f82d9906..2c4934db 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java @@ -531,7 +531,11 @@ public class AppointmentDao { Map placeHolders = new HashMap<>(); placeHolders.put("{{call_name}}", application.getCall().getName()); - placeHolders.put("{{protocol_number}}", String.valueOf(application.getProtocol().getProtocolNumber())); + String protocolNumber=application.getProtocol().getExternalProtocolNumber(); + if(protocolNumber==null){ + protocolNumber= String.valueOf(application.getProtocol().getProtocolNumber()); + } + placeHolders.put("{{protocol_number}}", protocolNumber); notificationDao.sendNotificationToInstructor(placeHolders, applicationEvaluationEntity, NotificationTypeEnum.NDG_GENERATION); notificationDao.sendNotificationToSuperUser(application, placeHolders, NotificationTypeEnum.NDG_GENERATION); log.info("NDG saved successfully for applicationId: {}", application.getId()); @@ -584,7 +588,11 @@ public class AppointmentDao { // Map placeHolders = notificationDao.sendNotificationToBeneficiary(application, NotificationTypeEnum.NDG_GENERATION); Map placeHolders = new HashMap<>(); placeHolders.put("{{call_name}}", application.getCall().getName()); - placeHolders.put("{{protocol_number}}", String.valueOf(application.getProtocol().getProtocolNumber())); + String protocolNumber=application.getProtocol().getExternalProtocolNumber(); + if(protocolNumber==null){ + protocolNumber= String.valueOf(application.getProtocol().getProtocolNumber()); + } + placeHolders.put("{{protocol_number}}", protocolNumber); notificationDao.sendNotificationToInstructor(placeHolders, applicationEvaluationEntity, NotificationTypeEnum.NDG_GENERATION); notificationDao.sendNotificationToSuperUser(application, placeHolders, NotificationTypeEnum.NDG_GENERATION); log.info("NDG saved for applicationId: {}, {}", application.getId(), application.getNdg()); diff --git a/src/main/java/net/gepafin/tendermanagement/dao/EmailNotificationDao.java b/src/main/java/net/gepafin/tendermanagement/dao/EmailNotificationDao.java index 3772ad71..e263a9f9 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/EmailNotificationDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/EmailNotificationDao.java @@ -174,8 +174,16 @@ public class EmailNotificationDao { public Map prepareEmailPlaceholders(ApplicationEntity applicationEntity, ApplicationAmendmentRequestEntity applicationAmendmentRequestEntity){ Map bodyPlaceholders = new HashMap<>(); bodyPlaceholders.put("{{call_name}}", applicationEntity.getCall().getName()); - bodyPlaceholders.put("{{protocol_number}}", applicationEntity.getProtocol().getProtocolNumber().toString()); - bodyPlaceholders.put("{{protocol_date}}", DateTimeUtil.formatLocalDateTime(applicationEntity.getProtocol().getCreatedDate(), GepafinConstant.DD_MM_YYYY)); + String protocolNumber=applicationEntity.getProtocol().getExternalProtocolNumber(); + if(protocolNumber==null){ + protocolNumber= String.valueOf(applicationEntity.getProtocol().getProtocolNumber()); + } + bodyPlaceholders.put("{{protocol_number}}", protocolNumber); + String protocolDate= DateTimeUtil.formatLocalDateTime(applicationEntity.getProtocol().getCreatedDate(), GepafinConstant.DD_MM_YYYY); + if(applicationEntity.getProtocol().getExternalProtocolDate()!=null){ + protocolDate= DateTimeUtil.formatLocalDateTime(applicationEntity.getProtocol().getExternalProtocolDate(), GepafinConstant.DD_MM_YYYY); + } + bodyPlaceholders.put("{{protocol_date}}", protocolDate); bodyPlaceholders.put("{{protocol_time}}", DateTimeUtil.parseLocalTimeToString(applicationEntity.getProtocol().getTime(), GepafinConstant.HH_MM_SS)); bodyPlaceholders.put("{{response_days}}", applicationAmendmentRequestEntity.getResponseDays().toString()); @@ -259,8 +267,16 @@ public class EmailNotificationDao { public void sendAdmissibilityNotificationEmailForAdmissibleApplication(ApplicationEntity applicationEntity) { Map bodyPlaceholders = new HashMap<>(); bodyPlaceholders.put("{{call_name}}", applicationEntity.getCall().getName()); - bodyPlaceholders.put("{{protocol_number}}", applicationEntity.getProtocol().getProtocolNumber().toString()); - bodyPlaceholders.put("{{protocol_date}}", DateTimeUtil.formatCreatedDate(applicationEntity.getProtocol().getCreatedDate())); + String protocolNumber=applicationEntity.getProtocol().getExternalProtocolNumber(); + if(protocolNumber==null){ + protocolNumber= String.valueOf(applicationEntity.getProtocol().getProtocolNumber()); + } + bodyPlaceholders.put("{{protocol_number}}", protocolNumber); + String protocolDate= DateTimeUtil.formatLocalDateTime(applicationEntity.getProtocol().getCreatedDate(), GepafinConstant.DD_MM_YYYY); + if(applicationEntity.getProtocol().getExternalProtocolDate()!=null){ + protocolDate= DateTimeUtil.formatLocalDateTime(applicationEntity.getProtocol().getExternalProtocolDate(), GepafinConstant.DD_MM_YYYY); + } + bodyPlaceholders.put("{{protocol_date}}", protocolDate); bodyPlaceholders.put("{{protocol_time}}", DateTimeUtil.parseLocalTimeToString(applicationEntity.getProtocol().getTime(), GepafinConstant.HH_MM_SS)); sendEmail(applicationEntity, SystemEmailTemplatesEntity.SystemEmailTemplatesEntityTypeEnum.ADMISSIBILITY_NOTIFICATION, bodyPlaceholders, null,null); @@ -269,8 +285,16 @@ public class EmailNotificationDao { public void sendInadmissibilityEmailForRejectedApplication(ApplicationEntity applicationEntity,ApplicationEvaluationEntity applicationEvaluationEntity) { Map bodyPlaceholders = new HashMap<>(); bodyPlaceholders.put("{{call_name}}", applicationEntity.getCall().getName()); - bodyPlaceholders.put("{{protocol_number}}", applicationEntity.getProtocol().getProtocolNumber().toString()); - bodyPlaceholders.put("{{protocol_date}}", DateTimeUtil.formatCreatedDate(applicationEntity.getProtocol().getCreatedDate())); + String protocolNumber=applicationEntity.getProtocol().getExternalProtocolNumber(); + if(protocolNumber==null){ + protocolNumber= String.valueOf(applicationEntity.getProtocol().getProtocolNumber()); + } + bodyPlaceholders.put("{{protocol_number}}", protocolNumber); + String protocolDate= DateTimeUtil.formatLocalDateTime(applicationEntity.getProtocol().getCreatedDate(), GepafinConstant.DD_MM_YYYY); + if(applicationEntity.getProtocol().getExternalProtocolDate()!=null){ + protocolDate= DateTimeUtil.formatLocalDateTime(applicationEntity.getProtocol().getExternalProtocolDate(), GepafinConstant.DD_MM_YYYY); + } + bodyPlaceholders.put("{{protocol_date}}", protocolDate); bodyPlaceholders.put("{{protocol_time}}", DateTimeUtil.parseLocalTimeToString(applicationEntity.getProtocol().getTime(), GepafinConstant.HH_MM_SS)); bodyPlaceholders.put("{{form_text}}", applicationEvaluationEntity.getMotivation()); diff --git a/src/main/java/net/gepafin/tendermanagement/dao/NotificationDao.java b/src/main/java/net/gepafin/tendermanagement/dao/NotificationDao.java index 17b86d6e..55ec6665 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/NotificationDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/NotificationDao.java @@ -174,7 +174,11 @@ public class NotificationDao { Map placeHolders = new HashMap<>(); placeHolders.put("{{call_name}}", application.getCall().getName()); - placeHolders.put("{{protocol_number}}", String.valueOf(application.getProtocol().getProtocolNumber())); + String protocolNumber=application.getProtocol().getExternalProtocolNumber(); + if(protocolNumber==null){ + protocolNumber= String.valueOf(application.getProtocol().getProtocolNumber()); + } + placeHolders.put("{{protocol_number}}", protocolNumber); NotificationReq notificationReq = createNotificationReq(notificationTypeEnum.getValue(), placeHolders, application.getUserId(), application.getUserWithCompany(), listOf(application.getCompanyId())); sendNotification(notificationReq); diff --git a/src/main/java/net/gepafin/tendermanagement/dao/ProtocolDao.java b/src/main/java/net/gepafin/tendermanagement/dao/ProtocolDao.java index c90ddf30..61895195 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/ProtocolDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/ProtocolDao.java @@ -170,7 +170,7 @@ public class ProtocolDao { vatNumber=company.getVatNumber(); }else { mittenteValue = tipoCorrispondenteWithoutCompany; - vatNumber = company.getCodiceFiscale(); + vatNumber = company.getCodiceFiscale(); } String companyName = company.getCompanyName() + " " + vatNumber; String callName = application.getCall().getName()+" "+ application.getCall().getId(); @@ -181,15 +181,15 @@ public class ProtocolDao { Map requestBody = new HashMap<>(); requestBody.put(GepafinConstant.PROTOCOL_DOC_URL,docUrl); - requestBody.put(GepafinConstant.PROTOCOL_COMPANY_NAME,companyName); requestBody.put(GepafinConstant.PROTOCOL_DOC_HASH,applicationSignedDocumentEntity.getFileHash()); requestBody.put(GepafinConstant.PROTOCOL_TIPO_PROTOCOLLAZIONE,TIPO_PROTOCOLLAZIONE); + requestBody.put(GepafinConstant.PROTOCOL_COMPANY_NAME_VAT_NUMBER,company.getCompanyName()); Map mittente = new HashMap<>(); - mittente.put(GepafinConstant.PROTOCOL_TIPO_CORRISPONDENTE_COMPANY, mittenteValue); - mittente.put(GepafinConstant.PROTOCOL_COMPANY_NAME_VAT_NUMBER,company.getCompanyName()); + mittente.put(GepafinConstant.PROTOCOL_TIPO_CORRISPONDENTE, mittenteValue); mittente.put(GepafinConstant.PROTOCOL_MEZZO,mezzo); mittente.put(GepafinConstant.PROTOCOL_INDIRIZZO_PEC,pecEmail); mittente.put(GepafinConstant.PROTOCOL_COMPANY_VAT_NUMBER,vatNumber); + mittente.put(GepafinConstant.PROTOCOL_COMPANY_NAME,company.getCompanyName()); List> destinatariObject=new ArrayList<>(); Map destinatari = new HashMap<>(); destinatari.put(GepafinConstant.PROTOCOL_CODICE_UO,codiceUo); diff --git a/src/main/java/net/gepafin/tendermanagement/scheduler/ApplicationEvaluationScheduler.java b/src/main/java/net/gepafin/tendermanagement/scheduler/ApplicationEvaluationScheduler.java index af4cb6be..413efe37 100644 --- a/src/main/java/net/gepafin/tendermanagement/scheduler/ApplicationEvaluationScheduler.java +++ b/src/main/java/net/gepafin/tendermanagement/scheduler/ApplicationEvaluationScheduler.java @@ -91,7 +91,11 @@ public class ApplicationEvaluationScheduler { // Map placeHolders = notificationDao.sendNotificationToBeneficiary(application, NotificationTypeEnum.EVALUATION_EXPIRED); Map placeHolders = new HashMap<>(); placeHolders.put("{{call_name}}", application.getCall().getName()); - placeHolders.put("{{protocol_number}}", String.valueOf(application.getProtocol().getProtocolNumber())); + String protocolNumber=application.getProtocol().getExternalProtocolNumber(); + if(protocolNumber==null){ + protocolNumber= String.valueOf(application.getProtocol().getProtocolNumber()); + } + placeHolders.put("{{protocol_number}}", protocolNumber); notificationDao.sendNotificationToSuperUser(application,placeHolders,NotificationTypeEnum.EVALUATION_EXPIRED); notificationDao.sendNotificationToInstructor(placeHolders,evaluation,NotificationTypeEnum.EVALUATION_EXPIRED); notificationDao.sendNotificationToInstructorManager(placeHolders,evaluation,NotificationTypeEnum.EVALUATION_EXPIRED); From 2f19a266525ddbdc311e487bb3595b4793958745 Mon Sep 17 00:00:00 2001 From: rajesh Date: Fri, 6 Jun 2025 15:46:40 +0530 Subject: [PATCH 4/6] Updated property --- src/main/resources/application.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index cac5e36b..2e40b904 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -83,7 +83,7 @@ sviluppumbria.username=protocollatoresvilumbria password=8e85ea4265e49497d697168fd3d34916 CLASSIFICA=06.004.3 TIPO_PROTOCOLLAZIONE=E -tipoCorrispondenteCompany=PersonaGiuridica +tipoCorrispondenteCompany=PersonaGiuridica tipoCorrispondenteWithoutCompany=Persona mezzo=Altro indirizzoPec=ufficio01@domain.com From 93acfe29807c023b5fdf29aead087b01ddd2ddf6 Mon Sep 17 00:00:00 2001 From: rajesh Date: Mon, 16 Jun 2025 15:35:29 +0530 Subject: [PATCH 5/6] Refactor NDG code --- .../constants/AppointmentApiConstant.java | 5 +- .../tendermanagement/dao/AppointmentDao.java | 318 +++++++++++------- .../tendermanagement/enums/NdgStatusEnum.java | 22 ++ 3 files changed, 229 insertions(+), 116 deletions(-) create mode 100644 src/main/java/net/gepafin/tendermanagement/enums/NdgStatusEnum.java diff --git a/src/main/java/net/gepafin/tendermanagement/constants/AppointmentApiConstant.java b/src/main/java/net/gepafin/tendermanagement/constants/AppointmentApiConstant.java index 6e5ed97c..799421fd 100644 --- a/src/main/java/net/gepafin/tendermanagement/constants/AppointmentApiConstant.java +++ b/src/main/java/net/gepafin/tendermanagement/constants/AppointmentApiConstant.java @@ -18,7 +18,7 @@ public class AppointmentApiConstant { 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_TYPE = "FullReport"; public static final String VISURA_MODE = "visure"; public static final String COD_AGENTE = "UtenzaAPIPortal"; public static final boolean IS_FROM_RATING = Boolean.FALSE; @@ -38,4 +38,7 @@ public class AppointmentApiConstant { public static final String COD_OPERAZIONE = "codOperazione"; public static final String MOTIVAZIONE_ID = "id"; public static final String PRODOTTO_CODE = "code"; + + public static final String WS_ANAGRAFICA_URL= "/WSAnagrafica.getList"; + } diff --git a/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java b/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java index f0fd43cb..66bf44d7 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java @@ -12,12 +12,7 @@ import net.gepafin.tendermanagement.config.Translator; import net.gepafin.tendermanagement.config.jwt.TokenProvider; import net.gepafin.tendermanagement.constants.AppointmentApiConstant; import net.gepafin.tendermanagement.constants.GepafinConstant; -import net.gepafin.tendermanagement.entities.ApplicationAmendmentRequestEntity; -import net.gepafin.tendermanagement.entities.ApplicationEntity; -import net.gepafin.tendermanagement.entities.ApplicationEvaluationEntity; -import net.gepafin.tendermanagement.entities.CompanyEntity; -import net.gepafin.tendermanagement.entities.DocumentEntity; -import net.gepafin.tendermanagement.entities.HubEntity; +import net.gepafin.tendermanagement.entities.*; import net.gepafin.tendermanagement.enums.*; import net.gepafin.tendermanagement.model.request.AppointmentCreationRequest; import net.gepafin.tendermanagement.model.request.AppointmentNdgRequest; @@ -30,11 +25,7 @@ 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.repositories.UserRepository; +import net.gepafin.tendermanagement.repositories.*; import net.gepafin.tendermanagement.service.AmazonS3Service; import net.gepafin.tendermanagement.service.ApplicationService; import net.gepafin.tendermanagement.service.CompanyService; @@ -62,13 +53,8 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; +import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicReference; -import java.util.concurrent.ScheduledExecutorService; @Slf4j @Component @@ -154,7 +140,7 @@ public class AppointmentDao { private final Map executorMap = new ConcurrentHashMap<>(); - + private final ConcurrentHashMap threadForDocumentMap = new ConcurrentHashMap<>(); private static final ThreadLocal threadLocalHubId = new ThreadLocal<>(); @@ -177,7 +163,7 @@ public class AppointmentDao { // Update application status log.info("Updating NDG status to IN_PROGRESS. applicationId: {}", applicationId); - application.setNdgStatus(GepafinConstant.NDG_IN_PROGRESS); + application.setNdgStatus(NdgStatusEnum.NDG_INITITATED.getValue()); applicationRepository.save(application); loggingUtil.addVersionHistory( @@ -401,6 +387,7 @@ public class AppointmentDao { } throw new RuntimeException("Max retries exceeded. Failed to login to Odessa."); } + private void CheckPasswordExpiredOrErrorInResponse(ApplicationEntity application, FeignException.Forbidden forbiddenException) { String responseBody = forbiddenException.contentUTF8(); @@ -436,89 +423,89 @@ public class AppointmentDao { } } } catch (IOException e) { - log.error("Unexpected exception during Odessa login.Error: {}",e.getMessage(), e); + log.error("Unexpected exception during Odessa login.Error: {}", e.getMessage(), e); } } - private void startAsyncNdgProcessing(Long applicationId) { - // If already polling for this applicationId, do nothing: - if (executorMap.containsKey(applicationId)) { - log.warn("Async processing already running for applicationId: {}", applicationId); - return; - } - ServletRequestAttributes requestAttributes = new ServletRequestAttributes(request); - - ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(runnable -> { - Thread t = new Thread(runnable); - t.setName("AsyncNdgProcessing-" + applicationId); - return t; - }); - executorMap.put(applicationId, scheduler); - - // Record the start time so we can stop after 2 hours: - long startTime = System.currentTimeMillis(); - long twoHoursMs = TimeUnit.HOURS.toMillis(2); - long fifteenMin = 15; // in MINUTES - - // We need a reference to cancel the scheduled task from inside itself when we're done: - AtomicReference> futureRef = new AtomicReference<>(); - - Runnable pollingTask = () -> { - RequestContextHolder.setRequestAttributes(requestAttributes, true); - Utils.setHttpServletRequestForThread(request,HttpMethodEnum.POST.getValue(),GepafinConstant.CREATE_NDG, (Long) request.getAttribute(GepafinConstant.USER_ACTION_ID)); - try { - // 1) If 2 hours have already passed, mark as FAILED and shut down: - if (System.currentTimeMillis() - startTime > twoHoursMs) { - ApplicationEntity app = applicationService.validateApplication(applicationId); - log.warn("2-hour timeout reached for applicationId {}. Marking NDG_FAILED.", applicationId); - app.setNdgStatus(GepafinConstant.NDG_FAILED); - applicationRepository.save(app); - - futureRef.get().cancel(false); - shutdownScheduler(applicationId); - return; - } - - // 2) Otherwise, call processNdgGeneration once: - processNdgGeneration(applicationId); - - // 3) After return, check if NDG is now set or timed out. If so, cancel & shut down: - ApplicationEntity updated = applicationService.validateApplication(applicationId); - if (isNdgValid(updated.getNdg())) { - log.info("NDG found for applicationId {}. Shutting down scheduler.", applicationId); - futureRef.get().cancel(false); - shutdownScheduler(applicationId); - } else if (updated.getNdgStatus() != null && updated.getNdgStatus().equals(GepafinConstant.NDG_FAILED)) { - log.info("NDG status is NDG_FAILED for applicationId {}. Shutting down scheduler.", applicationId); - futureRef.get().cancel(false); - shutdownScheduler(applicationId); - } - // Otherwise: no NDG yet, not timed out → next run happens in 15 minutes automatically. - } catch (Exception ex) { - log.error("Unexpected error during scheduled polling for applicationId {}: {}", applicationId, ex.getMessage(), ex); - try { - ApplicationEntity checkApp = applicationService.validateApplication(applicationId); - if (System.currentTimeMillis() - startTime > twoHoursMs) { - log.warn("After exception, 2-hour window passed for applicationId {}. Marking NDG_FAILED.", applicationId); - checkApp.setNdgStatus(GepafinConstant.NDG_FAILED); - applicationRepository.save(checkApp); - - futureRef.get().cancel(false); - shutdownScheduler(applicationId); - } - } catch (Exception ignore) { - futureRef.get().cancel(false); - shutdownScheduler(applicationId); - } - } - }; - - // Schedule pollingTask: run now (delay=0), then every fifteen minutes: - ScheduledFuture future = scheduler.scheduleWithFixedDelay(pollingTask, 0, // initial delay = 0 min → run immediately - fifteenMin, // subsequent runs every 15 minutes - TimeUnit.MINUTES); - futureRef.set(future); - } +// private void startAsyncNdgProcessing(Long applicationId) { +// // If already polling for this applicationId, do nothing: +// if (executorMap.containsKey(applicationId)) { +// log.warn("Async processing already running for applicationId: {}", applicationId); +// return; +// } +// ServletRequestAttributes requestAttributes = new ServletRequestAttributes(request); +// +// ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(runnable -> { +// Thread t = new Thread(runnable); +// t.setName("AsyncNdgProcessing-" + applicationId); +// return t; +// }); +// executorMap.put(applicationId, scheduler); +// +// // Record the start time so we can stop after 2 hours: +// long startTime = System.currentTimeMillis(); +// long twoHoursMs = TimeUnit.HOURS.toMillis(2); +// long fifteenMin = 15; // in MINUTES +// +// // We need a reference to cancel the scheduled task from inside itself when we're done: +// AtomicReference> futureRef = new AtomicReference<>(); +// +// Runnable pollingTask = () -> { +// RequestContextHolder.setRequestAttributes(requestAttributes, true); +// Utils.setHttpServletRequestForThread(request,HttpMethodEnum.POST.getValue(),GepafinConstant.CREATE_NDG, (Long) request.getAttribute(GepafinConstant.USER_ACTION_ID)); +// try { +// // 1) If 2 hours have already passed, mark as FAILED and shut down: +// if (System.currentTimeMillis() - startTime > twoHoursMs) { +// ApplicationEntity app = applicationService.validateApplication(applicationId); +// log.warn("2-hour timeout reached for applicationId {}. Marking NDG_FAILED.", applicationId); +// app.setNdgStatus(GepafinConstant.NDG_FAILED); +// applicationRepository.save(app); +// +// futureRef.get().cancel(false); +// shutdownScheduler(applicationId); +// return; +// } +// +// // 2) Otherwise, call processNdgGeneration once: +// processNdgGeneration(applicationId); +// +// // 3) After return, check if NDG is now set or timed out. If so, cancel & shut down: +// ApplicationEntity updated = applicationService.validateApplication(applicationId); +// if (isNdgValid(updated.getNdg())) { +// log.info("NDG found for applicationId {}. Shutting down scheduler.", applicationId); +// futureRef.get().cancel(false); +// shutdownScheduler(applicationId); +// } else if (updated.getNdgStatus() != null && updated.getNdgStatus().equals(GepafinConstant.NDG_FAILED)) { +// log.info("NDG status is NDG_FAILED for applicationId {}. Shutting down scheduler.", applicationId); +// futureRef.get().cancel(false); +// shutdownScheduler(applicationId); +// } +// // Otherwise: no NDG yet, not timed out → next run happens in 15 minutes automatically. +// } catch (Exception ex) { +// log.error("Unexpected error during scheduled polling for applicationId {}: {}", applicationId, ex.getMessage(), ex); +// try { +// ApplicationEntity checkApp = applicationService.validateApplication(applicationId); +// if (System.currentTimeMillis() - startTime > twoHoursMs) { +// log.warn("After exception, 2-hour window passed for applicationId {}. Marking NDG_FAILED.", applicationId); +// checkApp.setNdgStatus(GepafinConstant.NDG_FAILED); +// applicationRepository.save(checkApp); +// +// futureRef.get().cancel(false); +// shutdownScheduler(applicationId); +// } +// } catch (Exception ignore) { +// futureRef.get().cancel(false); +// shutdownScheduler(applicationId); +// } +// } +// }; +// +// // Schedule pollingTask: run now (delay=0), then every fifteen minutes: +// ScheduledFuture future = scheduler.scheduleWithFixedDelay(pollingTask, 0, // initial delay = 0 min → run immediately +// fifteenMin, // subsequent runs every 15 minutes +// TimeUnit.MINUTES); +// futureRef.set(future); +// } private void shutdownScheduler(Long applicationId) { @@ -529,12 +516,10 @@ public class AppointmentDao { log.info("Scheduler shut down for applicationId: {}", applicationId); } - private void processNdgGeneration(Long applicationId) { + 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); - ApplicationEntity application = applicationService.validateApplication(applicationId); - 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."); @@ -552,7 +537,7 @@ public class AppointmentDao { // Try retrieving NDG by VAT number AppointmentLoginResponse ndgResponse = retrieveNdgByVatNumber(company.getVatNumber(), authorizationToken, hub, application); if (isNdgValid(ndgResponse.getNdg())) { - saveNdgAndIdVisura(application, company, ndgResponse.getNdg()); + saveNdg(application, company, ndgResponse.getNdg()); log.info("NDG successfully generated for applicationId: {}", applicationId); } else { log.info("Polling for NDG for applicationId: {}", applicationId); @@ -568,7 +553,7 @@ public class AppointmentDao { log.info("Starting single‐shot NDG polling attempt for applicationId: {}, CompanyId: {}, HubId: {}", application.getId(), company.getId(), hub.getId()); ApplicationEntity oldApplication = Utils.getClonedEntityForData(application); - CompanyEntity oldCompanyEntity=Utils.getClonedEntityForData(company); + CompanyEntity oldCompanyEntity = Utils.getClonedEntityForData(company); long startTime = System.currentTimeMillis(); long twoHoursMs = TimeUnit.HOURS.toMillis(2); @@ -589,7 +574,7 @@ public class AppointmentDao { company.setNdg(fetchedNdg); application.setNdg(fetchedNdg); - application.setNdgStatus(GepafinConstant.NDG_GENERATED); + application.setNdgStatus(NdgStatusEnum.NDG_GENERATED.getValue()); application.setStatus(ApplicationStatusTypeEnum.NDG.getValue()); application.setIdVisura(visuraResponse.getIdVisura()); @@ -678,10 +663,10 @@ public class AppointmentDao { return ndg != null && !ndg.isEmpty(); } - private void saveNdgAndIdVisura(ApplicationEntity application, CompanyEntity company, String ndg) { + private void saveNdg(ApplicationEntity application, CompanyEntity company, String ndg) { ApplicationEntity oldApplication = Utils.getClonedEntityForData(application); - CompanyEntity oldCompanyEntity=Utils.getClonedEntityForData(company); + CompanyEntity oldCompanyEntity = Utils.getClonedEntityForData(company); application.setNdg(ndg); application.setNdgStatus(GepafinConstant.NDG_GENERATED); application.setStatus(ApplicationStatusTypeEnum.NDG.getValue()); @@ -738,13 +723,13 @@ public class AppointmentDao { // Parse and return the NDG response return parseNdgResponse(responseJson); } catch (FeignException.Forbidden forbiddenException) { - log.error("403 Forbidden during NDG retrieval | ApplicationId: {}, HubId: {}", application.getId(), hub.getId()); + log.error("403 Forbidden during NDG retrieval | ApplicationId: {}, HubId: {}", application.getId(), hub.getId()); logForbiddenError(); // Regenerate the token and retry String newAuthorizationToken = regenerateTokenAndSave(hub, application); return retrieveNdgByVatNumber(vatNumber, newAuthorizationToken, hub, application); } catch (Exception e) { - log.error("Error during NDG retrieval | ApplicationId: {}, HubId: {}, Message: {}", application.getId(), hub.getId(), e.getMessage(), e); + log.error("Error during NDG retrieval | ApplicationId: {}, HubId: {}, Message: {}", application.getId(), hub.getId(), e.getMessage(), e); throw new RuntimeException("NDG retrieval failed.", e); } } @@ -993,7 +978,7 @@ public class AppointmentDao { return appointmentCreationResponse; } catch (FeignException.Forbidden forbiddenException) { - log.error("403 Forbidden received while retrieving template. Attempting to regenerate token and retry. Application ID: {}", applicationId); + log.error("403 Forbidden received while retrieving template. Attempting to regenerate token and retry. Application ID: {}", applicationId); regenerateTokenAndSave(hub, application); return createAppointment(applicationId, createAppointmentRequest); } @@ -1094,7 +1079,7 @@ public class AppointmentDao { } public AppointmentCreationRequest buildAppointmentCreationRequest(Long applicationId, CreateAppointmentRequest createAppointmentRequest, Long areaCode, - AppointmentCreationRequest templateRichiestaData) { + AppointmentCreationRequest templateRichiestaData) { ApplicationEntity application = applicationService.validateApplication(applicationId); CreateAppointmentRequest.Nota nota = createAppointmentRequest.getNota(); @@ -1207,7 +1192,7 @@ public class AppointmentDao { } private void uploadDocumentToExternalSystemSync(Long documentId, UploadDocToExternalSystemRequest docToExternalSystemRequest, ApplicationEntity application) { - log.info("Starting sync document upload for documentId: {}", documentId); + log.info("Starting sync document upload for documentId: {}", documentId); // Synchronous upload logic DocumentEntity systemDoc = documentDao.validateDocument(documentId); @@ -1248,7 +1233,7 @@ public class AppointmentDao { log.info("Document uploaded successfully to external system: {}", parsedResponse); } catch (FeignException.Forbidden forbiddenException) { - log.error("403 Forbidden from external system during upload for documentId: {}. Retrying with new token...", documentId); + log.error("403 Forbidden from external system during upload for documentId: {}. Retrying with new token...", documentId); regenerateTokenAndSave(hub, application); uploadDocumentToExternalSystemSync(documentId, docToExternalSystemRequest, application); } catch (Exception e) { @@ -1328,4 +1313,107 @@ public class AppointmentDao { throw new RuntimeException("Failed to parse response: " + e.getMessage(), e); } } -} \ No newline at end of file + + private void startAsyncNdgProcessing(Long applicationId) { + if (executorMap.containsKey(applicationId)) { + log.warn("Async processing already running for applicationId: {}", applicationId); + return; + } + ApplicationEntity application = applicationService.validateApplication(applicationId); + CompanyEntity company = companyService.validateCompany(application.getCompanyId()); + ApplicationEntity oldApplication = Utils.getClonedEntityForData(application); + CompanyEntity oldCompanyEntity = Utils.getClonedEntityForData(company); + ServletRequestAttributes requestAttributes = new ServletRequestAttributes(request); + ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(r -> { + Thread t = new Thread(r); + t.setName("AsyncNdgPolling-" + applicationId); + return t; + }); + + executorMap.put(applicationId, scheduler); + application.setNdgStatus(NdgStatusEnum.NDG_IN_PROGRESS.getValue()); + applicationRepository.save(application); + loggingUtil.addVersionHistory( + VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldApplication).newData(application).build()); + + long startTime = System.currentTimeMillis(); + long twoHoursMs = TimeUnit.HOURS.toMillis(2); + long fifteenMin = 15; + + HubEntity hub = hubRepository.findByHubId(application.getHubId()); + + // 1. Run full NDG generation logic once upfront + processNdgGeneration(application, company, hub); + + AtomicReference> futureRef = new AtomicReference<>(); + + // 2. Now define the polling logic ONLY + Runnable pollingTask = () -> { + RequestContextHolder.setRequestAttributes(requestAttributes, true); + Utils.setHttpServletRequestForThread(request, HttpMethodEnum.POST.getValue(), + GepafinConstant.CREATE_NDG, (Long) request.getAttribute(GepafinConstant.USER_ACTION_ID)); + try { + + // Stop if timeout + if (System.currentTimeMillis() - startTime > twoHoursMs) { + log.warn("Polling timed out for applicationId {}. Marking NDG_FAILED.", applicationId); + application.setNdgStatus(GepafinConstant.NDG_FAILED); + applicationRepository.save(application); + loggingUtil.addVersionHistory( + VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldApplication).newData(application).build()); + + futureRef.get().cancel(false); + shutdownScheduler(applicationId); + return; + } + + // If NDG already present or marked failed + if (isNdgValid(application.getNdg()) || GepafinConstant.NDG_FAILED.equals(application.getNdgStatus())) { + log.info("NDG already present or failed for applicationId {}. Stopping polling.", applicationId); + futureRef.get().cancel(false); + shutdownScheduler(applicationId); + return; + } + + // Only Visura polling here: + String visuraListJson = getVisuraList(application.getIdVisura(), hub.getAppointmentAuthTokenId(), application, hub); + String parsedNdg = parseNdgFromVisuraListResponse(visuraListJson); + + if (isNdgValid(parsedNdg)) { + log.info("Valid NDG parsed from VisuraList: {} | applicationId: {}", parsedNdg, application.getId()); + + company.setNdg(parsedNdg); + application.setNdg(parsedNdg); + application.setNdgStatus(GepafinConstant.NDG_GENERATED); + application.setStatus(ApplicationStatusTypeEnum.NDG.getValue()); +// application.setIdVisura(visuraResponse.getIdVisura()); + + companyRepository.save(company); + applicationRepository.save(application); + loggingUtil.addVersionHistory( + VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldApplication).newData(application).build()); + loggingUtil.addVersionHistory( + VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldCompanyEntity).newData(company).build()); + + ApplicationEvaluationEntity eval = applicationEvaluationService.validateApplicationEvaluation(application.getApplicationEvaluationId()); + Map placeholders = new HashMap<>(); + placeholders.put("{{call_name}}", application.getCall().getName()); + placeholders.put("{{protocol_number}}", String.valueOf(application.getProtocol().getProtocolNumber())); + notificationDao.sendNotificationToInstructor(placeholders, eval, NotificationTypeEnum.NDG_GENERATION); + notificationDao.sendNotificationToSuperUser(application, placeholders, NotificationTypeEnum.NDG_GENERATION); + + log.info("NDG saved successfully for applicationId: {}", application.getId()); + return; + } + + } catch (Exception ex) { + log.error("Error during NDG polling: {}", ex.getMessage(), ex); + } + }; + + ScheduledFuture future = scheduler.scheduleWithFixedDelay(pollingTask, fifteenMin, fifteenMin, TimeUnit.MINUTES); + futureRef.set(future); + } + +} + diff --git a/src/main/java/net/gepafin/tendermanagement/enums/NdgStatusEnum.java b/src/main/java/net/gepafin/tendermanagement/enums/NdgStatusEnum.java new file mode 100644 index 00000000..2d16a7bd --- /dev/null +++ b/src/main/java/net/gepafin/tendermanagement/enums/NdgStatusEnum.java @@ -0,0 +1,22 @@ +package net.gepafin.tendermanagement.enums; + + +import com.fasterxml.jackson.annotation.JsonValue; + +public enum NdgStatusEnum { + + NDG_INITITATED("NDG_INITITATED"), + NDG_GENERATED("NDG_GENERATED"), + NDG_IN_PROGRESS("NDG_IN_PROGRESS"); + + private String value; + + NdgStatusEnum(String value) { + this.value = value; + } + + @JsonValue + public String getValue() { + return value; + } +} From 6d66037adbb4a8728987b1733868ca98bb7d377f Mon Sep 17 00:00:00 2001 From: rajesh Date: Wed, 18 Jun 2025 20:23:12 +0530 Subject: [PATCH 6/6] Done ticket GEPAFINBE-223 & GEPAFINBE-224 --- .../constants/GepafinConstant.java | 2 + .../tendermanagement/dao/ApplicationDao.java | 82 +++++++++++++----- .../tendermanagement/dao/AppointmentDao.java | 85 ++++++++++++++++--- .../tendermanagement/dao/CompanyDao.java | 67 +++++++++++---- .../entities/ApplicationEntity.java | 3 + .../entities/NdganagEntity.java | 33 +++++++ .../enums/UserActionContextEnum.java | 2 + .../repositories/CompanyRepository.java | 2 + .../repositories/NdganagRepository.java | 14 +++ .../service/AppointmentService.java | 3 + .../service/CompanyService.java | 1 + .../service/impl/AppointmentServiceImpl.java | 12 +++ .../service/impl/CompanyServiceImpl.java | 8 +- .../web/rest/api/AppointmentApi.java | 11 +++ .../web/rest/api/CompanyApi.java | 13 +++ .../rest/api/impl/AppointmentController.java | 16 ++++ .../rest/api/impl/CompanyApiController.java | 13 +++ .../db/changelog/db.changelog-1.0.0.xml | 20 +++++ src/main/resources/message_en.properties | 2 + src/main/resources/message_it.properties | 2 + 20 files changed, 344 insertions(+), 47 deletions(-) create mode 100644 src/main/java/net/gepafin/tendermanagement/entities/NdganagEntity.java create mode 100644 src/main/java/net/gepafin/tendermanagement/repositories/NdganagRepository.java diff --git a/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java b/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java index 90988320..3874fc83 100644 --- a/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java +++ b/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java @@ -572,6 +572,8 @@ public class GepafinConstant { public static final String PROTOCOL_EXTERNAL_DATE="DATA_PG_DT"; public static final String PROTOCOL_DOC_SUFFIX="#noauth"; public static final String CREATE_NDG="CHECK_OR_CREATE_NDG_CODE"; + public static final String NDG_NOT_FOUND="ndg.not.found"; + public static final String EMAIL_PEC_REQUIRED="email.pec.cannot.null"; } diff --git a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java index 38067a66..02fb01d2 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java @@ -557,25 +557,69 @@ public class ApplicationDao { VersionActionTypeEnum actionType = VersionActionTypeEnum.INSERT; List contentResponseBeans = formDao.convertFormEntityToFormResponseBean(formEntity).getContent(); - contentResponseBeans.stream() - .filter(content -> "numberinput".equals(content.getName()) && content.getId().toString().equals(applicationFormFieldRequestBean.getFieldId())) - .map(ContentResponseBean::getSettings) - .flatMap(List::stream) - .filter(setting -> "isRequestedAmount".equals(setting.getName()) && Boolean.TRUE.equals(setting.getValue())) - .findFirst() - .ifPresent(setting -> { - Object fieldValue = applicationFormFieldRequestBean.getFieldValue(); - if(fieldValue!=null) { - try { - BigDecimal amountRequested = new BigDecimal(fieldValue.toString()); - applicationFormEntity.getApplication().setAmountRequested(amountRequested); - log.info("Set amountRequested to {} for Application ID: {}", amountRequested, applicationFormEntity.getApplication().getId()); - } catch (NumberFormatException e) { - log.error("Invalid number format for requested amount: {}", fieldValue, e); - throw new IllegalArgumentException("Field value is not a valid number: " + fieldValue, e); - } - } - }); +// contentResponseBeans.stream() +// .filter(content -> "numberinput".equals(content.getName()) && content.getId().toString().equals(applicationFormFieldRequestBean.getFieldId())) +// .map(ContentResponseBean::getSettings) +// .flatMap(List::stream) +// .filter(setting -> "isRequestedAmount".equals(setting.getName()) && Boolean.TRUE.equals(setting.getValue())) +// .findFirst() +// .ifPresent(setting -> { +// Object fieldValue = applicationFormFieldRequestBean.getFieldValue(); +// if(fieldValue!=null) { +// try { +// BigDecimal amountRequested = new BigDecimal(fieldValue.toString()); +// applicationFormEntity.getApplication().setAmountRequested(amountRequested); +// log.info("Set amountRequested to {} for Application ID: {}", amountRequested, applicationFormEntity.getApplication().getId()); +// } catch (NumberFormatException e) { +// log.error("Invalid number format for requested amount: {}", fieldValue, e); +// throw new IllegalArgumentException("Field value is not a valid number: " + fieldValue, e); +// } +// } +// }); + + contentResponseBeans.stream() + .filter(content -> content.getId().toString().equals(applicationFormFieldRequestBean.getFieldId())) + .findFirst() + .ifPresent(content -> { + Object fieldValue = applicationFormFieldRequestBean.getFieldValue(); + if (fieldValue == null) { + return; + } + + // Convert settings list to a map + Map settingMap = content.getSettings().stream() + .collect(Collectors.toMap(SettingResponseBean::getName, SettingResponseBean::getValue, (v1, v2) -> v1)); + + String fieldType = content.getName(); + + // Define handlers for different (fieldType + settingName) combinations + Map handlers = new HashMap<>(); + + // Add handler for isRequestedAmount + if ("numberinput".equals(fieldType) && Boolean.TRUE.equals(settingMap.get("isRequestedAmount"))) { + handlers.put("isRequestedAmount", () -> { + try { + BigDecimal amountRequested = new BigDecimal(fieldValue.toString()); + applicationFormEntity.getApplication().setAmountRequested(amountRequested); + log.info("Set amountRequested to {} for Application ID: {}", amountRequested, applicationFormEntity.getApplication().getId()); + } catch (NumberFormatException e) { + log.error("Invalid number format for requested amount: {}", fieldValue, e); + throw new IllegalArgumentException("Field value is not a valid number: " + fieldValue, e); + } + }); + } + + // Add handler for isPecEmail + if ("textinput".equals(fieldType) && Boolean.TRUE.equals(settingMap.get("isPecEmail"))) { + handlers.put("isPecEmail", () -> { + applicationFormEntity.getApplication().setPecEmail(fieldValue.toString()); + log.info("Set PEC to {} for Application ID: {}", fieldValue, applicationFormEntity.getApplication().getId()); + }); + } + + // Run all applicable handlers + handlers.values().forEach(Runnable::run); + }); ApplicationFormFieldEntity oldApplicationFormFieldData = null; diff --git a/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java b/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java index 66bf44d7..76a7fcff 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java @@ -138,6 +138,9 @@ public class AppointmentDao { @Autowired private ApplicationEvaluationDao applicationEvaluationDao; + @Autowired + private NdganagRepository ndganagRepository; + private final Map executorMap = new ConcurrentHashMap<>(); @@ -535,7 +538,13 @@ public class AppointmentDao { String authorizationToken = getBearerToken(hub); // Try retrieving NDG by VAT number - AppointmentLoginResponse ndgResponse = retrieveNdgByVatNumber(company.getVatNumber(), authorizationToken, hub, application); + NdganagEntity ndganagEntity = ndganagRepository.findByVatNumber(company.getVatNumber()); + AppointmentLoginResponse ndgResponse=new AppointmentLoginResponse(); + if (ndganagEntity != null || ndganagEntity.getNdg() != null) { + ndgResponse.setNdg(ndganagEntity.getNdg()); + }else { + ndgResponse = retrieveNdgByVatNumber(company.getVatNumber(), authorizationToken, hub, application); + } if (isNdgValid(ndgResponse.getNdg())) { saveNdg(application, company, ndgResponse.getNdg()); log.info("NDG successfully generated for applicationId: {}", applicationId); @@ -716,10 +725,7 @@ public class AppointmentDao { try { log.info("Initiating NDG retrieval by VAT number | ApplicationId: {}, HubId: {}, VAT: {}", application.getId(), hub.getId(), vatNumber); // Prepare the NDG request - AppointmentNdgRequest ndgRequest = getAppointmentNdgRequest(vatNumber); - // Call the API to retrieve NDG - ResponseEntity response = appointmentApiService.getNdgByVatNumber(ndgRequest, authorizationToken); - String responseJson = Utils.convertObjectToJson(response.getBody()); + String responseJson = getNdgFromExternalService(vatNumber, authorizationToken); // Parse and return the NDG response return parseNdgResponse(responseJson); } catch (FeignException.Forbidden forbiddenException) { @@ -875,30 +881,38 @@ public class AppointmentDao { } public AppointmentLoginResponse parseNdgResponse(String jsonResponse) { + AppointmentLoginResponse loginResponse = new AppointmentLoginResponse(); + String ndg=extractNdg(jsonResponse); + if (ndg==null){ return null;} + else { + loginResponse.setNdg(ndg); + } + return loginResponse; + } + private String extractNdg(String jsonResponse) { try { ObjectMapper objectMapper = new ObjectMapper(); JsonNode rootNode = objectMapper.readTree(jsonResponse); JsonNode dataArray = rootNode.get(GepafinConstant.DATA_STRING); if (dataArray == null || !dataArray.isArray() || dataArray.isEmpty()) { log.info("NDG data is empty or missing in the response."); - AppointmentLoginResponse emptyResponse = new AppointmentLoginResponse(); - emptyResponse.setNdg(null); - return emptyResponse; + return null; } JsonNode firstDataEntry = dataArray.get(0); AppointmentLoginResponse response = new AppointmentLoginResponse(); if (firstDataEntry.has(GepafinConstant.NDG_STRING)) { - response.setNdg(normalizeNullValue(firstDataEntry.get(GepafinConstant.NDG_STRING).asText())); + String ndg=normalizeNullValue(firstDataEntry.get(GepafinConstant.NDG_STRING).asText()); + return ndg; } - return response; } catch (Exception e) { log.error("Failed to parse response: {}", e.getMessage(), e); throw new RuntimeException("Failed to parse NDG response.", e); } + return null; } - private String normalizeNullValue(String value) { + public String normalizeNullValue(String value) { return (value == null || GepafinConstant.NULL_STRING.equalsIgnoreCase(value.trim())) ? null : value; } @@ -1415,5 +1429,54 @@ public class AppointmentDao { futureRef.set(future); } + public NdgResponse getNdgByVatNumber(String vatNumber, UserEntity userEntity) { + HubEntity hub=userEntity.getHub(); + NdganagEntity ndganagEntity = ndganagRepository.findByVatNumber(vatNumber); + NdgResponse ndgResponse=new NdgResponse(); + String jsonResponse=null; + String authorizationToken = hub.getAppointmentAuthTokenId(); + if (ndganagEntity == null || ndganagEntity.getNdg() == null) { + + try { + log.info("Initiating NDG retrieval by VAT number | HubId: {}, VAT: {}", hub.getId(), vatNumber); + // Prepare the NDG request + jsonResponse=getNdgFromExternalService(vatNumber, authorizationToken); + checkAndSaveNdg(jsonResponse, ndgResponse); + // Parse and return the NDG response + } catch (FeignException.Forbidden forbiddenException) { + log.error("403 Forbidden during NDG retrieval | HubId: {}", hub.getId()); + logForbiddenError(); + // Regenerate the token and retry + String newAuthorizationToken = regenerateTokenAndSave(hub, null); + jsonResponse= getNdgFromExternalService(vatNumber,newAuthorizationToken); + if (checkAndSaveNdg(jsonResponse, ndgResponse)) return null; + } catch (Exception e) { + log.error("Error during NDG retrieval |, HubId: {}, Message: {}", hub.getId(), e.getMessage(), e); + throw new RuntimeException("NDG retrieval failed.", e); + } + }else { + ndgResponse.setNdg(ndganagEntity.getNdg()); + } + return ndgResponse; + } + + private boolean checkAndSaveNdg(String jsonResponse, NdgResponse ndgResponse) { + String ndg=extractNdg(jsonResponse); + if (ndg==null){ + return true; + } + else { + ndgResponse.setNdg(ndg); + } + return false; + } + + private String getNdgFromExternalService(String vatNumber, String authorizationToken) { + AppointmentNdgRequest ndgRequest = getAppointmentNdgRequest(vatNumber); + // Call the API to retrieve NDG + ResponseEntity response = appointmentApiService.getNdgByVatNumber(ndgRequest, authorizationToken); + String responseJson = Utils.convertObjectToJson(response.getBody()); + return responseJson; + } } diff --git a/src/main/java/net/gepafin/tendermanagement/dao/CompanyDao.java b/src/main/java/net/gepafin/tendermanagement/dao/CompanyDao.java index 26ca3f29..8b59eebe 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/CompanyDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/CompanyDao.java @@ -2,6 +2,7 @@ package net.gepafin.tendermanagement.dao; import org.springframework.data.domain.Pageable; // Correct package +import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -413,7 +414,7 @@ public class CompanyDao { Translator.toLocale(GepafinConstant.INVALID_LIMIT)); } - int successfulUpdates = 0; + int successfulUpdates = 0; int failedUpdates = 0; int invalidVatNumbers = 0; @@ -531,7 +532,11 @@ public class CompanyDao { Map dataMap = Utils.extractMap(companyDataMap, "data"); Object dataObj = companyDataMap.get("data"); - if (dataMap == null) { + if (dataObj instanceof Map singleMap) { + dataMap = (Map) singleMap; + } else if (dataObj instanceof List list && !list.isEmpty() && list.get(0) instanceof Map firstMap) { + dataMap = (Map) firstMap; + } else { log.warn("Company ID {}: 'data' section is missing or invalid in the JSON.", company.getId()); return; } @@ -540,11 +545,11 @@ public class CompanyDao { updateCodiceAtecoField(company); } else { - Object pecEmail = Utils.extractMap(dataMap, "pec"); + Object pecEmail = dataMap.get("pec"); if (pecEmail == null) { log.warn("Company ID {}: 'pec' section is missing or invalid.", company.getId()); - company.setPec((String) pecEmail); } + company.setPec((String) pecEmail); // Extract 'atecoClassification' section Map atecoClassificationMap = Utils.extractMap(dataMap, "atecoClassification"); @@ -577,49 +582,79 @@ public class CompanyDao { } public void getCompanyEntity() { - List companyEntities=companyRepository.findAll(); + List companyEntities=companyRepository.findByJsonIsNotNullAndPecIsNull(); + List companyEntityList=new ArrayList<>(); for (CompanyEntity company:companyEntities){ - if(company.getJson()!=null){ + if(company.getJson()!=null && company.getPec()==null){ if (company == null || company.getJson() == null || company.getJson().isEmpty()) { log.warn("Company is null or JSON data is empty."); - return; + continue; } Map vatCheckResponse = Utils.convertJsonStringToMap(company.getJson()); if (vatCheckResponse == null) { log.warn("Company ID {}: Invalid JSON response.", company.getId()); - return; + continue; } Map companyDataMap = Utils.convertJsonStringToMap(company.getJson()); if (companyDataMap == null) { log.warn("Company ID {}: Failed to parse JSON data.", company.getId()); - return; + continue; } + Object dataObj = vatCheckResponse.get("data"); - if (!(dataObj instanceof Map dataMap)) { - log.warn("Company ID {}: 'data' is missing or not a valid object.", company.getId()); - return; + Map dataMap=null; +// if (!(dataObj instanceof Map dataMap)) { +// log.warn("Company ID {}: 'data' is missing or not a valid object.", company.getId()); +// continue; +// } + + if (dataObj instanceof Map singleMap) { + dataMap = (Map) singleMap; + } else if (dataObj instanceof List list && !list.isEmpty() && list.get(0) instanceof Map firstMap) { + dataMap = (Map) firstMap; + } else { + log.warn("Company ID {}: 'data' section is missing or invalid in the JSON.", company.getId()); + continue; } + + if (dataMap.containsKey("pec")) { + Object pecEmailObj = dataMap.get("pec"); + if (pecEmailObj instanceof String pec && !pec.isEmpty()) { + company.setPec(pec); // Only set if valid string + companyEntityList.add(company); + } else { + log.warn("Company ID {}: 'pec' is missing, empty, or not a string.", company.getId()); + continue; + } + } + + if (!dataMap.containsKey("dettaglio")) { log.warn("Company ID {}: 'dettaglio' not present inside 'data'. Skipping codiceAteco update.", company.getId()); - return; + continue; } + Object dettaglioObj = dataMap.get("dettaglio"); if (!(dettaglioObj instanceof Map dettaglio)) { log.warn("Company ID {}: 'dettaglio' is not a valid object.", company.getId()); - return; + continue; } - Object pecEmailObj = dettaglio.get("pec"); if (pecEmailObj instanceof String pec && !pec.isEmpty()) { - company.setPec(pec); // Only set if valid string + if(pec!=null) { + company.setPec(pec); // Only set if valid string + companyEntityList.add(company); + } } else { log.warn("Company ID {}: 'pec' is missing, empty, or not a string.", company.getId()); + continue; } } } + companyRepository.saveAll(companyEntityList); } } diff --git a/src/main/java/net/gepafin/tendermanagement/entities/ApplicationEntity.java b/src/main/java/net/gepafin/tendermanagement/entities/ApplicationEntity.java index e2208c77..8e783c70 100644 --- a/src/main/java/net/gepafin/tendermanagement/entities/ApplicationEntity.java +++ b/src/main/java/net/gepafin/tendermanagement/entities/ApplicationEntity.java @@ -73,4 +73,7 @@ public class ApplicationEntity extends BaseEntity { @Column(name = "APPLICATION_EVALUATION_ID") private Long applicationEvaluationId; + @Column(name = "PEC_EMAIL") + private String pecEmail; + } \ No newline at end of file diff --git a/src/main/java/net/gepafin/tendermanagement/entities/NdganagEntity.java b/src/main/java/net/gepafin/tendermanagement/entities/NdganagEntity.java new file mode 100644 index 00000000..463f31c9 --- /dev/null +++ b/src/main/java/net/gepafin/tendermanagement/entities/NdganagEntity.java @@ -0,0 +1,33 @@ +package net.gepafin.tendermanagement.entities; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; +import lombok.Data; +import org.hibernate.annotations.Where; + +@Entity +@Table(name = "NDGANAG") +@Data +@Where(clause = "is_deleted = false") +public class NdganagEntity extends BaseEntity{ + + @Column(name = "NDG") + private String ndg; + + @Column(name = "COMPANY_NAME") + private String companyName; + + @Column(name = "VAT_NUMBER") + private String vatNumber; + + @Column(name = "CODICE_FISCALE") + private String codiceFiscale; + + @Column(name = "JSON") + private String json; + + @Column(name = "IS_DELETED") + private Boolean isDeleted; + +} diff --git a/src/main/java/net/gepafin/tendermanagement/enums/UserActionContextEnum.java b/src/main/java/net/gepafin/tendermanagement/enums/UserActionContextEnum.java index fe2e4cd1..1de6b333 100644 --- a/src/main/java/net/gepafin/tendermanagement/enums/UserActionContextEnum.java +++ b/src/main/java/net/gepafin/tendermanagement/enums/UserActionContextEnum.java @@ -67,6 +67,7 @@ public enum UserActionContextEnum { GET_COMPANY_BY_USER("GET_COMPANY_BY_USER"), REMOVE_COMPANY_FROM_USER("REMOVE_COMPANY_FROM_USER"), UPDATE_COMPANY_JSON("UPDATE_COMPANY_JSON"), + EXTRACT_PEC_FROM_COMPANY("EXTRACT_PEC_FROM_COMPANY"), /** LookUpData action context **/ CREATE_LOOKUP_DATA("CREATE_LOOKUP_DATA"), @@ -180,6 +181,7 @@ public enum UserActionContextEnum { CHECK_OR_CREATE_NDG_CODE("CHECK_OR_CREATE_NDG_CODE"), CREATE_APPOINTMENT("CREATE_APPOINTMENT"), UPLOAD_DOCUMENT_TO_EXTERNAL_SYSTEM("UPLOAD_DOCUMENT_TO_EXTERNAL_SYSTEM"), + GET_NDG_BY_VAT_NUMBER("GET_NDG_BY_VAT_NUMBER"), GET_ALL_NOTIFICATION_BY_PAGINATION("GET_ALL_NOTIFICATION_BY_PAGINATION"), GET_ALL_CALL_BY_PAGINATION("GET_ALL_CALL_BY_PAGINATION"), diff --git a/src/main/java/net/gepafin/tendermanagement/repositories/CompanyRepository.java b/src/main/java/net/gepafin/tendermanagement/repositories/CompanyRepository.java index 350b9b49..39d261ea 100644 --- a/src/main/java/net/gepafin/tendermanagement/repositories/CompanyRepository.java +++ b/src/main/java/net/gepafin/tendermanagement/repositories/CompanyRepository.java @@ -31,5 +31,7 @@ public interface CompanyRepository extends JpaRepository { """) Page findCompaniesWithMissingVatCheck(Pageable pageable); + List findByJsonIsNotNullAndPecIsNull(); + } diff --git a/src/main/java/net/gepafin/tendermanagement/repositories/NdganagRepository.java b/src/main/java/net/gepafin/tendermanagement/repositories/NdganagRepository.java new file mode 100644 index 00000000..ad0fe99a --- /dev/null +++ b/src/main/java/net/gepafin/tendermanagement/repositories/NdganagRepository.java @@ -0,0 +1,14 @@ +package net.gepafin.tendermanagement.repositories; + +import net.gepafin.tendermanagement.entities.NdganagEntity; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + + +@Repository +public interface NdganagRepository extends JpaRepository { + + NdganagEntity findByVatNumber(String vatNumber); + + +} diff --git a/src/main/java/net/gepafin/tendermanagement/service/AppointmentService.java b/src/main/java/net/gepafin/tendermanagement/service/AppointmentService.java index 842901a4..e324804c 100644 --- a/src/main/java/net/gepafin/tendermanagement/service/AppointmentService.java +++ b/src/main/java/net/gepafin/tendermanagement/service/AppointmentService.java @@ -13,4 +13,7 @@ public interface AppointmentService { AppointmentCreationResponse createAppointmentForApplication(HttpServletRequest request, Long applicationId, CreateAppointmentRequest createAppointmentRequest); DocumentUploadResponse uploadDocToExternalSystem(HttpServletRequest request, Long documentId, UploadDocToExternalSystemRequest docToExternalSystemRequest); + + NdgResponse getNdgByVatNumber(HttpServletRequest request,String vatNumber); + } diff --git a/src/main/java/net/gepafin/tendermanagement/service/CompanyService.java b/src/main/java/net/gepafin/tendermanagement/service/CompanyService.java index 74b6be0e..4c868f1e 100644 --- a/src/main/java/net/gepafin/tendermanagement/service/CompanyService.java +++ b/src/main/java/net/gepafin/tendermanagement/service/CompanyService.java @@ -47,4 +47,5 @@ public interface CompanyService { void updateMissingVatCheckResponses(HttpServletRequest request, LimitRequest limitRequest); + void extractPecFromJson(HttpServletRequest request); } diff --git a/src/main/java/net/gepafin/tendermanagement/service/impl/AppointmentServiceImpl.java b/src/main/java/net/gepafin/tendermanagement/service/impl/AppointmentServiceImpl.java index 2e7960df..6c49b214 100644 --- a/src/main/java/net/gepafin/tendermanagement/service/impl/AppointmentServiceImpl.java +++ b/src/main/java/net/gepafin/tendermanagement/service/impl/AppointmentServiceImpl.java @@ -2,12 +2,14 @@ package net.gepafin.tendermanagement.service.impl; import jakarta.servlet.http.HttpServletRequest; import net.gepafin.tendermanagement.dao.AppointmentDao; +import net.gepafin.tendermanagement.entities.UserEntity; import net.gepafin.tendermanagement.model.request.CreateAppointmentRequest; import net.gepafin.tendermanagement.model.request.UploadDocToExternalSystemRequest; import net.gepafin.tendermanagement.model.response.AppointmentCreationResponse; import net.gepafin.tendermanagement.model.response.DocumentUploadResponse; import net.gepafin.tendermanagement.model.response.NdgResponse; import net.gepafin.tendermanagement.service.AppointmentService; +import net.gepafin.tendermanagement.util.Validator; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -17,6 +19,9 @@ public class AppointmentServiceImpl implements AppointmentService { @Autowired private AppointmentDao appointmentDao; + @Autowired + private Validator validator; + @Override public NdgResponse checkNdgForAppointment(HttpServletRequest request, Long applicationId) { @@ -34,4 +39,11 @@ public class AppointmentServiceImpl implements AppointmentService { return appointmentDao.uploadDocumentToExternalSystem(documentId, docToExternalSystemRequest); } + + @Override + public NdgResponse getNdgByVatNumber(HttpServletRequest request,String vatNumber) { + UserEntity userEntity = validator.validateUser(request); + NdgResponse ndgResponse= appointmentDao.getNdgByVatNumber(vatNumber,userEntity); + return ndgResponse; + } } 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 f1d8f890..f9a8b8c8 100644 --- a/src/main/java/net/gepafin/tendermanagement/service/impl/CompanyServiceImpl.java +++ b/src/main/java/net/gepafin/tendermanagement/service/impl/CompanyServiceImpl.java @@ -140,6 +140,12 @@ public class CompanyServiceImpl implements CompanyService { @Override public void updateMissingVatCheckResponses(HttpServletRequest request, LimitRequest limitRequest) { UserEntity userEntity =validator.validateUser(request); - companyDao.updateMissingVatCheckResponses(request, limitRequest); + companyDao.updateMissingVatCheckResponses(request,limitRequest); + } + + @Override + public void extractPecFromJson(HttpServletRequest request) { + UserEntity userEntity =validator.validateUser(request); + companyDao.getCompanyEntity(); } } diff --git a/src/main/java/net/gepafin/tendermanagement/web/rest/api/AppointmentApi.java b/src/main/java/net/gepafin/tendermanagement/web/rest/api/AppointmentApi.java index 5507492a..64f767b9 100644 --- a/src/main/java/net/gepafin/tendermanagement/web/rest/api/AppointmentApi.java +++ b/src/main/java/net/gepafin/tendermanagement/web/rest/api/AppointmentApi.java @@ -56,4 +56,15 @@ public interface AppointmentApi { ResponseEntity> uploadDocumentToExternalSystem(HttpServletRequest request, @Parameter(description = "The document id", required = true) @PathVariable(value = "documentId", required = true) Long documentId, @RequestBody UploadDocToExternalSystemRequest docToExternalSystemRequest); + + @Operation(summary = "API to get ndg by vatNumber", 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 = "/vatNumber/{vatNumber}", produces = MediaType.APPLICATION_JSON_VALUE) + ResponseEntity> getNdgByVatNumber(HttpServletRequest request,@PathVariable(value = "vatNumber", required = true) @Parameter(description = "vatNumber",required = true)String vatNumber); + } 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 fea29d0d..a30e18be 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 @@ -169,4 +169,17 @@ public interface CompanyApi { @PreAuthorize("hasRole('ROLE_SUPER_ADMIN') ") ResponseEntity> updateMissingVatCheckResponses(HttpServletRequest request, @Parameter(description = "Limit request object ", required = true) @RequestBody LimitRequest limitRequest); + + @Operation(summary = "Api to extract pec from the company json", 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 = "/pec", produces = { "application/json" }) + @PreAuthorize("hasRole('ROLE_SUPER_ADMIN') ") + ResponseEntity> extractPecFromJson(HttpServletRequest request); + + } diff --git a/src/main/java/net/gepafin/tendermanagement/web/rest/api/impl/AppointmentController.java b/src/main/java/net/gepafin/tendermanagement/web/rest/api/impl/AppointmentController.java index 1708b8cd..7212355a 100644 --- a/src/main/java/net/gepafin/tendermanagement/web/rest/api/impl/AppointmentController.java +++ b/src/main/java/net/gepafin/tendermanagement/web/rest/api/impl/AppointmentController.java @@ -17,6 +17,7 @@ import net.gepafin.tendermanagement.service.AppointmentService; import net.gepafin.tendermanagement.util.LoggingUtil; import net.gepafin.tendermanagement.web.rest.api.AppointmentApi; import net.gepafin.tendermanagement.web.rest.api.errors.Status; +import org.opensaml.xmlsec.signature.G; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -81,4 +82,19 @@ public class AppointmentController implements AppointmentApi { .body(new Response<>(documentUploadResponse, Status.SUCCESS, Translator.toLocale(message))); } + + @Override + public ResponseEntity> getNdgByVatNumber(HttpServletRequest request,String vatNumber) { + loggingUtil.logUserAction( + UserActionRequest.builder().request(request).actionType(UserActionLogsEnum.UPLOAD).actionContext(UserActionContextEnum.GET_NDG_BY_VAT_NUMBER).build()); + + NdgResponse ndgResponse= appointmentService.getNdgByVatNumber(request,vatNumber); + if(ndgResponse==null){ + return ResponseEntity.status(HttpStatus.CREATED) + .body(new Response<>(ndgResponse, Status.NOT_FOUND, Translator.toLocale(GepafinConstant.NDG_NOT_FOUND))); + } + + return ResponseEntity.status(HttpStatus.CREATED) + .body(new Response<>(ndgResponse, Status.SUCCESS, Translator.toLocale(GepafinConstant.NDG_FETCH_SUCCESSFULLY))); + } } 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 64dee16b..08d34ccf 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 @@ -201,4 +201,17 @@ public class CompanyApiController implements CompanyApi{ return ResponseEntity.status(HttpStatus.OK) .body(new Response<>(null, Status.SUCCESS, Translator.toLocale(GepafinConstant.COMPANY_UPDATED_SUCCESS_MSG))); } + + @Override + public ResponseEntity> extractPecFromJson(HttpServletRequest request) { + + log.info("Api to set pec from json field"); + + companyService.extractPecFromJson(request); + + loggingUtil.logUserAction(UserActionRequest.builder().request(request).actionType(UserActionLogsEnum.UPDATE).actionContext(UserActionContextEnum.EXTRACT_PEC_FROM_COMPANY).build()); + + return ResponseEntity.status(HttpStatus.OK) + .body(new Response<>(null, Status.SUCCESS, Translator.toLocale(GepafinConstant.COMPANY_UPDATED_SUCCESS_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 f69ef624..36f52dee 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 @@ -2955,4 +2955,24 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/message_en.properties b/src/main/resources/message_en.properties index e69af662..8d9aae03 100644 --- a/src/main/resources/message_en.properties +++ b/src/main/resources/message_en.properties @@ -408,3 +408,5 @@ resend.email.sent.failed.msg = Failed to resend the email. application.readmit.success=Application has been readmitted successfully. no.email.log.msg = No failed emails found for given userActionId. user.action.id.not.found = User Action id not found. +ndg.not.found=NDG not found. +email.pec.cannot.null=Email pec is required. diff --git a/src/main/resources/message_it.properties b/src/main/resources/message_it.properties index ef23cfac..5a94ede6 100644 --- a/src/main/resources/message_it.properties +++ b/src/main/resources/message_it.properties @@ -399,3 +399,5 @@ resend.email.sent.failed.msg = Impossibile inviare nuovamente l'e-mail. application.readmit.success=L'applicazione è stata riammessa con successo. no.email.log.msg = Nessuna email trovata per userActionId specificato. user.action.id.not.found = ID azione utente non trovato. +ndg.not.found=NDG non trovato. +email.pec.cannot.null=L'indirizzo email pec obbligatorio.