Merge pull request #120 from Kitzanos/feature/GEPAFINBE-106

GEPAFINBE-106 (Create Appointments (Only for Hub Gepafin))
This commit is contained in:
rbonazzo-KZ
2024-12-06 12:13:09 +01:00
committed by GitHub
35 changed files with 1532 additions and 14 deletions

View File

@@ -88,8 +88,8 @@
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-s3</artifactId>
<version>1.12.312</version>
<artifactId>aws-java-sdk</artifactId>
<version>1.12.563</version>
</dependency>
<dependency>

View File

@@ -0,0 +1,27 @@
package net.gepafin.tendermanagement.constants;
public class AppointmentApiConstant {
public static final String ODESSA_LOGIN = "/WSGatewayLogin.apiLogin";
public static final String GET_NDG_BY_VAT_NUMBER = "/WSAnagrafica.getListaNdg";
public static final String CREATE_VISURA = "/WSAnagrafica.createVisura";
public static final String GET_VISURA_LIST = "/WSAnagrafica.getVisuraList";
public static final String GET_APPOINTMENT_TEMPLATE = "/WSCrmConsulenza.getAppuntamentoTemplate?idAppuntamentoTemplate=7";
public static final String CREATE_APPOINTMENT_FROM_TEMPLATE = "/WSCrmConsulenza.createAppointmentFromTemplate";
public static final String UPLOAD_APOOINTMENT_DOCUMENT = "/WSDocumentDetail.createStream";
//get ndg number
public static final int TARGET_PAGE_SIZE = 1;
public static final int RECORD_PER_PAGE_SIZE = 10;
//create visura request Body constant
public static final boolean CREA_ANAGRAFICA = Boolean.TRUE;
public static final boolean SALVA_DOCUMENTI = Boolean.TRUE;
public static final String VISURA_PROVIDER = "cerved";
public static final String VISURA_TYPE = "StandardReport";
public static final String VISURA_MODE = "visure";
public static final String COD_AGENTE = "UtenzaAPIPortal";
public static final boolean IS_FROM_RATING = Boolean.FALSE;
public static final boolean IS_ANAGRAFICA_LEGAME = Boolean.FALSE;
}

View File

@@ -306,5 +306,37 @@ public class GepafinConstant {
public static final String LOGIN_ATTEMPT_ID = "loginAttemptId";
public static final String USER_ACTION_ID = "userActionId";
public static final String ATLEAST_ONE_ID_REQUIRED="atleast.one.id.required";
//Appointment
public static final String NDG_IN_PROGRESS = "IN_PROGRESS";
public static final String NDG_AVAILABLE = "ndg.available";
public static final String NDG_GENERATION_IS_IN_PROGRESS = "ndg.generation.in.progress";
public static final String NDG_GENERATED = "NDG_GENERATED";
public static final String NDG_NOT_FOUND_FOR_APPLICATION = "ndg.not.found.for.this.application.or.invalid";
public static final String APPOINTMENT_ALREADY_CREATED = "appointment.already.created";
public static final String EXTERNAL_DOCUMENT_UPLOAD_FAILURE_MSG = "document.not.uploaded.to.external.system.please.try.again";
public static final String PROVIDE_VALID_APPLICATION_DOC_ID = "provide.valid.application.document.id";
public static final String DOCUMENT_UPLOADED_SUCCESSFULLY_TO_EXTERNAL_SYSTEM = "document.uploaded.successfully.to.external.system";
public static final String ERROR_UPLOADING_DOCUMENT = "error.in.uploading.document.check.input";
public static final String DOCUMENT_ALREADY_UPLOADED = "document.already.uploaded";
public static final String NDG_NOT_MATCHED_OR_NOT_FOUND = "ndg.not.found.or.not.matched";
public static final String NO_NDG_FOR_ANOTHER_HUB = "ndg.generation.is.only.for.gepafin";
public static final String NO_APPOINTMENT_FOR_ANOTHER_HUB = "appointment.creation.is.only.for.gepafin";
public static final String NO_DOCUMENT_UPLOAD_FOR_ANOTHER_HUB = "upload.document.is.only.for.gepafin";
public static final String APPOINTMENT_CREATED = "appointment.created.successfully";
public static final String DATA_STRING = "data";
public static final String DOCUMENT_ATTACHMENT_ID_STRING = "documentAttachmentId";
public static final String TEMP_FILE_PATH = "/tmp/";
public static final String RICHIESTA_CLIENTE_STRING = "richiestaCliente";
public static final String ID_STRING = "id";
public static final String NULL_STRING = "null";
public static final String NDG_STRING = "ndg";
public static final String ID_VISURA_STRING = "idVisura";
public static final String NDG_FETCH_SUCCESSFULLY = "ndg.fetch.successfully";
public static final String AUTH_JWT_SECRET_KEY = "hTa5qe$af/4',BFs";
public static final String JWT_ALGO_HEADER = "{\"alg\":\"HS256\",\"typ\":\"JWT\"}";
public static final String HMAC_ALGO = "HmacSHA256";
public static final String ERROR_IN_GENERATING_NDG_TRY_AGAIN = "error.try.again";
public static final String POLLING_THREAD_NAME = "Ndg-Polling-Thread-";
}

View File

@@ -830,6 +830,9 @@ public class ApplicationDao {
if (status.equals(ApplicationStatusTypeEnum.DRAFT) && Boolean.TRUE.equals(applicationEntity.getStatus().equals(ApplicationStatusTypeEnum.AWAITING.getValue()))) {
applicationEntity.setStatus(status.getValue());
}
if(status.equals(ApplicationStatusTypeEnum.ADMISSIBLE) && Boolean.TRUE.equals(applicationEntity.getStatus().equals(ApplicationStatusTypeEnum.APPOINTMENT.getValue()))){
applicationEntity.setStatus(status.getValue());
}
applicationEntity = applicationRepository.save(applicationEntity);
if (!status.equals(ApplicationStatusTypeEnum.SUBMIT)) {

View File

@@ -0,0 +1,889 @@
package net.gepafin.tendermanagement.dao;
import com.amazonaws.services.s3.AmazonS3Client;
import com.amazonaws.services.s3.model.GetObjectRequest;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import feign.FeignException;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import net.gepafin.tendermanagement.config.Translator;
import net.gepafin.tendermanagement.constants.AppointmentApiConstant;
import net.gepafin.tendermanagement.constants.GepafinConstant;
import net.gepafin.tendermanagement.entities.ApplicationEntity;
import net.gepafin.tendermanagement.entities.CompanyEntity;
import net.gepafin.tendermanagement.entities.DocumentEntity;
import net.gepafin.tendermanagement.entities.HubEntity;
import net.gepafin.tendermanagement.enums.ApplicationStatusTypeEnum;
import net.gepafin.tendermanagement.enums.DocumentSourceTypeEnum;
import net.gepafin.tendermanagement.enums.VersionActionTypeEnum;
import net.gepafin.tendermanagement.model.request.AppointmentCreationRequest;
import net.gepafin.tendermanagement.model.request.AppointmentNdgRequest;
import net.gepafin.tendermanagement.model.request.AppointmentVisuraListRequest;
import net.gepafin.tendermanagement.model.request.AppointmentVisuraRequest;
import net.gepafin.tendermanagement.model.request.CreateAppointmentRequest;
import net.gepafin.tendermanagement.model.request.UploadDocToExternalSystemRequest;
import net.gepafin.tendermanagement.model.request.VersionHistoryRequest;
import net.gepafin.tendermanagement.model.response.AppointmentCreationResponse;
import net.gepafin.tendermanagement.model.response.AppointmentLoginResponse;
import net.gepafin.tendermanagement.model.response.DocumentUploadResponse;
import net.gepafin.tendermanagement.model.response.NdgResponse;
import net.gepafin.tendermanagement.repositories.ApplicationRepository;
import net.gepafin.tendermanagement.repositories.CompanyRepository;
import net.gepafin.tendermanagement.repositories.DocumentRepository;
import net.gepafin.tendermanagement.repositories.HubRepository;
import net.gepafin.tendermanagement.service.ApplicationService;
import net.gepafin.tendermanagement.service.CompanyService;
import net.gepafin.tendermanagement.service.feignClient.AppointmentApiService;
import net.gepafin.tendermanagement.util.LoggingUtil;
import net.gepafin.tendermanagement.util.Utils;
import net.gepafin.tendermanagement.web.rest.api.errors.CustomValidationException;
import net.gepafin.tendermanagement.web.rest.api.errors.Status;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
@Slf4j
@Component
public class AppointmentDao {
@Value("${appointment.portal.user}")
private String user;
@Value("${appointment.portal.password}")
private String password;
@Value("${appointment.portal.source}")
private String source;
@Value("${appointment.portal.context}")
private String context;
@Value("${default.hub.uuid}")
private String defaultHubUuid;
@Value("${aws.s3.url}")
private String s3Url;
@Value("${aws.s3.bucket.name}")
private String OLD_BUCKET;
@Value("${flagDaFirmare}")
private Boolean flagDaFirmare;
@Autowired
private HubRepository hubRepository;
@Autowired
private AppointmentApiService appointmentApiService;
@Autowired
private ApplicationService applicationService;
@Autowired
private CompanyService companyService;
@Autowired
private ApplicationRepository applicationRepository;
@Autowired
private CompanyRepository companyRepository;
@Autowired
private DocumentDao documentDao;
@Autowired
private AmazonS3Client s3Client;
@Autowired
private DocumentRepository documentRepository;
@Autowired
private HttpServletRequest request;
@Autowired
private LoggingUtil loggingUtil;
private final Map<Long, ExecutorService> executorMap = new ConcurrentHashMap<>();
public NdgResponse checkNdgForAppointment(Long applicationId) {
try {
NdgResponse ndgResponseToReturn = new NdgResponse();
// Validate application, company, and hub
ApplicationEntity application = applicationService.validateApplication(applicationId);
if (application.getNdgStatus() != null && application.getNdgStatus().equalsIgnoreCase(GepafinConstant.NDG_IN_PROGRESS)) {
throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.NDG_GENERATION_IS_IN_PROGRESS));
}
//cloned for old application data
ApplicationEntity oldApplicationData = Utils.getClonedEntityForData(application);
CompanyEntity company = companyService.validateCompany(application.getCompanyId());
HubEntity hub = hubRepository.findByHubId(application.getHubId());
if (!hub.getUniqueUuid().equals(defaultHubUuid)) {
log.info("Ndg cannot be created for another Hub, it is default for Gepafin.");
throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.NO_NDG_FOR_ANOTHER_HUB));
}
// Check if NDG and idVisura are already present
if (isNdgAndIdVisuraPresent(application)) {
log.info("NDG already exist for applicationId: {}", applicationId);
ndgResponseToReturn.setNdg(application.getNdg());
return ndgResponseToReturn;
}
// Authenticate and fetch token if required
if (hub.getAppointmentAuthTokenId() == null && hub.getAreaCode() == null) {
hub = authenticateAndSaveToken(hub);
}
String authorizationToken = getBearerToken(hub);
// Try retrieving NDG by VAT number
AppointmentLoginResponse ndgResponse = retrieveNdgByVatNumber(company.getVatNumber(), authorizationToken, hub, application);
//For testing purpose Commenting it
if (isNdgValid(ndgResponse.getNdg())) {
saveNdgAndIdVisura(application, company, ndgResponse.getNdg(), null);
ndgResponseToReturn.setNdg(application.getNdg());
return ndgResponseToReturn;
}
return getNdgResponse(company, authorizationToken, hub, application, ndgResponseToReturn, oldApplicationData);
} catch (FeignException e) {
log.error("Error in feign client call during NDG handling: {}", e.getMessage(), e);
Utils.callException(e.status(), e);
} catch (CustomValidationException e) {
log.info("Custom validation exception: {}", e.getMessage());
throw e;
} catch (Exception e) {
log.error("Error during NDG handling: {}", e.getMessage(), e);
throw new RuntimeException("Error during fetching NDG.");
}
throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.ERROR_IN_GENERATING_NDG_TRY_AGAIN));
}
private NdgResponse getNdgResponse(CompanyEntity company, String authorizationToken, HubEntity hub, ApplicationEntity application, NdgResponse ndgResponseToReturn,
ApplicationEntity oldApplicationData) {
// Create Visura if NDG is not found
AppointmentLoginResponse visuraResponse = createVisura(company, authorizationToken, hub);
if (isNdgValid(visuraResponse.getNdg())) {
saveNdgAndIdVisura(application, company, visuraResponse.getNdg(), visuraResponse.getIdVisura());
ndgResponseToReturn.setNdg(application.getNdg());
} else if (visuraResponse.getIdVisura() != null) {
application.setNdgStatus(GepafinConstant.NDG_IN_PROGRESS);
application.setStatus(ApplicationStatusTypeEnum.NDG.getValue());
applicationRepository.save(application);
/** This code is responsible for adding a version history log for the "Updating ndg status in application" operation. **/
loggingUtil.addVersionHistory(
VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldApplicationData).newData(application).build());
startNdgPollingTask(application, company, hub);
throw new CustomValidationException(Status.SUCCESS, Translator.toLocale(GepafinConstant.NDG_GENERATION_IS_IN_PROGRESS));
}
return ndgResponseToReturn;
}
private static String getBearerToken(HubEntity hub) {
return "Bearer " + hub.getAppointmentAuthTokenId();
}
private void startNdgPollingTask(ApplicationEntity application, CompanyEntity company, HubEntity hub) {
//cloned for all data
ApplicationEntity oldApplicationData = Utils.getClonedEntityForData(application);
CompanyEntity oldCompanyData = Utils.getClonedEntityForData(company);
// Check if a thread is already running for this application
if (executorMap.containsKey(application.getId())) {
log.warn("Polling task already running for applicationId: {}", application.getId());
return;
}
// Create a dedicated thread (single-threaded executor) for this application
ExecutorService executor = Executors.newSingleThreadExecutor(runnable -> {
Thread thread = new Thread(runnable);
thread.setName(GepafinConstant.POLLING_THREAD_NAME + application.getId());
return thread;
});
executorMap.put(application.getId(), executor);
// Submit polling task to this thread
executor.submit(() -> {
try {
log.info("Polling task started for applicationId: {} on thread: {}", application.getId(), Thread.currentThread().getName());
long startTime = System.currentTimeMillis();
while (true) {
if (application.getNdg() != null)
break;
try {
String visuraListJson = getVisuraList(application.getIdVisura(), hub.getAppointmentAuthTokenId(), application, hub);
String ndg = parseNdgFromVisuraListResponse(visuraListJson);
if (isNdgValid(ndg)) {
company.setNdg(ndg);
application.setNdgStatus(GepafinConstant.NDG_GENERATED);
application.setStatus(ApplicationStatusTypeEnum.NDG.getValue());
application.setNdg(ndg);
applicationRepository.save(application);
companyRepository.save(company);
log.info("NDG obtained for applicationId: {} and saved successfully.", application.getId());
break; // Exit the loop after successful NDG retrieval
} else {
log.warn("NDG not found for applicationId: {} in Visura List API response.", application.getId());
}
// Check if polling time has exceeded the limit
if (System.currentTimeMillis() - startTime > TimeUnit.HOURS.toMillis(2)) {
log.warn("Polling timed out for applicationId: {}", application.getId());
break;
}
// Wait before the next polling attempt
Thread.sleep(TimeUnit.MINUTES.toMillis(15));
} catch (InterruptedException e) {
log.warn("Polling task interrupted for applicationId: {}", application.getId());
Thread.currentThread().interrupt();
break;
} catch (Exception e) {
log.error("Error during NDG polling for applicationId: {}", application.getId(), e);
}
}
} finally {
// Cleanup: Shut down the thread for this application
executor.shutdown();
executorMap.remove(application.getId());
log.info("Polling task completed and thread shut down for applicationId: {}", application.getId());
}
});
/** This code is responsible for adding a version history log for the "Update application ndgCode and ndgStatus" operation. **/
loggingUtil.addVersionHistory(
VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldApplicationData).newData(application).build());
/** This code is responsible for adding a version history log for the "Update company ndgCode" operation. **/
loggingUtil.addVersionHistory(VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldCompanyData).newData(company).build());
}
private boolean isNdgAndIdVisuraPresent(ApplicationEntity application) {
String ndg = application.getNdg();
String idVisura = application.getIdVisura();
if (ndg != null && idVisura == null) {
return true;
} else if (ndg == null && idVisura != null) {
return false;
} else
return ndg != null;
}
private boolean isNdgValid(String ndg) {
return ndg != null && !ndg.isEmpty();
}
private void saveNdgAndIdVisura(ApplicationEntity application, CompanyEntity company, String ndg, String idVisura) {
//cloned for old application and company data
ApplicationEntity oldApplicationData = Utils.getClonedEntityForData(application);
CompanyEntity oldCompanyData = Utils.getClonedEntityForData(company);
application.setNdg(ndg);
application.setIdVisura(idVisura);
application.setNdgStatus(GepafinConstant.NDG_GENERATED);
application.setStatus(ApplicationStatusTypeEnum.NDG.getValue());
company.setNdg(ndg);
companyRepository.save(company);
applicationRepository.save(application);
/** This code is responsible for adding a version history log for the "update application ndg code, status, and Id visura" operation. **/
loggingUtil.addVersionHistory(
VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldApplicationData).newData(application).build());
/** This code is responsible for adding a version history log for the "update company ndg code" operation. **/
loggingUtil.addVersionHistory(VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldCompanyData).newData(company).build());
log.info("NDG saved for applicationId: {}, {}", application.getId(), application.getNdg());
}
private String getVisuraList(String idVisura, String authorizationToken, ApplicationEntity application, HubEntity hub) {
AppointmentVisuraListRequest visuraListRequest = new AppointmentVisuraListRequest();
AppointmentVisuraListRequest.VisuraFilter filter = new AppointmentVisuraListRequest.VisuraFilter();
filter.setIdVisura(idVisura);
visuraListRequest.setFilter(filter);
try {
String requestJson = Utils.convertObjectToJson(visuraListRequest);
ResponseEntity<Object> response = appointmentApiService.getVisuraList(requestJson, authorizationToken);
return Utils.convertObjectToJson(response.getBody());
} catch (FeignException.Forbidden forbiddenException) {
log.error("403 Forbidden received while getting visuraList for Ndg code. Regenerating token...");
// Regenerate the token and retry
String newAuthorizationToken = regenerateTokenAndSave(hub);
return getVisuraList(idVisura, newAuthorizationToken, application, hub);
} catch (Exception e) {
log.error("Failed to fetch Ndg code: {}", e.getMessage(), e);
throw new RuntimeException("Error fetching Ndg List", e);
}
}
private HubEntity authenticateAndSaveToken(HubEntity hub) {
HubEntity oldHubData = Utils.getClonedEntityForData(hub);
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);
/** This code is responsible for adding a version history log for the "Updating auth token for login api in hub" operation. **/
loggingUtil.addVersionHistory(VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldHubData).newData(hub).build());
// Prepare the request body (adjust if necessary for login API)
Map<String, Object> body = Collections.emptyMap();
// Perform login API call
ResponseEntity<Object> 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);
/** This code is responsible for adding a version history log for the "inserting token and areaCode from login odessa response for appointment flow api's"
* operation. **/
loggingUtil.addVersionHistory(
VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldHubData).newData(hub).build());
log.info("Saved new authToken and areaCode for Hub.");
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 (Exception e) {
log.error("Failed to authenticate user on Odessa : {}", e.getMessage(), e);
throw new RuntimeException("Authentication failed on Odessa. try again", e);
}
}
private AppointmentLoginResponse retrieveNdgByVatNumber(String vatNumber, String authorizationToken, HubEntity hub, ApplicationEntity application) {
try {
// Prepare the NDG request
AppointmentNdgRequest ndgRequest = getAppointmentNdgRequest(vatNumber);
// Call the API to retrieve NDG
ResponseEntity<Object> 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 received while retrieving NDG. Regenerating token...");
// Regenerate the token and retry
String newAuthorizationToken = regenerateTokenAndSave(hub);
return retrieveNdgByVatNumber(vatNumber, newAuthorizationToken, hub, application);
} catch (Exception e) {
log.error("Failed to retrieve NDG by VAT number: {}", e.getMessage(), e);
throw new RuntimeException("NDG retrieval failed.", e);
}
}
private String regenerateTokenAndSave(HubEntity hub) {
try {
hub = authenticateAndSaveToken(hub);
return "Bearer " + hub.getAppointmentAuthTokenId();
} catch (Exception e) {
log.error("Failed to regenerate token from Odessa: {}", e.getMessage());
throw new RuntimeException("Token regeneration failed from Odessa.", e);
}
}
private AppointmentLoginResponse createVisura(CompanyEntity company, String authorizationToken, HubEntity hub) {
try {
String visuraRequest = getAppointmentVisuraRequest(company, hub.getAreaCode());
ResponseEntity<Object> response = appointmentApiService.createVisura(visuraRequest, authorizationToken);
String responseJson = Utils.convertObjectToJson(response.getBody());
return parseVisuraResponse(responseJson);
} catch (FeignException.Forbidden forbiddenException) {
log.error("403 Forbidden received while retrieving NDG. Regenerating token...");
// Regenerate the token and retry
String newAuthorizationToken = regenerateTokenAndSave(hub);
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 AppointmentNdgRequest getAppointmentNdgRequest(String 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 {
ObjectMapper objectMapper = new ObjectMapper();
JsonNode rootNode = objectMapper.readTree(jsonResponse);
JsonNode dataNode = rootNode.get(GepafinConstant.DATA_STRING);
if (dataNode != null) {
AppointmentLoginResponse response = new AppointmentLoginResponse();
response.setIdVisura(normalizeNullValue(dataNode.get(GepafinConstant.ID_VISURA_STRING).asText()));
response.setNdg(normalizeNullValue(dataNode.get(GepafinConstant.NDG_STRING).asText()));
return response;
} else {
throw new RuntimeException("Invalid JSON structure: Missing 'data' node.");
}
} catch (Exception e) {
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
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)) {
throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.NDG_NOT_FOUND_FOR_APPLICATION));
}
// Generate authorization token and fetch template data
String authorizationToken = getBearerToken(hub);
ResponseEntity<Object> response = appointmentApiService.getAppointmentTemplateForTemplateCreation(authorizationToken);
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, hub.getAreaCode(),
templateRichiestaData);
String appointmentRequestBody = Utils.convertObjectToJson(appointmentCreationRequest);
// Make API call to create the appointment
ResponseEntity<Object> appointmentResponse = appointmentApiService.createAppointment(authorizationToken, context, appointmentRequestBody);
String appointmentId = extractAppointmentIdFromResponse(appointmentResponse);
if (appointmentId != null) {
// 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. Regenerating token...");
regenerateTokenAndSave(hub);
return createAppointment(applicationId, createAppointmentRequest);
}
}
private String extractAppointmentIdFromResponse(ResponseEntity<Object> appointmentResponse) {
if (appointmentResponse.getBody() != null) {
Map<String, Object> responseBody = (Map<String, Object>) appointmentResponse.getBody();
if (responseBody.containsKey(GepafinConstant.DATA_STRING)) {
Map<String, Object> data = (Map<String, Object>) responseBody.get(GepafinConstant.DATA_STRING);
if (data != null && data.containsKey(GepafinConstant.ID_STRING)) {
return data.get(GepafinConstant.ID_STRING).toString();
}
}
}
return null;
}
public AppointmentCreationRequest parseTemplateResponseData(String jsonResponse) {
try {
ObjectMapper objectMapper = new ObjectMapper();
JsonNode rootNode = objectMapper.readTree(jsonResponse);
JsonNode richiestaClienteArray = rootNode.path(GepafinConstant.DATA_STRING).path(GepafinConstant.RICHIESTA_CLIENTE_STRING);
AppointmentCreationRequest appointmentCreationRequest = new AppointmentCreationRequest();
AppointmentCreationRequest.Input input = new AppointmentCreationRequest.Input();
// Map `richiestaCliente` array
List<AppointmentCreationRequest.RichiestaCliente> richiestaClienteList = new ArrayList<>();
if (richiestaClienteArray.isArray()) {
for (JsonNode richiestaNode : richiestaClienteArray) {
richiestaClienteList.add(objectMapper.treeToValue(richiestaNode, AppointmentCreationRequest.RichiestaCliente.class));
}
}
input.setRichiestaCliente(richiestaClienteList);
appointmentCreationRequest.setInput(input);
return appointmentCreationRequest;
} catch (Exception e) {
log.error("Error parsing template response: {}", e.getMessage(), e);
throw new IllegalStateException("Failed to parse template response", e);
}
}
public AppointmentCreationRequest buildAppointmentCreationRequest(Long applicationId, CreateAppointmentRequest createAppointmentRequest, String 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<AppointmentCreationRequest.RichiestaCliente> 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);
richiestaClienteList.add(richiestaCliente);
}
input.setRichiestaCliente(richiestaClienteList);
appointmentCreationRequest.setInput(input);
return appointmentCreationRequest;
}
public DocumentUploadResponse uploadDocumentToExternalSystem(Long documentId, UploadDocToExternalSystemRequest docToExternalSystemRequest, Long applicationId) {
DocumentUploadResponse response = new DocumentUploadResponse();
DocumentEntity systemDoc = documentDao.validateDocument(documentId);
ApplicationEntity application = getApplicationEntityForDocument(applicationId, systemDoc);
//cloned for old document data
DocumentEntity oldDocumentEntity = Utils.getClonedEntityForData(systemDoc);
if (!docToExternalSystemRequest.getInput().getAttributes().getNdg().equalsIgnoreCase(application.getNdg())) {
throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.NDG_NOT_MATCHED_OR_NOT_FOUND));
}
Long hubId = application.getHubId();
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();
log.info("Processing {}", oldUrl);
String authorizationToken = getBearerToken(hub);
try {
File localFile = downloadFileFromS3(oldUrl);
MultipartFile multipartFile = convertFileToMultipartFile(localFile);
UploadDocToExternalSystemRequest externalSystemRequest = new UploadDocToExternalSystemRequest();
UploadDocToExternalSystemRequest.Input input = getUploadDocumentInput(docToExternalSystemRequest);
externalSystemRequest.setInput(input);
String uploadDocRequest = Utils.convertObjectToJson(externalSystemRequest);
ResponseEntity<Object> uploadedDocumentData = appointmentApiService.uploadDocumentToExternalSystemForAppointment(authorizationToken, context, uploadDocRequest,
multipartFile);
String responseData = Utils.convertObjectToJson(uploadedDocumentData.getBody());
DocumentUploadResponse parsedDocumentUploadResponse = parseDocumentUploadResponse(responseData);
if (parsedDocumentUploadResponse == null) {
throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.ERROR_UPLOADING_DOCUMENT));
}
systemDoc.setDocumentAttachmentId(parsedDocumentUploadResponse.getDocumentAttachmentId());
documentRepository.save(systemDoc);
/** This code is responsible for adding a version history log for the "Update document with document attachment id" operation. **/
loggingUtil.addVersionHistory(
VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldDocumentEntity).newData(systemDoc).build());
log.info("Document uploaded successfully to external system : {}", parsedDocumentUploadResponse);
response.setDocumentAttachmentId(systemDoc.getDocumentAttachmentId());
return response;
} catch (FeignException.Forbidden forbiddenException) {
log.error("403 Forbidden received while uploading document to external system. Regenerating token...");
regenerateTokenAndSave(hub);
return uploadDocumentToExternalSystem(systemDoc.getSourceId(), docToExternalSystemRequest, applicationId);
} catch (Exception e) {
log.error("Exception in uploading document to external system {}", e.getMessage());
throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.EXTERNAL_DOCUMENT_UPLOAD_FAILURE_MSG));
}
}
private ApplicationEntity getApplicationEntityForDocument(Long applicationId, DocumentEntity systemDoc) {
if (systemDoc.getDocumentAttachmentId() != null) {
throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.DOCUMENT_ALREADY_UPLOADED));
}
ApplicationEntity application;
if (systemDoc.getSource().equalsIgnoreCase(DocumentSourceTypeEnum.APPLICATION.getValue()) && Objects.equals(systemDoc.getSourceId(), applicationId)) {
application = applicationService.validateApplication(systemDoc.getSourceId());
} else {
throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.PROVIDE_VALID_APPLICATION_DOC_ID));
}
return application;
}
private UploadDocToExternalSystemRequest.Input getUploadDocumentInput(UploadDocToExternalSystemRequest docToExternalSystemRequest) {
UploadDocToExternalSystemRequest.Input input = new UploadDocToExternalSystemRequest.Input();
input.setIdTipoProtocollo(docToExternalSystemRequest.getInput().getIdTipoProtocollo());
input.setIdClassificazione(docToExternalSystemRequest.getInput().getIdClassificazione());
input.setFlagDaFirmare(flagDaFirmare);
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 = extractS3KeyFromUrl(fileUrl);
File localFile = new File(GepafinConstant.TEMP_FILE_PATH + extractFileName(key));
GetObjectRequest getObjectRequest = new GetObjectRequest(OLD_BUCKET, key);
try (InputStream s3Stream = s3Client.getObject(getObjectRequest).getObjectContent(); FileOutputStream outputStream = new FileOutputStream(localFile)) {
s3Stream.transferTo(outputStream);
}
log.info("Downloaded file from old S3 bucket: {}", key);
return localFile;
}
private String extractS3KeyFromUrl(String url) {
return url.replace(s3Url, "");
}
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);
}
}
}

View File

@@ -42,4 +42,17 @@ public class ApplicationEntity extends BaseEntity {
@ManyToOne
@JoinColumn(name = "USER_WITH_COMPANY_ID")
private UserWithCompanyEntity userWithCompany;
@Column(name = "NDG")
private String ndg;
@Column(name = "ID_VISURA")
private String idVisura;
@Column(name = "NDG_STATUS")
private String ndgStatus;
@Column(name = "APPOINTMENT_ID")
private String appointmentId;
}

View File

@@ -63,4 +63,6 @@ public class CompanyEntity extends BaseEntity{
@JoinColumn(name = "HUB_ID")
private HubEntity hub;
@Column(name = "NDG")
private String ndg;
}

View File

@@ -29,4 +29,7 @@ public class DocumentEntity extends BaseEntity{
@Column(name ="IS_DELETED", nullable = false)
private Boolean isDeleted = false;
@Column(name="DOCUMENT_ATTACHMENT_ID")
private String documentAttachmentId;
}

View File

@@ -54,4 +54,13 @@ public class HubEntity extends BaseEntity{
@Column(name = "EMAIL_SERVICE_CONFIG")
private String emailServiceConfig;
@Column(name = "AUTH_TOKEN")
private String authToken;
@Column(name = "APPOINTMENT_AUTH_TOKEN_ID")
private String appointmentAuthTokenId;
@Column(name = "AREA_CODE")
private String areaCode;
}

View File

@@ -12,7 +12,10 @@ public enum ApplicationStatusTypeEnum {
SOCCORSO("SOCCORSO"),
APPROVED("APPROVED"),
REJECTED("REJECTED"),
EVALUATION("EVALUATION");
EVALUATION("EVALUATION"),
APPOINTMENT("APPOINTMENT"),
NDG("NDG"),
ADMISSIBLE("ADMISSIBLE");
private String value;

View File

@@ -151,7 +151,12 @@ public enum UserActionContextEnum {
/** scheduler action context **/
AMENDMENT_EXPIRATION_SCHEDULER("AMENDMENT_EXPIRATION_SCHEDULER"),
EVALUATION_EXPIRATION_SCHEDULER("EVALUATION_EXPIRATION_SCHEDULER");
EVALUATION_EXPIRATION_SCHEDULER("EVALUATION_EXPIRATION_SCHEDULER"),
/** appointment action context **/
CHECK_OR_CREATE_NDG_CODE("CHECK_OR_CREATE_NDG_CODE"),
CREATE_APPOINTMENT("CREATE_APPOINTMENT"),
UPLOAD_DOCUMENT_TO_EXTERNAL_SYSTEM("UPLOAD_DOCUMENT_TO_EXTERNAL_SYSTEM");
private final String value;

View File

@@ -0,0 +1,42 @@
package net.gepafin.tendermanagement.model.request;
import lombok.Data;
import java.util.List;
@Data
public class AppointmentCreationRequest {
private Input input;
@Data
public static class Input {
private String id;
private String ndg;
private List<RichiestaCliente> richiestaCliente;
}
@Data
public static class RichiestaCliente {
private String codAbi;
private String codCab;
private Integer durataMesiFinanziamento;
private Integer idMotivazione;
private String idNota;
private String importoAgevolato;
private Double importoBreveTermine;
private String importoMedioLungoTermine;
private String codTipoProdotto;
private String codCategoriaProdotto;
private String codFormaTecnica;
private String codProdotto;
private String codOperazione;
private Nota nota;
}
@Data
public static class Nota {
private String titolo;
private String testo;
}
}

View File

@@ -0,0 +1,20 @@
package net.gepafin.tendermanagement.model.request;
import lombok.Data;
@Data
public class AppointmentNdgRequest {
private Filter filter;
private Pagination pagination;
@Data
public static class Filter {
private String partitaIva;
}
@Data
public static class Pagination {
private int targetPage;
private int recordsPerPage;
}
}

View File

@@ -0,0 +1,14 @@
package net.gepafin.tendermanagement.model.request;
import lombok.Data;
@Data
public class AppointmentVisuraListRequest {
private AppointmentVisuraListRequest.VisuraFilter filter;
@Data
public static class VisuraFilter {
private String idVisura;
}
}

View File

@@ -0,0 +1,28 @@
package net.gepafin.tendermanagement.model.request;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import lombok.Setter;
@Data
public class AppointmentVisuraRequest {
private VisuraInput input;
@Data
public static class VisuraInput {
private String codiceFiscale;
private String partitaIva;
private boolean creaAnagrafica;
private boolean salvaDocumenti;
private String visuraProvider;
private String visuraType;
private String visuraMode;
private String codArea;
private String codAgente;
@JsonProperty("isFromRating")
private boolean isFromRating;
@JsonProperty("isAnagraficaLegame")
private boolean isAnagraficaLegame;
}
}

View File

@@ -0,0 +1,17 @@
package net.gepafin.tendermanagement.model.request;
import lombok.Data;
@Data
public class CreateAppointmentRequest {
private Double importoBreveTermine;
private Integer durataMesiFinanziamento;
private Nota nota;
@Data
public static class Nota {
private String titolo;
private String testo;
}
}

View File

@@ -0,0 +1,23 @@
package net.gepafin.tendermanagement.model.request;
import lombok.Data;
@Data
public class UploadDocToExternalSystemRequest {
private Input input;
@Data
public static class Input {
private Long idTipoProtocollo;
private Long idClassificazione;
private Boolean flagDaFirmare;
private String descrizione;
private Attributes attributes;
@Data
public static class Attributes {
private String ndg;
private String email;
}
}
}

View File

@@ -0,0 +1,8 @@
package net.gepafin.tendermanagement.model.response;
import lombok.Data;
@Data
public class AppointmentCreationResponse {
private String appointmentId;
}

View File

@@ -0,0 +1,16 @@
package net.gepafin.tendermanagement.model.response;
import lombok.Data;
@Data
public class AppointmentLoginResponse {
private String tokenId;
private String areaCode;
private Long companyId;
private String codecFiscale;
private String vatNumber;
private String ndg;
private String message;
private String idVisura;
}

View File

@@ -0,0 +1,8 @@
package net.gepafin.tendermanagement.model.response;
import lombok.Data;
@Data
public class DocumentUploadResponse {
private String documentAttachmentId;
}

View File

@@ -0,0 +1,8 @@
package net.gepafin.tendermanagement.model.response;
import lombok.Data;
@Data
public class NdgResponse {
private String ndg;
}

View File

@@ -1,10 +1,10 @@
package net.gepafin.tendermanagement.repositories;
import net.gepafin.tendermanagement.entities.HubEntity;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
@Repository
@@ -12,4 +12,6 @@ public interface HubRepository extends JpaRepository<HubEntity, Long> {
Optional<HubEntity> findByUniqueUuid(String hubUuid);
@Query("SELECT h FROM HubEntity h WHERE h.id = :hubId")
HubEntity findByHubId(@Param("hubId") Long hubId);
}

View File

@@ -0,0 +1,16 @@
package net.gepafin.tendermanagement.service;
import jakarta.servlet.http.HttpServletRequest;
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;
public interface AppointmentService {
NdgResponse checkNdgForAppointment(HttpServletRequest request, Long applicationId);
AppointmentCreationResponse createAppointmentForApplication(HttpServletRequest request, Long applicationId, CreateAppointmentRequest createAppointmentRequest);
DocumentUploadResponse uploadDocToExternalSystem(HttpServletRequest request, Long documentId, UploadDocToExternalSystemRequest docToExternalSystemRequest, Long applicationId);
}

View File

@@ -0,0 +1,44 @@
package net.gepafin.tendermanagement.service.feignClient;
import net.gepafin.tendermanagement.constants.AppointmentApiConstant;
import net.gepafin.tendermanagement.model.request.AppointmentNdgRequest;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.multipart.MultipartFile;
import java.util.Map;
@FeignClient(value = "appointment-api-service", url = "${appointment.base.url}")
public interface AppointmentApiService {
@PostMapping(value = AppointmentApiConstant.ODESSA_LOGIN, consumes = MediaType.APPLICATION_JSON_VALUE)
ResponseEntity<Object> loginWithOdessa(@RequestHeader("auth") String authToken, @RequestHeader("source") String source, @RequestHeader("context") String context,
@RequestHeader("user") String user, @RequestHeader("password") String password, @RequestBody(required = false) Map<String, Object> body);
@PostMapping(value = AppointmentApiConstant.GET_NDG_BY_VAT_NUMBER, consumes = MediaType.APPLICATION_JSON_VALUE)
ResponseEntity<Object> getNdgByVatNumber(@RequestBody AppointmentNdgRequest ndgRequest, @RequestHeader("Authorization") String token);
@PostMapping(value = AppointmentApiConstant.CREATE_VISURA, consumes = MediaType.APPLICATION_JSON_VALUE)
ResponseEntity<Object> createVisura(@RequestBody String visuraRequest, @RequestHeader("Authorization") String token);
@GetMapping(value = AppointmentApiConstant.GET_VISURA_LIST, consumes = MediaType.APPLICATION_JSON_VALUE)
ResponseEntity<Object> getVisuraList(@RequestBody String visuraRequest, @RequestHeader("Authorization") String token);
@GetMapping(value = AppointmentApiConstant.GET_APPOINTMENT_TEMPLATE, consumes = MediaType.APPLICATION_JSON_VALUE)
ResponseEntity<Object> getAppointmentTemplateForTemplateCreation(@RequestHeader("Authorization") String token);
@PostMapping(value = AppointmentApiConstant.CREATE_APPOINTMENT_FROM_TEMPLATE, consumes = MediaType.APPLICATION_JSON_VALUE)
ResponseEntity<Object> createAppointment(@RequestHeader("Authorization") String token, @RequestHeader("context") String context, String appointmentCreationRequest);
@PostMapping(value = AppointmentApiConstant.UPLOAD_APOOINTMENT_DOCUMENT, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
ResponseEntity<Object> uploadDocumentToExternalSystemForAppointment(@RequestHeader("Authorization") String token, @RequestHeader("context") String context,
@RequestPart("input") String uploadDocumentRequest, @RequestPart("file") MultipartFile file);
}

View File

@@ -0,0 +1,38 @@
package net.gepafin.tendermanagement.service.impl;
import jakarta.servlet.http.HttpServletRequest;
import net.gepafin.tendermanagement.dao.AppointmentDao;
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 org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class AppointmentServiceImpl implements AppointmentService {
@Autowired
private AppointmentDao appointmentDao;
@Override
public NdgResponse checkNdgForAppointment(HttpServletRequest request, Long applicationId) {
return appointmentDao.checkNdgForAppointment(applicationId);
}
@Override
public AppointmentCreationResponse createAppointmentForApplication(HttpServletRequest request, Long applicationId, CreateAppointmentRequest createAppointmentRequest) {
return appointmentDao.createAppointment(applicationId, createAppointmentRequest);
}
@Override
public DocumentUploadResponse uploadDocToExternalSystem(HttpServletRequest request, Long documentId, UploadDocToExternalSystemRequest docToExternalSystemRequest,
Long applicationId) {
return appointmentDao.uploadDocumentToExternalSystem(documentId, docToExternalSystemRequest, applicationId);
}
}

View File

@@ -51,6 +51,7 @@ import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.crypto.Cipher;
import javax.crypto.Mac;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
@@ -580,4 +581,35 @@ public class Utils {
// Clear the RequestContextHolder after task execution
RequestContextHolder.resetRequestAttributes();
}
public static String generateAuthTokenForLoginToOdessa() {
try {
// Your weak secret key
String secretKey = GepafinConstant.AUTH_JWT_SECRET_KEY;
// Header
String header = GepafinConstant.JWT_ALGO_HEADER;
String encodedHeader = Base64.getUrlEncoder().withoutPadding().encodeToString(header.getBytes(StandardCharsets.UTF_8));
// Payload
String payload = "{\"iat\":" + (System.currentTimeMillis() / 1000) + "}";
String encodedPayload = Base64.getUrlEncoder().withoutPadding().encodeToString(payload.getBytes(StandardCharsets.UTF_8));
// Combine header and payload
String dataToSign = encodedHeader + "." + encodedPayload;
// Sign the token manually
Mac mac = Mac.getInstance(GepafinConstant.HMAC_ALGO);
SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getBytes(StandardCharsets.UTF_8), GepafinConstant.HMAC_ALGO);
mac.init(secretKeySpec);
byte[] signatureBytes = mac.doFinal(dataToSign.getBytes(StandardCharsets.UTF_8));
String signature = Base64.getUrlEncoder().withoutPadding().encodeToString(signatureBytes);
// Return the final JWT
return dataToSign + "." + signature;
} catch (Exception e) {
throw new RuntimeException("Failed to generate JWT token", e);
}
}
}

View File

@@ -0,0 +1,60 @@
package net.gepafin.tendermanagement.web.rest.api;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.ExampleObject;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import jakarta.servlet.http.HttpServletRequest;
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.model.util.Response;
import net.gepafin.tendermanagement.web.rest.api.errors.ErrorConstants;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
public interface AppointmentApi {
@Operation(summary = "API to check or create ndg.", 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 = "/application/{applicationId}/check-ndg", produces = MediaType.APPLICATION_JSON_VALUE)
ResponseEntity<Response<NdgResponse>> checkNdgForAppointment(HttpServletRequest request,
@Parameter(description = "The application id", required = true) @PathVariable(value = "applicationId", required = true) Long applicationId);
@Operation(summary = "API to create appointment.", responses = { @ApiResponse(responseCode = "200", description = "OK"),
@ApiResponse(responseCode = "404", description = "Not Found", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, examples = {
@ExampleObject(value = ErrorConstants.NOTFOUND_ERROR_EXAMPLE) })),
@ApiResponse(responseCode = "401", description = "Unauthorized", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, examples = {
@ExampleObject(value = ErrorConstants.UNAUTHORIZED_ERROR_EXAMPLE) })),
@ApiResponse(responseCode = "400", description = "Bad Request", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, examples = {
@ExampleObject(value = ErrorConstants.BADREQUEST_ERROR_EXAMPLE) })) })
@PostMapping(value = "/application/{applicationId}", produces = MediaType.APPLICATION_JSON_VALUE)
ResponseEntity<Response<AppointmentCreationResponse>> createAppointment(HttpServletRequest request,
@Parameter(description = "The application id", required = true) @PathVariable(value = "applicationId", required = true) Long applicationId,
@RequestBody CreateAppointmentRequest createAppointmentRequest);
@Operation(summary = "API to Upload document to external system.", responses = { @ApiResponse(responseCode = "200", description = "OK"),
@ApiResponse(responseCode = "404", description = "Not Found", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, examples = {
@ExampleObject(value = ErrorConstants.NOTFOUND_ERROR_EXAMPLE) })),
@ApiResponse(responseCode = "401", description = "Unauthorized", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, examples = {
@ExampleObject(value = ErrorConstants.UNAUTHORIZED_ERROR_EXAMPLE) })),
@ApiResponse(responseCode = "400", description = "Bad Request", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, examples = {
@ExampleObject(value = ErrorConstants.BADREQUEST_ERROR_EXAMPLE) })) })
@PostMapping(value = "/application/{applicationId}/document/{documentId}", produces = MediaType.APPLICATION_JSON_VALUE)
ResponseEntity<Response<DocumentUploadResponse>> uploadDocumentToExternalSystem(HttpServletRequest request,
@Parameter(description = "The document id", required = true) @PathVariable(value = "documentId", required = true) Long documentId,
@Parameter(description = "The application id", required = true) @PathVariable(value = "applicationId", required = true) Long applicationId,
@RequestBody UploadDocToExternalSystemRequest docToExternalSystemRequest);
}

View File

@@ -0,0 +1,76 @@
package net.gepafin.tendermanagement.web.rest.api.impl;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import net.gepafin.tendermanagement.config.Translator;
import net.gepafin.tendermanagement.constants.GepafinConstant;
import net.gepafin.tendermanagement.enums.UserActionContextEnum;
import net.gepafin.tendermanagement.enums.UserActionLogsEnum;
import net.gepafin.tendermanagement.model.request.CreateAppointmentRequest;
import net.gepafin.tendermanagement.model.request.UploadDocToExternalSystemRequest;
import net.gepafin.tendermanagement.model.request.UserActionRequest;
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.model.util.Response;
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.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("${openapi.gepafin.base-path:/v1/appointment}")
@Slf4j
public class AppointmentController implements AppointmentApi {
@Autowired
private AppointmentService appointmentService;
@Autowired
private LoggingUtil loggingUtil;
@Override
public ResponseEntity<Response<NdgResponse>> checkNdgForAppointment(HttpServletRequest request, Long applicationId) {
/** This code is responsible for creating user action logs for the "checking or creating ndg" operation. **/
loggingUtil.logUserAction(
UserActionRequest.builder().request(request).actionType(UserActionLogsEnum.INSERT).actionContext(UserActionContextEnum.CHECK_OR_CREATE_NDG_CODE).build());
NdgResponse appointmentLoginResponse = appointmentService.checkNdgForAppointment(request, applicationId);
return ResponseEntity.status(HttpStatus.OK).body(new Response<>(appointmentLoginResponse, Status.SUCCESS, Translator.toLocale(GepafinConstant.NDG_FETCH_SUCCESSFULLY)));
}
@Override
public ResponseEntity<Response<AppointmentCreationResponse>> createAppointment(HttpServletRequest request, Long applicationId,
CreateAppointmentRequest createAppointmentRequest) {
/** This code is responsible for creating user action logs for the "create appointment" operation. **/
loggingUtil.logUserAction(
UserActionRequest.builder().request(request).actionType(UserActionLogsEnum.INSERT).actionContext(UserActionContextEnum.CREATE_APPOINTMENT).build());
AppointmentCreationResponse appointmentCreationResponse = appointmentService.createAppointmentForApplication(request, applicationId, createAppointmentRequest);
return ResponseEntity.status(HttpStatus.CREATED)
.body(new Response<>(appointmentCreationResponse, Status.SUCCESS, Translator.toLocale(GepafinConstant.APPOINTMENT_CREATED)));
}
@Override
public ResponseEntity<Response<DocumentUploadResponse>> uploadDocumentToExternalSystem(HttpServletRequest request, Long documentId, Long applicationId,
UploadDocToExternalSystemRequest docToExternalSystemRequest) {
/** This code is responsible for creating user action logs for the "Upload document to external system" operation. **/
loggingUtil.logUserAction(
UserActionRequest.builder().request(request).actionType(UserActionLogsEnum.UPLOAD).actionContext(UserActionContextEnum.UPLOAD_DOCUMENT_TO_EXTERNAL_SYSTEM).build());
DocumentUploadResponse documentUploadResponse = appointmentService.uploadDocToExternalSystem(request, documentId, docToExternalSystemRequest, applicationId);
return ResponseEntity.status(HttpStatus.OK)
.body(new Response<>(documentUploadResponse, Status.SUCCESS, Translator.toLocale(GepafinConstant.DOCUMENT_UPLOADED_SUCCESSFULLY_TO_EXTERNAL_SYSTEM)));
}
}

View File

@@ -15,3 +15,11 @@ gepafin_email=rinaldo.bonazzo@bflows.net
rinaldo_email=rinaldo.bonazzo@bflows.net
carlo_email=test@test.test
default.hub.uuid=p4lk3bcx1RStqTaIVVbXs
#Login to Odessa, Appointment Creation, Upload document Configuration
appointment.base.url=https://demo.galileonetwork.it/gateway/rest
appointment.portal.user=UtenzaAPIPortal@621
appointment.portal.password=u13nzaAP1P0rtal
appointment.portal.source=GEPAFINPORTAL
appointment.portal.context=GEPAFINPORTAL
flagDaFirmare=false

View File

@@ -14,3 +14,10 @@ gepafin_email=test@test.test
rinaldo_email=test@test.test
carlo_email=test@test.test
default.hub.uuid=p4lk3bcx1RStqTaIVVbXs
appointment.base.url=https://demo.galileonetwork.it/gateway/rest
appointment.portal.user=UtenzaAPIPortal@621
appointment.portal.password=u13nzaAP1P0rtal
appointment.portal.source=GEPAFINPORTAL
appointment.portal.context=GEPAFINPORTAL
flagDaFirmare=false

View File

@@ -22,3 +22,11 @@ rinaldo_email=rinaldo.bonazzo@bflows.net
carlo_email=carlo.mancosu@bflows.net
default.hub.uuid=p4lk3bcx1RStqTaIVVbXs
# TEST DEPLOY Configuration
#Login to Odessa, Appointment Creation, Upload document Configuration
appointment.base.url=https://demo.galileonetwork.it/gateway/rest
appointment.portal.user=UtenzaAPIPortal@621
appointment.portal.password=u13nzaAP1P0rtal
appointment.portal.source=GEPAFINPORTAL
appointment.portal.context=GEPAFINPORTAL
flagDaFirmare=true

View File

@@ -65,3 +65,6 @@ default.email.signature=Gepafin S.p.a
default.hub.pdf.banner=https://mementoresources.s3.amazonaws.com/gepafin/staging/template/gepafin-logo.jpg
#feign client config
spring.cloud.openfeign.client.config.default.connectTimeout=300000
spring.cloud.openfeign.client.config.default.readTimeout=300000

View File

@@ -1972,5 +1972,24 @@
path="db/dump/update_system_email_template_for_updating_amendment_mail_notification_mail_04_12_2024_1.sql"/>
</changeSet>
<changeSet id="04-12-2024_3" author="Piyush">
<addColumn tableName="hub">
<column name="auth_token" type="TEXT"/>
<column name="appointment_auth_token_id" type="TEXT"/>
<column name="area_code" type="TEXT"/>
</addColumn>
<addColumn tableName="company">
<column name="ndg" type="TEXT"/>
</addColumn>
<addColumn tableName="application">
<column name="ndg" type="TEXT"/>
<column name="id_visura" type="TEXT"/>
<column name="ndg_status" type="TEXT"/>
<column name="appointment_id" type="TEXT"/>
</addColumn>
<addColumn tableName="document">
<column name="document_attachment_id" type="TEXT"/>
</addColumn>
</changeSet>
</databaseChangeLog>

View File

@@ -315,3 +315,22 @@ response.days.not.null=Response days should not be null and greater than zero.
application.cannot.approved.or.rejected=Application cannot be approved and rejected because amendment is active.
atleast.one.id.required=At least one of companyId or applicationId must be provided
#Appointment flow messages
ndg.generated = NDG Generated.
ndg.available = NDG Available.
ndg.generation.in.progress = NDG generation is in progress.
ndg.fetch.successfully = NDG fetch successfully.
appointment.already.created = Appointment Already Created.
ndg.not.found.for.this.application.or.invalid = Ndg not found for this application or invalid.
provide.valid.application.document.id = Provide valid application document id.
document.uploaded.successfully.to.external.system = Document uploaded successfully to external system.
error.in.uploading.document.check.input = Error in uploading document check input data or try again.
document.already.uploaded = Document already uploaded.
document.not.uploaded.to.external.system.please.try.again = Document not uploaded to external system, please try again.
ndg.not.found.or.not.matched = The provided NDG does not match the application NDG, or the NDG has not been generated.
ndg.generation.is.only.for.gepafin = NDG generation is only available for GEPAFIN Hub.
appointment.creation.is.only.for.gepafin = Appointment creation is only allowed for GEPAFIN Hub.
upload.document.is.only.for.gepafin = Document cant be uploaded, this is only available for GEPAFIN Hub.
appointment.created.successfully = Appointment created successfully.
error.try.again = Service call error while performing the operation. Please try again.

View File

@@ -197,8 +197,6 @@ invalid.vatnumber=Numero di partita IVA non valido.
vatnumber.mandatory=Il numero di partita IVA � obbligatorio.
vatnumber.already.exists=Il numero di partita IVA esiste gi�.
invalid.email=Email non valida.
company.id.mandatory=L'ID dell'azienda � obbligatorio.
user.already.connected.to.company=L'utente � gi� collegato a questa azienda.
validation.error.missing.firstName=Il nome � obbligatorio.
validation.error.missing.lastName=Il cognome � obbligatorio.
validation.error.missing.codiceFiscale=Il Codice Fiscale � obbligatorio.
@@ -308,3 +306,21 @@ company.id.required.for.preferred.call=ID azienda obbligatorio quando si richied
response.days.not.null=I giorni di risposta non devono essere nulli e maggiori di zero.
application.cannot.approved.or.rejected=La domanda non può essere approvata o rifiutata perché l'emendamento è attivo.
atleast.one.id.required=Almeno uno tra companyId o applicationId deve essere fornito.
#Appointment flow messages
ndg.available = NDG disponibile.
ndg.generation.in.progress = La generazione NDG ? in corso.
ndg.fetch.successfully = Recupero NDG riuscito.
appointment.already.created = Appuntamento gi? creato.
ndg.not.found.for.this.application.or.invalid = NDG non trovato per questa applicazione o non valido.
provide.valid.application.document.id = Fornisci un ID documento applicativo valido.
document.uploaded.successfully.to.external.system = Documento caricato con successo nel sistema esterno.
error.in.uploading.document.check.input = Errore nel caricamento del documento. Controlla i dati inseriti o riprova.
document.already.uploaded = Documento gi? caricato.
document.not.uploaded.to.external.system.please.try.again = Documento non caricato nel sistema esterno, riprova.
ndg.not.found.or.not.matched = L'NDG fornito non corrisponde all'NDG dell'applicazione o non ? stato generato.
ndg.generation.is.only.for.gepafin = La generazione dell'NDG ? disponibile solo per GEPAFIN.
appointment.creation.is.only.for.gepafin = La creazione degli appuntamenti ? consentita solo per GEPAFIN.
upload.document.is.only.for.gepafin = Il documento non pu? essere caricato, questa operazione ? disponibile solo per il Hub GEPAFIN.
appointment.created.successfully = Appuntamento creato con successo.
error.try.again = Errore di chiamata di servizio durante l'esecuzione dell'operazione. Riprovare.