diff --git a/pom.xml b/pom.xml index 93a592ed..c463be91 100644 --- a/pom.xml +++ b/pom.xml @@ -231,6 +231,20 @@ spring-test + + + org.springframework.boot + spring-boot-starter-websocket + + + org.springframework.boot + spring-boot-starter-amqp + + + io.projectreactor.netty + reactor-netty + + diff --git a/src/main/java/net/gepafin/tendermanagement/TendermanagementApplication.java b/src/main/java/net/gepafin/tendermanagement/TendermanagementApplication.java index 7ec98464..c220b176 100644 --- a/src/main/java/net/gepafin/tendermanagement/TendermanagementApplication.java +++ b/src/main/java/net/gepafin/tendermanagement/TendermanagementApplication.java @@ -23,8 +23,7 @@ public class TendermanagementApplication { @Override public void addCorsMappings(CorsRegistry registry) { - registry.addMapping("/**").allowedOrigins("http://localhost:3000") - .allowedMethods("GET", "POST", "PUT", "DELETE", "HEAD").allowCredentials(true); + registry.addMapping("/**").allowedOrigins("http://localhost:3000").allowedMethods("GET", "POST", "PUT", "DELETE", "HEAD").allowCredentials(true); } } diff --git a/src/main/java/net/gepafin/tendermanagement/config/SecurityConfig.java b/src/main/java/net/gepafin/tendermanagement/config/SecurityConfig.java index 090f3688..8c11eac2 100644 --- a/src/main/java/net/gepafin/tendermanagement/config/SecurityConfig.java +++ b/src/main/java/net/gepafin/tendermanagement/config/SecurityConfig.java @@ -109,6 +109,7 @@ public class SecurityConfig { .requestMatchers("/v1/api-docs/**").permitAll() // API docs .requestMatchers("/v1/user/reset-password/initiate").permitAll() .requestMatchers("/v1/user/reset-password").permitAll() + .requestMatchers("/wss/**").permitAll() // if this is not running use this /gs-guide-websocket .anyRequest().authenticated()) .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)) .exceptionHandling(exceptionHandling -> exceptionHandling diff --git a/src/main/java/net/gepafin/tendermanagement/config/WebSocketConfig.java b/src/main/java/net/gepafin/tendermanagement/config/WebSocketConfig.java new file mode 100644 index 00000000..1b90a882 --- /dev/null +++ b/src/main/java/net/gepafin/tendermanagement/config/WebSocketConfig.java @@ -0,0 +1,39 @@ +package net.gepafin.tendermanagement.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; +import org.springframework.messaging.simp.config.MessageBrokerRegistry; +import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; +import org.springframework.web.socket.config.annotation.StompEndpointRegistry; +import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer; + +@Configuration +@EnableWebSocketMessageBroker +public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { + + @Value("${spring.rabbitmq.host}") + private String relayHost; + + @Value("${spring.rabbitmq.port}") + private int relayPort; + + @Value("${spring.rabbitmq.username}") + private String clientUserName; + + @Value("${spring.rabbitmq.password}") + private String clientPassword; + + @Override + public void configureMessageBroker(MessageBrokerRegistry config) { + + config.enableStompBrokerRelay("/topic").setRelayHost(relayHost).setRelayPort(relayPort).setClientLogin(clientUserName).setClientPasscode(clientPassword); + config.setApplicationDestinationPrefixes("/app"); + } + + @Override + public void registerStompEndpoints(StompEndpointRegistry registry) { + + registry.addEndpoint("/wss").setAllowedOrigins("http://localhost:3000", "http://127.0.0.1:5500/", "https://bandi-staging.memento.credit/**", "https://bandi.gepafin.it/**") + .withSockJS(); + } +} diff --git a/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java b/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java index 42a9cd41..bbbeb1eb 100644 --- a/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java +++ b/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java @@ -342,5 +342,16 @@ public class GepafinConstant { public static final String DOCUMENT_UPLOADING_IN_PROGRESS = "document.uploading.is.in.progress"; public static final String ASYNC_DOCUMENT_UPLOAD_NAME = "AsyncDocumentUpload-"; public static final String All_DOCUMENT_CHECKED_AND_ONE_CHECKLIST_CHECKED="all.document.checked.and.one.checklist.checked"; + + //Notification + public static final String COMMON_SINGLE_CHANNEL_PREFIX = "/topic/notifications_user_"; + public static final String COMPANY_PREFIX = "_company_"; + public static final String NOTIFICATION_SENT_SUCCESSFULLY = "notification.sent.successfully"; + public static final String NOTIFICATION_FETCHED_SUCCESSFULLY= "notification.fetched.successfully"; + public static final String NOTIFICATION_NOT_FOUND= "notification.not.found"; + public static final String NOTIFICATION_ALREADY_IN_THAT_STATE="notification.already.in.state"; + public static final String NOTIFICATION_DELETED_SUCCESSFULLY="notification.deleted.successfully"; + public static final String NOTIFICATION_UPDATED_SUCCESSFULLY="notification.updated.successfully"; + public static final String USER_WITH_COMPANY_NOT_FOUND = "user.with.company.not.found"; } diff --git a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationAmendmentRequestDao.java b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationAmendmentRequestDao.java index 4e56814a..d9b3ed91 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationAmendmentRequestDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationAmendmentRequestDao.java @@ -110,6 +110,14 @@ public class ApplicationAmendmentRequestDao { @Autowired private DocumentRepository documentRepository; + @Autowired + private CompanyService companyService; + + @Autowired + private NotificationDao notificationDao; + + @Autowired + private UserRepository userRepository; public ApplicationAmendmentRequestResponse getApplicationDataForAmendment(Long applicationEvaluationId) { log.info("Fetching the application data for the Amendment process {}", applicationEvaluationId); @@ -326,6 +334,10 @@ public class ApplicationAmendmentRequestDao { assignedApplicationsEntity.setStatus(AssignedApplicationEnum.SOCCORSO.getValue()); assignedApplicationsRepository.save(assignedApplicationsEntity); + Map placeHolders = notificationDao.sendNotificationToBeneficiary(applicationEntity, NotificationTypeEnum.AMENDMENT_CREATION); + + notificationDao.sendNotificationToInstructor(placeHolders,applicationAmendmentRequestEntity.getApplicationEvaluationEntity(),NotificationTypeEnum.AMENDMENT_CREATION); + /** This code is responsible for adding a version history log for the "Update Assigned Application" operation. **/ loggingUtil.addVersionHistory(VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldAssignedApplication).newData(assignedApplicationsEntity).build()); } @@ -458,7 +470,8 @@ public class ApplicationAmendmentRequestDao { UserEntity userEntity = userService.validateUser(application.getUserId()); response.setBeneficiaryName(buildBeneficiaryName(userEntity)); - + CompanyEntity company = companyService.validateCompany(application.getCompanyId()); + response.setCompanyName(company.getCompanyName()); Long protocolNumber = entity.getProtocol() != null ? entity.getProtocol().getProtocolNumber() : null; response.setProtocolNumber(protocolNumber); @@ -962,8 +975,7 @@ public class ApplicationAmendmentRequestDao { ApplicationAmendmentRequestEntity existingApplicationAmendment = validateApplicationAmendmentRequest(id); //cloned entity for old data and versioning ApplicationAmendmentRequestEntity oldApplicationAmendmentEntity = Utils.getClonedEntityForData(existingApplicationAmendment); - - List amendmentRequestList = applicationAmendmentRequestRepository.findAllByApplicationEvaluationIdAndIsDeletedFalse( + List amendmentRequestList = applicationAmendmentRequestRepository.findAllByApplicationEvaluationIdAndIsDeletedFalse( existingApplicationAmendment.getApplicationEvaluationEntity().getId() ); @@ -1008,6 +1020,11 @@ public class ApplicationAmendmentRequestDao { AssignedApplicationsEntity oldAssignedApplicationData = Utils.getClonedEntityForData(assignedApplicationsEntity); assignedApplicationsEntity = assignedApplicationsRepository.save(existingApplicationAmendment.getApplicationEvaluationEntity().getAssignedApplicationsEntity()); + + Map placeHolders = notificationDao.sendNotificationToBeneficiary(application, NotificationTypeEnum.AMENDMENT_CLOSED); + + notificationDao.sendNotificationToInstructor(placeHolders,existingApplicationAmendment.getApplicationEvaluationEntity(),NotificationTypeEnum.AMENDMENT_CLOSED); + /** This code is responsible for adding a version history log for the "Update Application Evaluation" operation. **/ loggingUtil.addVersionHistory(VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldApplicationEvaluationEntity) .newData(existingApplicationEvaluationEntity).build()); @@ -1027,6 +1044,7 @@ public class ApplicationAmendmentRequestDao { return response; } + public ApplicationAmendmentRequestResponse extendResponseDays(Long id, Long newResponseDays) { ApplicationAmendmentRequestEntity applicationAmendmentRequestEntity = validateApplicationAmendmentRequest(id); diff --git a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java index 45a69384..621fea19 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java @@ -10,6 +10,7 @@ import net.gepafin.tendermanagement.model.request.ApplicationFormFieldRequestBea import net.gepafin.tendermanagement.model.request.ApplicationRequest; import net.gepafin.tendermanagement.model.request.ApplicationRequestBean; import net.gepafin.tendermanagement.model.request.EmailLogRequest; +import net.gepafin.tendermanagement.model.request.NotificationReq; import net.gepafin.tendermanagement.model.request.VersionHistoryRequest; import net.gepafin.tendermanagement.model.response.*; import net.gepafin.tendermanagement.repositories.*; @@ -162,6 +163,15 @@ public class ApplicationDao { @Autowired private ApplicationEvaluationService applicationEvaluationService; + @Autowired + private NotificationDao notificationDao; + + @Autowired + private UserRepository userRepository; + + @Autowired + private RoleRepository roleRepository; + public ApplicationResponseBean createApplication(HttpServletRequest request, ApplicationRequestBean applicationRequestBean, Long formId, Long applicationId) { FormEntity formEntity = formService.validateForm(formId); // callService.validatePublishedCall(formEntity.getCall().getId()); @@ -387,6 +397,16 @@ public class ApplicationDao { responseBean.setStatus(applicationEntity.getStatus()); responseBean.setComments(applicationEntity.getComments()); responseBean.setCompanyId(applicationEntity.getCompanyId()); + Optional assignedApplicationsOptional = + assignedApplicationsRepository.findByApplicationIdAndIsDeletedFalse(applicationEntity.getId()); + if(assignedApplicationsOptional.isPresent()){ + responseBean.setAssignedUserId(assignedApplicationsOptional.get().getUserId()); + UserEntity user = userService.validateUser(assignedApplicationsOptional.get().getUserId()); + String firstName = user.getFirstName() != null ? user.getFirstName() : ""; + String lastName = user.getLastName() != null ? user.getLastName() : ""; + String userName = String.join(" ", firstName, lastName).trim(); + responseBean.setAssignedUserName(userName); + } CompanyEntity company=companyService.validateCompany(applicationEntity.getCompanyId()); responseBean.setCompanyName(company.getCompanyName()); if(applicationEntity.getProtocol() != null) { @@ -841,6 +861,8 @@ public class ApplicationDao { applicationEntity.setStatus(ApplicationStatusTypeEnum.SUBMIT.getValue()); applicationEntity.setSubmissionDate(DateTimeUtil.DateServerToUTC(LocalDateTime.now())); applicationEntity = applicationRepository.save(applicationEntity); + Map placeHolders=notificationDao.sendNotificationToBeneficiary(applicationEntity,NotificationTypeEnum.APPLICATION_SUBMISSION); + notificationDao.sendNotificationToSuperUser(applicationEntity,placeHolders,NotificationTypeEnum.APPLICATION_SUBMISSION); /** This code is responsible for adding a version history log for "Update application status" operation. **/ loggingUtil.addVersionHistory( diff --git a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationEvaluationDao.java b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationEvaluationDao.java index aad85864..5a67b2ae 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationEvaluationDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationEvaluationDao.java @@ -94,6 +94,12 @@ public class ApplicationEvaluationDao { @Autowired private CompanyService companyService; + + @Autowired + private NotificationDao notificationDao; + + @Autowired + private UserRepository userRepository; @Autowired private Validator validator; @@ -558,7 +564,16 @@ public class ApplicationEvaluationDao { response.setAssignedAt(assignedApplications.getAssignedAt()); } response.setEvaluationEndDate(entity.getEndDate()); - + Optional assignedApplicationsOptional = + assignedApplicationsRepository.findByApplicationIdAndIsDeletedFalse(application.getId()); + if(assignedApplicationsOptional.isPresent()){ + response.setAssignedUserId(assignedApplicationsOptional.get().getUserId()); + UserEntity assignedUser = userService.validateUser(assignedApplicationsOptional.get().getUserId()); + String assignedUserFirstName = assignedUser.getFirstName() != null ? assignedUser.getFirstName() : ""; + String assignedUserLastName = assignedUser.getLastName() != null ? assignedUser.getLastName() : ""; + String userName = String.join(" ", assignedUserFirstName, assignedUserLastName).trim(); + response.setAssignedUserName(userName); + } LocalDateTime callEndDate = application.getCall().getEndDate(); response.setCallEndDate(callEndDate); if (application.getCompanyId() != null) { @@ -597,8 +612,14 @@ public class ApplicationEvaluationDao { setIfUpdated(entity::getMotivation, entity::setMotivation, req.getMotivation()); actionType = VersionActionTypeEnum.UPDATE; } else { + ApplicationEntity application=entity.getAssignedApplicationsEntity().getApplication(); entity = convertToEntity(user, req, assignedApplicationId); actionType = VersionActionTypeEnum.INSERT; + + Map placeHolders = notificationDao.sendNotificationToBeneficiary(application, NotificationTypeEnum.EVALUATION_CREATION); + notificationDao.sendNotificationToSuperUser(application,placeHolders,NotificationTypeEnum.EVALUATION_CREATION); + notificationDao.sendNotificationToInstructor(placeHolders,entity,NotificationTypeEnum.EVALUATION_CREATION); + } ApplicationStatusForEvaluation status = req.getApplicationStatus(); // Fetch all amendment request entities associated with the evaluation ID @@ -1797,12 +1818,17 @@ public class ApplicationEvaluationDao { if (Boolean.TRUE.equals(statusType.equals((ApplicationStatusTypeEnum.REJECTED.getValue())))) { emailNotificationDao.sendInadmissibilityEmailForRejectedApplication(application,existingEntity); } + + Map placeHolders = notificationDao.sendNotificationToBeneficiary(application, NotificationTypeEnum.EVALUATION_RESULT); + notificationDao.sendNotificationToSuperUser(application,placeHolders,NotificationTypeEnum.EVALUATION_RESULT); + notificationDao.sendNotificationToInstructor(placeHolders,existingEntity,NotificationTypeEnum.EVALUATION_RESULT); + return convertToResponse(entity); } return null; } - public ApplicationEvaluationEntity validateApplicationEvaluationByApplicationId(Long applicationId) { + public ApplicationEvaluationEntity validateApplicationEvaluationByApplicationId(Long applicationId) { return applicationEvaluationRepository .findByApplicationIdAndIsDeletedFalse(applicationId) .orElseThrow(() -> new ResourceNotFoundException(Status.NOT_FOUND, diff --git a/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java b/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java index f1fb2b8f..a45b142d 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java @@ -16,14 +16,18 @@ 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.entities.UserEntity; import net.gepafin.tendermanagement.enums.ApplicationStatusTypeEnum; import net.gepafin.tendermanagement.enums.DocumentSourceTypeEnum; +import net.gepafin.tendermanagement.enums.NotificationTypeEnum; +import net.gepafin.tendermanagement.enums.RoleStatusEnum; 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.NotificationReq; import net.gepafin.tendermanagement.model.request.UploadDocToExternalSystemRequest; import net.gepafin.tendermanagement.model.request.VersionHistoryRequest; import net.gepafin.tendermanagement.model.response.AppointmentCreationResponse; @@ -34,6 +38,7 @@ 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.ApplicationService; import net.gepafin.tendermanagement.service.CompanyService; import net.gepafin.tendermanagement.service.feignClient.AppointmentApiService; @@ -58,6 +63,7 @@ import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; @@ -130,6 +136,12 @@ public class AppointmentDao { @Autowired private TokenProvider tokenProvider; + @Autowired + private NotificationDao notificationDao; + + @Autowired + private UserRepository userRepository; + private final Map executorMap = new ConcurrentHashMap<>(); private final ConcurrentHashMap threadForDocumentMap = new ConcurrentHashMap<>(); @@ -312,6 +324,8 @@ public class AppointmentDao { company.setNdg(ndg); companyRepository.save(company); applicationRepository.save(application); + Map placeHolders=notificationDao.sendNotificationToBeneficiary(application,NotificationTypeEnum.NDG_GENERATION); + notificationDao.sendNotificationToSuperUser(application,placeHolders,NotificationTypeEnum.NDG_GENERATION); // /** This code is responsible for adding a version history log for the "update application ndg code, status, and Id visura" operation. **/ // loggingUtil.addVersionHistory( diff --git a/src/main/java/net/gepafin/tendermanagement/dao/CallDao.java b/src/main/java/net/gepafin/tendermanagement/dao/CallDao.java index 9b3b12a5..7382f1c6 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/CallDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/CallDao.java @@ -15,7 +15,9 @@ import java.util.zip.ZipOutputStream; import jakarta.servlet.http.HttpServletRequest; import net.gepafin.tendermanagement.entities.*; import net.gepafin.tendermanagement.enums.DocumentSourceTypeEnum; +import net.gepafin.tendermanagement.enums.NotificationTypeEnum; import net.gepafin.tendermanagement.enums.VersionActionTypeEnum; +import net.gepafin.tendermanagement.model.request.NotificationReq; import net.gepafin.tendermanagement.model.request.VersionHistoryRequest; import net.gepafin.tendermanagement.model.response.*; import net.gepafin.tendermanagement.repositories.*; @@ -49,6 +51,7 @@ import net.gepafin.tendermanagement.web.rest.api.errors.Status; import static net.gepafin.tendermanagement.enums.RoleStatusEnum.ROLE_SUPER_ADMIN; import static net.gepafin.tendermanagement.util.Utils.setIfUpdated; +import static org.hibernate.internal.util.collections.CollectionHelper.listOf; @Component public class CallDao { @@ -108,6 +111,15 @@ public class CallDao { @Autowired private HttpServletRequest request; + @Autowired + private NotificationDao notificationDao; + + @Autowired + private BeneficiaryRepository beneficiaryRepository; + + @Autowired + private NotificationTypeRepository notificationTypeRepository; + public CallResponse createCallStep1(CreateCallRequestStep1 createCallRequest, UserEntity userEntity) { createCallRequest.setRegionId(userEntity.getRoleEntity().getRegion().getId()); CallEntity callEntity = convertToCallEntity(createCallRequest, userEntity); @@ -828,10 +840,20 @@ public class CallDao { validateStatusChange(currentStatus, statusReq); callEntity.setStatus(statusReq.getValue()); callEntity = callRepository.save(callEntity); - + + //Creating notification. + List userIds = beneficiaryRepository.findUserIdsByHubIdAndBeneficiaryId(callEntity.getHub().getId()); + Map placeholders = new HashMap<>(); + placeholders.put("{{call_name}}", callEntity.getName()); + userIds.forEach(userId -> { + List companyIds = notificationDao.getAllCompanyIdsForUser(userId); + NotificationReq notificationReq = notificationDao.createNotificationReq(NotificationTypeEnum.CALL_CREATED.getValue(), placeholders, userId, null, companyIds); + notificationDao.sendNotification(notificationReq); + }); + /** This code is responsible for adding a version history log for the "update call status" operation **/ loggingUtil.addVersionHistory(VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldCallEntity).newData(callEntity).build()); - + return convertToCallResponseBean(callEntity); } diff --git a/src/main/java/net/gepafin/tendermanagement/dao/NotificationDao.java b/src/main/java/net/gepafin/tendermanagement/dao/NotificationDao.java new file mode 100644 index 00000000..64db69ca --- /dev/null +++ b/src/main/java/net/gepafin/tendermanagement/dao/NotificationDao.java @@ -0,0 +1,271 @@ +package net.gepafin.tendermanagement.dao; + +import lombok.extern.slf4j.Slf4j; +import net.gepafin.tendermanagement.config.Translator; +import net.gepafin.tendermanagement.constants.GepafinConstant; +import net.gepafin.tendermanagement.entities.ApplicationEntity; +import net.gepafin.tendermanagement.entities.ApplicationEvaluationEntity; +import net.gepafin.tendermanagement.entities.NotificationEntity; +import net.gepafin.tendermanagement.entities.NotificationTypeEntity; +import net.gepafin.tendermanagement.entities.UserEntity; +import net.gepafin.tendermanagement.entities.UserWithCompanyEntity; +import net.gepafin.tendermanagement.enums.NotificationEnum; +import net.gepafin.tendermanagement.enums.NotificationTypeEnum; +import net.gepafin.tendermanagement.enums.RoleStatusEnum; +import net.gepafin.tendermanagement.model.request.NotificationReq; +import net.gepafin.tendermanagement.model.response.NotificationResponse; +import net.gepafin.tendermanagement.repositories.NotificationRepository; +import net.gepafin.tendermanagement.repositories.NotificationTypeRepository; +import net.gepafin.tendermanagement.repositories.UserRepository; +import net.gepafin.tendermanagement.repositories.UserWithCompanyRepository; +import net.gepafin.tendermanagement.service.ApplicationService; +import net.gepafin.tendermanagement.util.DateTimeUtil; +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.factory.annotation.Autowired; +import org.springframework.messaging.simp.SimpMessagingTemplate; +import org.springframework.stereotype.Component; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import static org.hibernate.internal.util.collections.CollectionHelper.listOf; + +@Component +@Slf4j +public class NotificationDao { + + @Autowired + private SimpMessagingTemplate messagingTemplate; + + @Autowired + private NotificationRepository notificationRepository; + + @Autowired + private NotificationTypeRepository notificationTypeRepository; + + @Autowired + private UserWithCompanyRepository userWithCompanyRepository; + + @Autowired + private UserRepository userRepository; + + @Autowired + private CompanyDao companyDao; + + @Autowired + private ApplicationService applicationService; + + @Autowired + private UserDao userDao; + + public NotificationResponse sendNotification(NotificationReq notificationReq) { + + // Ensure userId is properly set in notificationReq if not already + Long userId = notificationReq.getUserId(); + if (userId == null) { + log.error("User ID is missing in the notification request."); + return null; + } + NotificationEntity notificationEntity = saveNotification(notificationReq); + log.info("Sending notification to user {} with content: {}", userId, notificationReq.getMessage()); + List companyIds = notificationReq.getCompanyIds(); + + if (companyIds == null || companyIds.isEmpty()) { + sendToUser(userId, notificationEntity); + } else { + sendToCompanies(userId, companyIds, notificationEntity); + } + + return convertNotificationEntityToNotificationResponse(notificationEntity); + } + + private NotificationEntity saveNotification(NotificationReq notificationReq) { + + return notificationRepository.save(convertNotificationRequestToNotificationEntity(notificationReq)); + } + + private void sendToUser(Long userId, NotificationEntity notificationEntity) { + + String userChannel = GepafinConstant.COMMON_SINGLE_CHANNEL_PREFIX + userId; + log.info("Channel for User {}", userChannel); + NotificationResponse notificationResponse = convertNotificationEntityToNotificationResponse(notificationEntity); + messagingTemplate.convertAndSend(userChannel, notificationResponse); + } + + private void sendToCompanies(Long userId, List companyIds, NotificationEntity notificationEntity) { + // Send notification to each company provided in the companyIds list + companyIds.forEach(companyId -> { + UserWithCompanyEntity userWithCompany = userWithCompanyRepository.findByUserIdAndCompanyIdAndIsDeletedFalseForNotification(userId, companyId); + String companyChannel = Utils.createChannelForUserAndCompany(userId, companyId); + log.info("Channel for User and Company {}, {}", userId, companyChannel); + if (userWithCompany == null) { + throw new CustomValidationException(Status.BAD_REQUEST, GepafinConstant.USER_WITH_COMPANY_NOT_FOUND); + } + notificationEntity.setUserWithCompany(userWithCompany); + notificationRepository.save(notificationEntity); + NotificationResponse notificationResponse = convertNotificationEntityToNotificationResponse(notificationEntity); + messagingTemplate.convertAndSend(companyChannel, notificationResponse); + }); + } + + private NotificationResponse convertNotificationEntityToNotificationResponse(NotificationEntity notificationEntity) { + + NotificationResponse notificationResponse = new NotificationResponse(); + notificationResponse.setId(notificationEntity.getId()); + notificationResponse.setUserId(notificationEntity.getUserId()); + notificationResponse.setStatus(notificationEntity.getStatus()); + notificationResponse.setMessage(notificationEntity.getMessage()); + notificationResponse.setCreatedDate(notificationEntity.getCreatedDate()); + notificationResponse.setUpdatedDate(notificationEntity.getUpdatedDate()); + notificationResponse.setRedirectUrl(notificationEntity.getNotificationType()); + notificationResponse.setCompanyId(notificationEntity.getUserWithCompany() != null ? notificationEntity.getUserWithCompany().getCompanyId() : null); + notificationResponse.setNotificationType(notificationEntity.getNotificationType()); + notificationResponse.setTitle(notificationEntity.getTitle()); + return notificationResponse; + } + + private NotificationEntity convertNotificationRequestToNotificationEntity(NotificationReq notificationReq) { + + NotificationEntity notificationEntity = new NotificationEntity(); + String message = notificationReq.getMessage(); + notificationEntity.setNotificationType(notificationReq.getNotificationType()); + notificationEntity.setUserId(notificationReq.getUserId()); + notificationEntity.setStatus(NotificationEnum.UNREAD.getValue()); + notificationEntity.setIsDeleted(Boolean.FALSE); + notificationEntity.setUserWithCompany(notificationReq.getUserWithCompanyEntity() != null ? notificationReq.getUserWithCompanyEntity() : null); + notificationEntity.setMessage(message); + notificationEntity.setTitle(notificationReq.getTitle()); + return notificationEntity; + } + + public NotificationReq createNotificationReq(String notificationType, Map placeholders, Long userId, UserWithCompanyEntity userWithCompanyEntity, + List companyIds) { + // Create NotificationReq object + NotificationReq notificationReq = new NotificationReq(); + NotificationTypeEntity notificationTypeEntity = notificationTypeRepository.findByNotificationNameAndIsDeletedFalse(notificationType); + notificationReq.setNotificationType(notificationType); + String message = Utils.replacePlaceholders(notificationTypeEntity.getJsonTemplate(), placeholders); + notificationReq.setMessage(message); + notificationReq.setUserId(userId); + notificationReq.setCompanyIds(companyIds); + String title = Utils.replacePlaceholders(notificationTypeEntity.getTitle(), placeholders); + notificationReq.setTitle(title); + notificationReq.setUserWithCompanyEntity(userWithCompanyEntity); + return notificationReq; + } + + public Map sendNotificationToBeneficiary(ApplicationEntity application, NotificationTypeEnum notificationTypeEnum) { + + Map placeHolders = new HashMap<>(); + placeHolders.put("{{call_name}}", application.getCall().getName()); + placeHolders.put("{{protocol_number}}", String.valueOf(application.getProtocol().getProtocolNumber())); + NotificationReq notificationReq = createNotificationReq(notificationTypeEnum.getValue(), placeHolders, application.getUserId(), application.getUserWithCompany(), + listOf(application.getCompanyId())); + sendNotification(notificationReq); + return placeHolders; + } + + public void sendNotificationToInstructor(Map placeHolders, ApplicationEvaluationEntity applicationEvaluationEntity, NotificationTypeEnum notificationTypeEnum) { + + Long instructorId = applicationEvaluationEntity.getUserId(); + ApplicationEntity application = applicationService.validateApplication(applicationEvaluationEntity.getApplicationId()); + if (instructorId != null) { + NotificationReq notificationreq = createNotificationReq(notificationTypeEnum.getValue(), placeHolders, instructorId, application.getUserWithCompany(), + listOf(application.getCompanyId())); + sendNotification(notificationreq); + } + } + public void sendNotificationToSuperUser(ApplicationEntity application, Map placeHolders, NotificationTypeEnum notificationTypeEnum) { + + List user = userRepository.findByRoleEntity_RoleTypeAndHubId(RoleStatusEnum.ROLE_SUPER_ADMIN.getValue(), application.getHubId()); + UserEntity userEntity1 = user.get(0); + if (userEntity1 != null) { + NotificationReq notificationreq = createNotificationReq(notificationTypeEnum.getValue(), placeHolders, userEntity1.getId(), application.getUserWithCompany(), + listOf(application.getCompanyId())); + sendNotification(notificationreq); + } + } + + public List getAllCompanyIdsForUser(Long userId) { + + return userWithCompanyRepository.findActiveCompanyIdsByUserId(userId); + } + + public NotificationResponse getNotificationById(Long id) { + + NotificationEntity notificationEntity = validateNotificationEntity(id); + return convertNotificationEntityToNotificationResponse(notificationEntity); + } + + private NotificationEntity validateNotificationEntity(Long id) { + + NotificationEntity notificationEntity = notificationRepository.findByIdAndIsDeletedFalse(id); + if (notificationEntity == null) { + throw new CustomValidationException(Status.NOT_FOUND, Translator.toLocale(GepafinConstant.NOTIFICATION_NOT_FOUND)); + } + return notificationEntity; + } + + public List getNotificationByUserId(Long userId, Long companyId, List statuses) { + + List notificationEntities = notificationRepository.findByUserIdAndIsDeletedFalse(userId); + UserWithCompanyEntity userWithCompany = null; + List statusStrings = new ArrayList<>(); + if (companyId != null) { + userWithCompany = companyDao.validateUserWithCompny(userId, companyId); + } + + if (statuses != null) { + statusStrings = statuses.stream().map(NotificationEnum::name) // Convert enum to its name as String + .toList(); + notificationEntities = notificationRepository.findByUserIdAndIsDeletedFalseAndStatusIn(userId, statusStrings); + + if (userWithCompany != null) { + notificationEntities = notificationRepository.findByUserIdAndUserWithCompanyIdAndIsDeletedFalseAndStatusIn(userId, userWithCompany.getId(), statusStrings); + } + } + + return notificationEntities.stream().map(this::convertNotificationEntityToNotificationResponse).collect(Collectors.toList()); + } + + public NotificationResponse updateNotificationStatus(Long id, NotificationEnum status) { + + NotificationEntity notificationEntity = validateNotificationEntity(id); + if (notificationEntity.getStatus().equals(status.getValue())) { + throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.NOTIFICATION_ALREADY_IN_THAT_STATE)); + } + notificationEntity.setStatus(status.getValue()); + notificationEntity.setUpdatedDate(DateTimeUtil.DateServerToUTC(LocalDateTime.now())); + notificationRepository.save(notificationEntity); + return convertNotificationEntityToNotificationResponse(notificationEntity); + } + + public void deleteNotification(Long id) { + + NotificationEntity notificationEntity = validateNotificationEntity(id); + notificationEntity.setIsDeleted(true); + notificationRepository.save(notificationEntity); + } + + public List getNotificationByCompanyIdAndUserId(Long userId, Long companyId) { + + companyDao.validateCompany(companyId); + userDao.validateUser(userId); + + UserWithCompanyEntity userWithCompanyData = userWithCompanyRepository.findByUserIdAndCompanyIdAndIsDeletedFalseForNotification(userId, companyId); + if (userWithCompanyData == null) { + throw new CustomValidationException(Status.BAD_REQUEST, GepafinConstant.USER_MUST_BE_ASSOCIATED_WITH_COMPANY); + } + + List notifications = notificationRepository.findByUserWithCompanyIdAndUserIdAndIsDeletedFalse(userWithCompanyData.getId(), + userWithCompanyData.getUserId()); + return notifications.stream().map(this::convertNotificationEntityToNotificationResponse).toList(); + } + +} diff --git a/src/main/java/net/gepafin/tendermanagement/entities/NotificationEntity.java b/src/main/java/net/gepafin/tendermanagement/entities/NotificationEntity.java new file mode 100644 index 00000000..718a7367 --- /dev/null +++ b/src/main/java/net/gepafin/tendermanagement/entities/NotificationEntity.java @@ -0,0 +1,39 @@ +package net.gepafin.tendermanagement.entities; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import lombok.Data; + +@Entity +@Table(name = "NOTIFICATION") +@Data +public class NotificationEntity extends BaseEntity { + + @Column(name = "USER_ID") + private Long userId; + + @Column(name = "MESSAGE") + private String message; + + @Column(name = "TITLE") + private String title; + + @Column(name = "STATUS") + private String status; + + @Column(name = "IS_DELETED") + private Boolean isDeleted; + + @Column(name = "NOTIFICATION_TYPE") + private String notificationType; + + @Column(name = "REDIRECT_LINK") + private String redirectLink; + + @ManyToOne + @JoinColumn(name = "USER_WITH_COMPANY_ID") + private UserWithCompanyEntity userWithCompany; +} diff --git a/src/main/java/net/gepafin/tendermanagement/entities/NotificationTypeEntity.java b/src/main/java/net/gepafin/tendermanagement/entities/NotificationTypeEntity.java new file mode 100644 index 00000000..40d3f220 --- /dev/null +++ b/src/main/java/net/gepafin/tendermanagement/entities/NotificationTypeEntity.java @@ -0,0 +1,24 @@ +package net.gepafin.tendermanagement.entities; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; +import lombok.Data; + +@Entity +@Data +@Table(name = "NOTIFICATION_TYPE") +public class NotificationTypeEntity extends BaseEntity { + + @Column(name = "NOTIFICATION_NAME") + private String notificationName; + + @Column(name = "JSON_TEMPLATE") + private String jsonTemplate; + + @Column(name = "TITLE") + private String title; + + @Column(name="IS_DELETED") + private Boolean isDeleted; +} diff --git a/src/main/java/net/gepafin/tendermanagement/enums/NotificationEnum.java b/src/main/java/net/gepafin/tendermanagement/enums/NotificationEnum.java new file mode 100644 index 00000000..5b8bf633 --- /dev/null +++ b/src/main/java/net/gepafin/tendermanagement/enums/NotificationEnum.java @@ -0,0 +1,27 @@ +package net.gepafin.tendermanagement.enums; + +import com.fasterxml.jackson.annotation.JsonValue; + +public enum NotificationEnum { + //status + READ("READ"), UNREAD("UNREAD"); + + private final String value; + + NotificationEnum(String value) { + + this.value = value; + } + + @JsonValue + public String getValue() { + + return value; + } + + @Override + public String toString() { + + return String.valueOf(value); + } +} diff --git a/src/main/java/net/gepafin/tendermanagement/enums/NotificationTypeEnum.java b/src/main/java/net/gepafin/tendermanagement/enums/NotificationTypeEnum.java new file mode 100644 index 00000000..54a13768 --- /dev/null +++ b/src/main/java/net/gepafin/tendermanagement/enums/NotificationTypeEnum.java @@ -0,0 +1,34 @@ +package net.gepafin.tendermanagement.enums; + +import com.fasterxml.jackson.annotation.JsonValue; + +public enum NotificationTypeEnum { + CALL_CREATED("CALL_CREATED"), + APPLICATION_SUBMISSION("APPLICATION_SUBMISSION"), + AMENDMENT_CREATION("AMENDMENT_CREATION"), + EVALUATION_RESULT("EVALUATION_RESULT"), + AMENDMENT_EXPIRED("AMENDMENT_EXPIRED"), + AMENDMENT_CLOSED("AMENDMENT_CLOSED"), + NDG_GENERATION("NDG_GENERATION"), + EVALUATION_CREATION("EVALUATION_CREATION"), + EVALUATION_EXPIRED("EVALUATION_EXPIRED"); + + private final String value; + + NotificationTypeEnum(String value) { + + this.value = value; + } + + @JsonValue + public String getValue() { + + return value; + } + + @Override + public String toString() { + + return String.valueOf(value); + } +} diff --git a/src/main/java/net/gepafin/tendermanagement/model/request/NotificationReq.java b/src/main/java/net/gepafin/tendermanagement/model/request/NotificationReq.java new file mode 100644 index 00000000..9582d700 --- /dev/null +++ b/src/main/java/net/gepafin/tendermanagement/model/request/NotificationReq.java @@ -0,0 +1,45 @@ +package net.gepafin.tendermanagement.model.request; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import net.gepafin.tendermanagement.entities.UserWithCompanyEntity; + +import java.time.LocalDateTime; +import java.util.List; + +@Data +public class NotificationReq { + + @JsonProperty(access = JsonProperty.Access.READ_ONLY) + Long id; + + @JsonProperty(access = JsonProperty.Access.READ_ONLY) + Long userId; + + String message; + + @JsonProperty(access = JsonProperty.Access.READ_ONLY) + String notificationType; + + @JsonProperty(access = JsonProperty.Access.READ_ONLY) + String status; + + @JsonProperty(access = JsonProperty.Access.READ_ONLY) + private LocalDateTime createdDate; + + @JsonProperty(access = JsonProperty.Access.READ_ONLY) + private LocalDateTime updatedDate; + + @JsonProperty(access = JsonProperty.Access.READ_ONLY) + private String redirectUrl; + + @JsonProperty(access = JsonProperty.Access.READ_ONLY) + private String title; + + @JsonProperty(access = JsonProperty.Access.READ_ONLY) + private List companyIds; + + @JsonIgnore + private UserWithCompanyEntity userWithCompanyEntity; +} diff --git a/src/main/java/net/gepafin/tendermanagement/model/response/ApplicationAmendmentRequestResponse.java b/src/main/java/net/gepafin/tendermanagement/model/response/ApplicationAmendmentRequestResponse.java index 0760217b..8e8aeb83 100644 --- a/src/main/java/net/gepafin/tendermanagement/model/response/ApplicationAmendmentRequestResponse.java +++ b/src/main/java/net/gepafin/tendermanagement/model/response/ApplicationAmendmentRequestResponse.java @@ -18,6 +18,7 @@ public class ApplicationAmendmentRequestResponse { private Long protocolNumber; private String callName; private String beneficiaryName; + private String companyName; private List formFields; private List applicationFormFields; private List amendmentDocuments; diff --git a/src/main/java/net/gepafin/tendermanagement/model/response/ApplicationEvaluationResponse.java b/src/main/java/net/gepafin/tendermanagement/model/response/ApplicationEvaluationResponse.java index b890b327..7da6d25f 100644 --- a/src/main/java/net/gepafin/tendermanagement/model/response/ApplicationEvaluationResponse.java +++ b/src/main/java/net/gepafin/tendermanagement/model/response/ApplicationEvaluationResponse.java @@ -26,6 +26,8 @@ public class ApplicationEvaluationResponse { private LocalDateTime createdDate; private LocalDateTime updatedDate; private String beneficiary; + private Long assignedUserId; + private String assignedUserName; private Long protocolNumber; private String callName; private String motivation; diff --git a/src/main/java/net/gepafin/tendermanagement/model/response/ApplicationResponse.java b/src/main/java/net/gepafin/tendermanagement/model/response/ApplicationResponse.java index d5df459e..24ad5a34 100644 --- a/src/main/java/net/gepafin/tendermanagement/model/response/ApplicationResponse.java +++ b/src/main/java/net/gepafin/tendermanagement/model/response/ApplicationResponse.java @@ -33,4 +33,8 @@ public class ApplicationResponse{ private Long protocolNumber; + private Long assignedUserId; + + private String assignedUserName; + } \ No newline at end of file diff --git a/src/main/java/net/gepafin/tendermanagement/model/response/NotificationResponse.java b/src/main/java/net/gepafin/tendermanagement/model/response/NotificationResponse.java new file mode 100644 index 00000000..0383cd25 --- /dev/null +++ b/src/main/java/net/gepafin/tendermanagement/model/response/NotificationResponse.java @@ -0,0 +1,21 @@ +package net.gepafin.tendermanagement.model.response; + +import lombok.Data; +import net.gepafin.tendermanagement.model.BaseBean; + +@Data +public class NotificationResponse extends BaseBean { + private Long userId; + + private String title; + + private String message; + + private String status; + + private Long companyId; + + private String redirectUrl; + + private String notificationType; +} diff --git a/src/main/java/net/gepafin/tendermanagement/model/response/UserResponseBean.java b/src/main/java/net/gepafin/tendermanagement/model/response/UserResponseBean.java index b0a5ef38..c8724d43 100644 --- a/src/main/java/net/gepafin/tendermanagement/model/response/UserResponseBean.java +++ b/src/main/java/net/gepafin/tendermanagement/model/response/UserResponseBean.java @@ -40,6 +40,7 @@ public class UserResponseBean extends BaseBean { private List companies; private Boolean privacy; + private Boolean terms; private Boolean marketing; diff --git a/src/main/java/net/gepafin/tendermanagement/repositories/BeneficiaryRepository.java b/src/main/java/net/gepafin/tendermanagement/repositories/BeneficiaryRepository.java index ecb6ed7d..984e477a 100644 --- a/src/main/java/net/gepafin/tendermanagement/repositories/BeneficiaryRepository.java +++ b/src/main/java/net/gepafin/tendermanagement/repositories/BeneficiaryRepository.java @@ -1,11 +1,16 @@ package net.gepafin.tendermanagement.repositories; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Repository; import net.gepafin.tendermanagement.entities.BeneficiaryEntity; +import java.util.List; + @Repository public interface BeneficiaryRepository extends JpaRepository { + @Query("SELECT u.id FROM UserEntity u JOIN u.beneficiary b WHERE b.id = u.beneficiary.id AND b.hubId = :hubId") + List findUserIdsByHubIdAndBeneficiaryId(Long hubId); } diff --git a/src/main/java/net/gepafin/tendermanagement/repositories/NotificationRepository.java b/src/main/java/net/gepafin/tendermanagement/repositories/NotificationRepository.java new file mode 100644 index 00000000..bce3ba68 --- /dev/null +++ b/src/main/java/net/gepafin/tendermanagement/repositories/NotificationRepository.java @@ -0,0 +1,20 @@ +package net.gepafin.tendermanagement.repositories; + +import net.gepafin.tendermanagement.entities.NotificationEntity; +import org.springframework.data.domain.Page; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface NotificationRepository extends JpaRepository { + + NotificationEntity findByIdAndIsDeletedFalse(Long id); + + List findByUserIdAndIsDeletedFalse(Long userId); + + List findByUserIdAndUserWithCompanyIdAndIsDeletedFalseAndStatusIn(Long userId, Long userWithCompanyId, List statuses); + + List findByUserIdAndIsDeletedFalseAndStatusIn(Long userId, List statuses); + + List findByUserWithCompanyIdAndUserIdAndIsDeletedFalse(Long userWithCompanyId, Long userId); +} diff --git a/src/main/java/net/gepafin/tendermanagement/repositories/NotificationTypeRepository.java b/src/main/java/net/gepafin/tendermanagement/repositories/NotificationTypeRepository.java new file mode 100644 index 00000000..78cef54d --- /dev/null +++ b/src/main/java/net/gepafin/tendermanagement/repositories/NotificationTypeRepository.java @@ -0,0 +1,8 @@ +package net.gepafin.tendermanagement.repositories; + +import net.gepafin.tendermanagement.entities.NotificationTypeEntity; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface NotificationTypeRepository extends JpaRepository { + NotificationTypeEntity findByNotificationNameAndIsDeletedFalse(String value); +} diff --git a/src/main/java/net/gepafin/tendermanagement/repositories/UserRepository.java b/src/main/java/net/gepafin/tendermanagement/repositories/UserRepository.java index d9a9577a..e8cb813a 100644 --- a/src/main/java/net/gepafin/tendermanagement/repositories/UserRepository.java +++ b/src/main/java/net/gepafin/tendermanagement/repositories/UserRepository.java @@ -25,4 +25,7 @@ public interface UserRepository extends JpaRepository { Optional findByBeneficiaryCodiceFiscaleAndHubId(String codiceFiscale, Long hubId); boolean existsByBeneficiaryCodiceFiscaleAndHubId(String codiceFiscale, Long hubId); + + List findByRoleEntity_RoleTypeAndHubId(String roleType, Long hubId); + } diff --git a/src/main/java/net/gepafin/tendermanagement/repositories/UserWithCompanyRepository.java b/src/main/java/net/gepafin/tendermanagement/repositories/UserWithCompanyRepository.java index ec93f2f6..13a197f1 100644 --- a/src/main/java/net/gepafin/tendermanagement/repositories/UserWithCompanyRepository.java +++ b/src/main/java/net/gepafin/tendermanagement/repositories/UserWithCompanyRepository.java @@ -1,22 +1,23 @@ package net.gepafin.tendermanagement.repositories; -import java.util.List; -import java.util.Optional; - +import net.gepafin.tendermanagement.entities.UserWithCompanyEntity; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; -import net.gepafin.tendermanagement.entities.UserWithCompanyEntity; - +import java.util.List; +import java.util.Optional; public interface UserWithCompanyRepository extends JpaRepository { - void deleteByCompanyIdAndIsDeletedFalse(Long companyId); + void deleteByCompanyIdAndIsDeletedFalse(Long companyId); - @Query("SELECT u.companyId FROM UserWithCompanyEntity u WHERE u.userId = :userId AND u.isDeleted = false") - List findActiveCompanyIdsByUserId(@Param("userId") Long userId); + @Query("SELECT u.companyId FROM UserWithCompanyEntity u WHERE u.userId = :userId AND u.isDeleted = false") + List findActiveCompanyIdsByUserId(@Param("userId") Long userId); - Optional findByUserIdAndCompanyIdAndIsDeletedFalse(Long userId, Long companyId); + Optional findByUserIdAndCompanyIdAndIsDeletedFalse(Long userId, Long companyId); + + @Query("SELECT u FROM UserWithCompanyEntity u WHERE u.userId = :userId AND u.companyId = :companyId AND u.isDeleted = false") + UserWithCompanyEntity findByUserIdAndCompanyIdAndIsDeletedFalseForNotification(Long userId, Long companyId); } diff --git a/src/main/java/net/gepafin/tendermanagement/scheduler/ApplicationAmendmentScheduler.java b/src/main/java/net/gepafin/tendermanagement/scheduler/ApplicationAmendmentScheduler.java index b9d63acd..91cfaf78 100644 --- a/src/main/java/net/gepafin/tendermanagement/scheduler/ApplicationAmendmentScheduler.java +++ b/src/main/java/net/gepafin/tendermanagement/scheduler/ApplicationAmendmentScheduler.java @@ -1,35 +1,42 @@ package net.gepafin.tendermanagement.scheduler; -import java.time.LocalDateTime; -import java.time.LocalTime; -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; - +import jakarta.servlet.http.HttpServletRequest; +import net.gepafin.tendermanagement.dao.ApplicationAmendmentRequestDao; +import net.gepafin.tendermanagement.dao.NotificationDao; +import net.gepafin.tendermanagement.entities.ApplicationAmendmentRequestEntity; import net.gepafin.tendermanagement.entities.ApplicationEntity; +import net.gepafin.tendermanagement.entities.ApplicationEvaluationEntity; import net.gepafin.tendermanagement.entities.AssignedApplicationsEntity; -import net.gepafin.tendermanagement.enums.*; +import net.gepafin.tendermanagement.enums.ApplicationAmendmentRequestEnum; +import net.gepafin.tendermanagement.enums.ApplicationEvaluationStatusTypeEnum; +import net.gepafin.tendermanagement.enums.ApplicationStatusTypeEnum; +import net.gepafin.tendermanagement.enums.AssignedApplicationEnum; +import net.gepafin.tendermanagement.enums.NotificationTypeEnum; +import net.gepafin.tendermanagement.enums.UserActionContextEnum; +import net.gepafin.tendermanagement.enums.UserActionLogsEnum; +import net.gepafin.tendermanagement.enums.VersionActionTypeEnum; +import net.gepafin.tendermanagement.model.request.UserActionRequest; +import net.gepafin.tendermanagement.model.request.VersionHistoryRequest; +import net.gepafin.tendermanagement.repositories.ApplicationAmendmentRequestRepository; import net.gepafin.tendermanagement.repositories.ApplicationEvaluationRepository; import net.gepafin.tendermanagement.repositories.ApplicationRepository; import net.gepafin.tendermanagement.repositories.AssignedApplicationsRepository; import net.gepafin.tendermanagement.service.ApplicationService; +import net.gepafin.tendermanagement.util.DateTimeUtil; +import net.gepafin.tendermanagement.util.LoggingUtil; +import net.gepafin.tendermanagement.util.Utils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; - -import jakarta.servlet.http.HttpServletRequest; -import net.gepafin.tendermanagement.dao.ApplicationAmendmentRequestDao; -import net.gepafin.tendermanagement.entities.ApplicationAmendmentRequestEntity; -import net.gepafin.tendermanagement.entities.ApplicationEvaluationEntity; -import net.gepafin.tendermanagement.model.request.UserActionRequest; -import net.gepafin.tendermanagement.model.request.VersionHistoryRequest; -import net.gepafin.tendermanagement.repositories.ApplicationAmendmentRequestRepository; -import net.gepafin.tendermanagement.util.DateTimeUtil; -import net.gepafin.tendermanagement.util.LoggingUtil; -import net.gepafin.tendermanagement.util.Utils; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; @Component public class ApplicationAmendmentScheduler { @@ -47,6 +54,9 @@ public class ApplicationAmendmentScheduler { @Autowired private LoggingUtil loggingUtil; + @Autowired + private NotificationDao notificationDao; + @Autowired private AssignedApplicationsRepository assignedApplicationsRepository; @@ -90,8 +100,10 @@ public class ApplicationAmendmentScheduler { try { ApplicationAmendmentRequestEntity oldAmendmentRequestEntity = Utils.getClonedEntityForData(request); request.setStatus(ApplicationAmendmentRequestEnum.EXPIRED.getValue()); + ApplicationEntity application=oldAmendmentRequestEntity.getApplicationEvaluationEntity().getAssignedApplicationsEntity().getApplication(); request = applicationAmendmentRepository.save(request); - + Map placeHolders=notificationDao.sendNotificationToBeneficiary(application,NotificationTypeEnum.AMENDMENT_EXPIRED); + notificationDao.sendNotificationToInstructor(placeHolders,oldAmendmentRequestEntity.getApplicationEvaluationEntity(),NotificationTypeEnum.AMENDMENT_EXPIRED); /** This code is responsible for adding a version history log for the "Update Application Amendment" operation. **/ loggingUtil.addVersionHistory(VersionHistoryRequest.builder().request(httpServletRequest).actionType(VersionActionTypeEnum.UPDATE).oldData(oldAmendmentRequestEntity).newData(request).build()); diff --git a/src/main/java/net/gepafin/tendermanagement/scheduler/ApplicationEvaluationScheduler.java b/src/main/java/net/gepafin/tendermanagement/scheduler/ApplicationEvaluationScheduler.java index a0444349..e7249645 100644 --- a/src/main/java/net/gepafin/tendermanagement/scheduler/ApplicationEvaluationScheduler.java +++ b/src/main/java/net/gepafin/tendermanagement/scheduler/ApplicationEvaluationScheduler.java @@ -1,8 +1,11 @@ package net.gepafin.tendermanagement.scheduler; import jakarta.servlet.http.HttpServletRequest; +import net.gepafin.tendermanagement.dao.NotificationDao; +import net.gepafin.tendermanagement.entities.ApplicationEntity; import net.gepafin.tendermanagement.entities.ApplicationEvaluationEntity; import net.gepafin.tendermanagement.enums.ApplicationEvaluationStatusTypeEnum; +import net.gepafin.tendermanagement.enums.NotificationTypeEnum; import net.gepafin.tendermanagement.enums.UserActionContextEnum; import net.gepafin.tendermanagement.enums.UserActionLogsEnum; import net.gepafin.tendermanagement.enums.VersionActionTypeEnum; @@ -22,6 +25,7 @@ import org.springframework.stereotype.Component; import java.time.LocalDateTime; import java.time.LocalTime; import java.util.List; +import java.util.Map; @Component public class ApplicationEvaluationScheduler { @@ -35,6 +39,9 @@ public class ApplicationEvaluationScheduler { @Autowired private HttpServletRequest httpServletRequest; + @Autowired + private NotificationDao notificationDao; + private static final Logger log = LoggerFactory.getLogger(ApplicationEvaluationScheduler.class); @Scheduled(cron = "0 0 2 * * ?") // Runs daily at midnight @@ -76,10 +83,15 @@ public class ApplicationEvaluationScheduler { ApplicationEvaluationEntity oldApplicationEvaluationEntity = Utils .getClonedEntityForData(evaluation); - + ApplicationEntity application=evaluation.getAssignedApplicationsEntity().getApplication(); evaluation.setStatus(ApplicationEvaluationStatusTypeEnum.EXPIRED.getValue()); evaluation = applicationEvaluationRepository.save(evaluation); + Map placeHolders = notificationDao.sendNotificationToBeneficiary(application, NotificationTypeEnum.EVALUATION_EXPIRED); + notificationDao.sendNotificationToSuperUser(application,placeHolders,NotificationTypeEnum.EVALUATION_EXPIRED); + notificationDao.sendNotificationToInstructor(placeHolders,evaluation,NotificationTypeEnum.EVALUATION_EXPIRED); + + // Logging version history for the update operation loggingUtil.addVersionHistory(VersionHistoryRequest.builder().request(httpServletRequest) .actionType(VersionActionTypeEnum.UPDATE).oldData(oldApplicationEvaluationEntity) diff --git a/src/main/java/net/gepafin/tendermanagement/service/NotificationService.java b/src/main/java/net/gepafin/tendermanagement/service/NotificationService.java new file mode 100644 index 00000000..4d5d3ba6 --- /dev/null +++ b/src/main/java/net/gepafin/tendermanagement/service/NotificationService.java @@ -0,0 +1,23 @@ +package net.gepafin.tendermanagement.service; + +import jakarta.servlet.http.HttpServletRequest; +import net.gepafin.tendermanagement.enums.NotificationEnum; +import net.gepafin.tendermanagement.model.request.NotificationReq; +import net.gepafin.tendermanagement.model.response.NotificationResponse; + +import java.util.List; + +public interface NotificationService { + NotificationResponse sendNotification(Long userId, NotificationReq notificationReq, Long companyId); + + public NotificationResponse getNotificationById(HttpServletRequest servletRequest, Long id); + + public List getNotificationByUserId(HttpServletRequest servletRequest, Long userId, Long companyId, List statuses); + + public NotificationResponse updateNotificationStatus(HttpServletRequest request, Long id, NotificationEnum status); + + public void deleteNotification(HttpServletRequest request, Long id); + + public List getNotificationsByCompanyIdAndUserId(Long userId, Long companyId); + +} diff --git a/src/main/java/net/gepafin/tendermanagement/service/impl/NotificationServiceImpl.java b/src/main/java/net/gepafin/tendermanagement/service/impl/NotificationServiceImpl.java new file mode 100644 index 00000000..9fa0956a --- /dev/null +++ b/src/main/java/net/gepafin/tendermanagement/service/impl/NotificationServiceImpl.java @@ -0,0 +1,62 @@ +package net.gepafin.tendermanagement.service.impl; + +import jakarta.servlet.http.HttpServletRequest; +import lombok.extern.slf4j.Slf4j; +import net.gepafin.tendermanagement.dao.NotificationDao; +import net.gepafin.tendermanagement.enums.NotificationEnum; +import net.gepafin.tendermanagement.model.request.NotificationReq; +import net.gepafin.tendermanagement.model.response.NotificationResponse; +import net.gepafin.tendermanagement.service.NotificationService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; + +import static org.hibernate.internal.util.collections.CollectionHelper.listOf; + +@Service +@Slf4j +public class NotificationServiceImpl implements NotificationService { + + @Autowired + private NotificationDao notificationDao; + + @Override + public NotificationResponse sendNotification(Long userId, NotificationReq notificationReq, Long companyId) { + + log.info("Sending notification to user {} with content: {}", userId, notificationReq.getMessage()); + notificationReq.setUserId(userId); + notificationReq.setCompanyIds(listOf(companyId)); + return notificationDao.sendNotification(notificationReq); + } + + @Override + public NotificationResponse getNotificationById(HttpServletRequest servletRequest, Long id) { + + return notificationDao.getNotificationById(id); + } + + @Override + public List getNotificationByUserId(HttpServletRequest servletRequest, Long userId, Long companyId, List statuses) { + + return notificationDao.getNotificationByUserId(userId, companyId, statuses); + } + + @Override + public NotificationResponse updateNotificationStatus(HttpServletRequest request, Long id, NotificationEnum status) { + + return notificationDao.updateNotificationStatus(id, status); + } + + @Override + public void deleteNotification(HttpServletRequest request, Long id) { + + notificationDao.deleteNotification(id); + return; + } + + @Override + public List getNotificationsByCompanyIdAndUserId(Long userId, Long companyId) { + return notificationDao.getNotificationByCompanyIdAndUserId(userId, companyId); + } +} \ 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 7d5b6344..b126e6dd 100644 --- a/src/main/java/net/gepafin/tendermanagement/util/Utils.java +++ b/src/main/java/net/gepafin/tendermanagement/util/Utils.java @@ -19,19 +19,15 @@ import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.node.ObjectNode; -import io.jsonwebtoken.Claims; import jakarta.persistence.ManyToMany; import jakarta.persistence.ManyToOne; import jakarta.persistence.OneToMany; import jakarta.persistence.OneToOne; import jakarta.servlet.http.HttpServletRequest; -import net.gepafin.tendermanagement.config.Translator; import net.gepafin.tendermanagement.constants.GepafinConstant; -import net.gepafin.tendermanagement.web.rest.api.errors.CustomValidationException; import org.apache.commons.collections4.MapUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import com.fasterxml.jackson.core.JsonProcessingException; @@ -48,7 +44,6 @@ import net.gepafin.tendermanagement.web.rest.api.errors.FeignClientForbiddenExce import net.gepafin.tendermanagement.web.rest.api.errors.FeignClientNotFoundException; import net.gepafin.tendermanagement.web.rest.api.errors.FeignClientUnauthorizedException; import net.gepafin.tendermanagement.web.rest.api.errors.FeignClientValidationException; -import org.springframework.http.MediaType; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; @@ -691,4 +686,8 @@ public class Utils { return null; } } + + public static String createChannelForUserAndCompany(Long userId, Long companyId) { + return GepafinConstant.COMMON_SINGLE_CHANNEL_PREFIX + userId + GepafinConstant.COMPANY_PREFIX + companyId; + } } \ No newline at end of file diff --git a/src/main/java/net/gepafin/tendermanagement/web/rest/api/NotificationApi.java b/src/main/java/net/gepafin/tendermanagement/web/rest/api/NotificationApi.java new file mode 100644 index 00000000..6f46244b --- /dev/null +++ b/src/main/java/net/gepafin/tendermanagement/web/rest/api/NotificationApi.java @@ -0,0 +1,100 @@ +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.enums.NotificationEnum; +import net.gepafin.tendermanagement.model.request.NotificationReq; +import net.gepafin.tendermanagement.model.response.NotificationResponse; +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.DeleteMapping; +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.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; + +import java.util.List; + +public interface NotificationApi { + @Operation(summary = "Api to send notification.", 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 = "/user/{userId}/sent", consumes = "application/json", produces = "application/json") + ResponseEntity> sendNotification(HttpServletRequest request, @RequestBody NotificationReq notificationReq, + @Parameter(description = "The company id", required = false) @RequestParam(value = "companyId", required = false) Long companyId, + @Parameter(description = "The user id", required = true) @PathVariable("userId") Long userId); + + @Operation(summary = "Api to get notification by id", 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 = "/{id}", produces = "application/json") + ResponseEntity> getNotificationById(HttpServletRequest request, + @Parameter(description = "The notification id", required = true) @PathVariable(value = "id", required = true) Long id); + + @Operation(summary = "Api to get notification by user id", 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 = "/user/{userId}", produces = "application/json") + ResponseEntity>> getNotificationByUserId(HttpServletRequest request, + @Parameter(description = "The user id", required = true) @PathVariable(value = "userId", required = true) Long userId, + @Parameter(description = "The company id", required = false) @RequestParam(value = "companyId", required = false) Long companyId, + @Parameter(description = "The notification status", required = false) @RequestParam(value = "status", required = false) List statuses); + + @Operation(summary = "Api to update notification status", responses = { @ApiResponse(responseCode = "200", description = "OK"), + @ApiResponse(responseCode = "404", description = "Not Found", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, examples = { + @ExampleObject(value = ErrorConstants.NOTFOUND_ERROR_EXAMPLE) })), + @ApiResponse(responseCode = "401", description = "Unauthorized", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, examples = { + @ExampleObject(value = ErrorConstants.UNAUTHORIZED_ERROR_EXAMPLE) })), + @ApiResponse(responseCode = "400", description = "Bad Request", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, examples = { + @ExampleObject(value = ErrorConstants.BADREQUEST_ERROR_EXAMPLE) })) }) + @PutMapping(value = "/{id}", produces = "application/json") + ResponseEntity> updateNotificationStatus(HttpServletRequest request, + @Parameter(description = "The notification id", required = true) @PathVariable(value = "id", required = true) Long id, + @Parameter(description = "The notification status", required = true) @RequestParam(value = "status", required = true) NotificationEnum status); + + @Operation(summary = "Api to delete notification", 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) })) }) + @DeleteMapping(value = "/{id}", produces = "application/json") + ResponseEntity> deleteNotification(HttpServletRequest request, + @Parameter(description = "The notification id", required = true) @PathVariable(value = "id", required = true) Long id); + + @Operation(summary = "API to get notifications by user ID and company ID", 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 = "/user/{userId}/company/{companyId}/notifications", produces = "application/json") + ResponseEntity>> getNotificationsByUserIdAndCompanyId(HttpServletRequest request, + @Parameter(description = "The user id", required = true) @PathVariable(value = "userId") Long userId, + @Parameter(description = "The company ID", required = true) @PathVariable(value = "companyId") Long companyId); + +} + + diff --git a/src/main/java/net/gepafin/tendermanagement/web/rest/api/impl/NotificationApiController.java b/src/main/java/net/gepafin/tendermanagement/web/rest/api/impl/NotificationApiController.java new file mode 100644 index 00000000..586a5f9e --- /dev/null +++ b/src/main/java/net/gepafin/tendermanagement/web/rest/api/impl/NotificationApiController.java @@ -0,0 +1,74 @@ +package net.gepafin.tendermanagement.web.rest.api.impl; + +import jakarta.servlet.http.HttpServletRequest; +import net.gepafin.tendermanagement.config.Translator; +import net.gepafin.tendermanagement.constants.GepafinConstant; +import net.gepafin.tendermanagement.enums.NotificationEnum; +import net.gepafin.tendermanagement.model.request.NotificationReq; +import net.gepafin.tendermanagement.model.response.NotificationResponse; +import net.gepafin.tendermanagement.model.util.Response; +import net.gepafin.tendermanagement.service.NotificationService; +import net.gepafin.tendermanagement.web.rest.api.NotificationApi; +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; + +import java.util.List; + +@RestController +@RequestMapping("${openapi.gepafin.base-path:/v1/notification}") +public class NotificationApiController implements NotificationApi { + + @Autowired + private NotificationService notificationService; + + public ResponseEntity> sendNotification(HttpServletRequest request, NotificationReq notificationReq, Long userId, Long companyId) { + + NotificationResponse notificationData = notificationService.sendNotification(userId, notificationReq, companyId); + + return ResponseEntity.status(HttpStatus.OK).body(new Response<>(notificationData, Status.SUCCESS, Translator.toLocale(GepafinConstant.NOTIFICATION_SENT_SUCCESSFULLY))); + } + + @Override + public ResponseEntity> getNotificationById(HttpServletRequest request, Long id) { + + NotificationResponse notificationResponse = notificationService.getNotificationById(request, id); + return ResponseEntity.status(HttpStatus.OK) + .body(new Response<>(notificationResponse, Status.SUCCESS, Translator.toLocale(GepafinConstant.NOTIFICATION_FETCHED_SUCCESSFULLY))); + } + + @Override + public ResponseEntity>> getNotificationByUserId(HttpServletRequest request, Long userId, Long companyId, List statuses) { + + List notificationResponses = notificationService.getNotificationByUserId(request, userId, companyId, statuses); + return ResponseEntity.status(HttpStatus.OK) + .body(new Response>(notificationResponses, Status.SUCCESS, Translator.toLocale(GepafinConstant.NOTIFICATION_FETCHED_SUCCESSFULLY))); + + } + + @Override + public ResponseEntity> updateNotificationStatus(HttpServletRequest request, Long id, NotificationEnum notificationEnums) { + + NotificationResponse notificationResponse = notificationService.updateNotificationStatus(request, id, notificationEnums); + return ResponseEntity.status(HttpStatus.OK) + .body(new Response<>(notificationResponse, Status.SUCCESS, Translator.toLocale(GepafinConstant.NOTIFICATION_UPDATED_SUCCESSFULLY))); + } + + @Override + public ResponseEntity> deleteNotification(HttpServletRequest request, Long id) { + + notificationService.deleteNotification(request, id); + return ResponseEntity.status(HttpStatus.OK).body(new Response<>(null, Status.SUCCESS, Translator.toLocale(GepafinConstant.NOTIFICATION_DELETED_SUCCESSFULLY))); + } + + @Override + public ResponseEntity>> getNotificationsByUserIdAndCompanyId(HttpServletRequest request,Long userId, Long companyId) { + List notificationResponses = notificationService.getNotificationsByCompanyIdAndUserId(userId, companyId); + return ResponseEntity.status(HttpStatus.OK) + .body(new Response<>(notificationResponses, Status.SUCCESS, Translator.toLocale(GepafinConstant.NOTIFICATION_FETCHED_SUCCESSFULLY))); + } + +} \ No newline at end of file diff --git a/src/main/resources/application-dev.properties b/src/main/resources/application-dev.properties index f99634bc..e211e883 100644 --- a/src/main/resources/application-dev.properties +++ b/src/main/resources/application-dev.properties @@ -22,4 +22,11 @@ appointment.portal.user=UtenzaAPIPortal@621 appointment.portal.password=u13nzaAP1P0rtal appointment.portal.source=GEPAFINPORTAL appointment.portal.context=GEPAFINPORTAL -flagDaFirmare=false \ No newline at end of file +flagDaFirmare=false + +# RabbitMQ properties for STOMP broker relay for Notification +spring.rabbitmq.host=rabbitmq.bflows.ai +spring.rabbitmq.port=61613 +spring.rabbitmq.username=guest +spring.rabbitmq.password=guest +spring.rabbitmq.virtual-host=/ \ No newline at end of file diff --git a/src/main/resources/application-local.properties b/src/main/resources/application-local.properties index 37bcabad..e52a9577 100644 --- a/src/main/resources/application-local.properties +++ b/src/main/resources/application-local.properties @@ -20,4 +20,11 @@ appointment.portal.user=UtenzaAPIPortal@621 appointment.portal.password=u13nzaAP1P0rtal appointment.portal.source=GEPAFINPORTAL appointment.portal.context=GEPAFINPORTAL -flagDaFirmare=false \ No newline at end of file +flagDaFirmare=false + +# RabbitMQ properties for STOMP broker relay for Notification +spring.rabbitmq.host=localhost +spring.rabbitmq.port=61613 +spring.rabbitmq.username=guest +spring.rabbitmq.password=guest +spring.rabbitmq.virtual-host=/ \ No newline at end of file diff --git a/src/main/resources/application-production.properties b/src/main/resources/application-production.properties index 31a7e7b4..df295088 100644 --- a/src/main/resources/application-production.properties +++ b/src/main/resources/application-production.properties @@ -29,4 +29,11 @@ appointment.portal.user=UtenzaAPIPortal@621 appointment.portal.password=u13nzaAP1P0rtal appointment.portal.source=GEPAFINPORTAL appointment.portal.context=GEPAFINPORTAL -flagDaFirmare=false \ No newline at end of file +flagDaFirmare=true + +# RabbitMQ properties for STOMP broker relay for Notification +spring.rabbitmq.host=rabbitmq.bflows.ai +spring.rabbitmq.port=61613 +spring.rabbitmq.username=guest +spring.rabbitmq.password=guest +spring.rabbitmq.virtual-host=/ \ No newline at end of file diff --git a/src/main/resources/application-testing.properties b/src/main/resources/application-testing.properties index 1dbc41cc..8ee53329 100644 --- a/src/main/resources/application-testing.properties +++ b/src/main/resources/application-testing.properties @@ -11,4 +11,18 @@ default_System_Receiver_Email=test@test.test gepafin_email=test@test.test rinaldo_email=test@test.test carlo_email=test@test.test -default.hub.uuid=p4lk3bcx1RStqTaIVVbXs \ No newline at end of file +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 + +# RabbitMQ properties for STOMP broker relay for Notification +spring.rabbitmq.host=rabbitmq.bflows.ai +spring.rabbitmq.port=61613 +spring.rabbitmq.username=guest +spring.rabbitmq.password=guest +spring.rabbitmq.virtual-host=/ \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 60f61aec..d0af99d3 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -4,7 +4,7 @@ spring.application.name=tendermanagement spring.servlet.multipart.max-file-size=300MB spring.servlet.multipart.max-request-size=300MB -spring.profiles.active=testing +spring.profiles.active=testing # JPA Configuration @@ -67,4 +67,5 @@ default.hub.pdf.banner=https://mementoresources.s3.amazonaws.com/gepafin/staging #feign client config spring.cloud.openfeign.client.config.default.connectTimeout=300000 -spring.cloud.openfeign.client.config.default.readTimeout=300000 \ No newline at end of file +spring.cloud.openfeign.client.config.default.readTimeout=300000 +spring.rabbitmq.connection-timeout=120000 \ No newline at end of file diff --git a/src/main/resources/db/changelog/db.changelog-1.0.0.xml b/src/main/resources/db/changelog/db.changelog-1.0.0.xml index 90ab3ca8..0fdebf9e 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 @@ -2070,4 +2070,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/db/dump/insert_json_template_for_notification_13_12_2024.sql b/src/main/resources/db/dump/insert_json_template_for_notification_13_12_2024.sql new file mode 100644 index 00000000..9856d855 --- /dev/null +++ b/src/main/resources/db/dump/insert_json_template_for_notification_13_12_2024.sql @@ -0,0 +1,10 @@ +INSERT INTO notification_type (notification_name,title, json_template,created_date,updated_date,is_deleted) VALUES +('CALL_CREATED', 'Un Nuovo Bando È Stato Pubblicato','Un nuovo bando intitolato {{call_name}} è stato pubblicato. Controllalo e invia le candidature prima della scadenza.','2024-12-19T10:16:26.472Z','2024-12-19T10:16:26.472Z','false'), +('APPLICATION_SUBMISSION','Candidatura Inviata con Successo per la Valutazione', 'La richiesta per {{call_name}} ai sensi del protocollo n. {{protocol_number}} è stata presentata con successo. È ora in fase di valutazione.','2024-12-19T10:16:26.472Z','2024-12-19T10:16:26.472Z','false'), +('AMENDMENT_CREATION','È Stato Creato un Emendamento per la Richiesta', 'È stato creato un emendamento per la richiesta in {{call_name}} ai sensi del protocollo n. {{protocol_number}}. Esamina le modifiche e procedi di conseguenza.','2024-12-19T10:16:26.472Z','2024-12-19T10:16:26.472Z','false'), +('EVALUATION_RESULT','Il Risultato della Valutazione per la Richiesta È Disponibile','Il risultato della valutazione per la richiesta ai sensi del protocollo n. {{protocol_number}} è ora disponibile.','2024-12-19T10:16:26.472Z','2024-12-19T10:16:26.472Z','false'), +('AMENDMENT_EXPIRED','L Emendamento per la Richiesta È Scaduto', 'L’emendamento per la richiesta in {{call_name}} ai sensi del protocollo n. {{protocol_number}} è scaduto.','2024-12-19T10:16:26.472Z','2024-12-19T10:16:26.472Z','false'), +('AMENDMENT_CLOSED','L Emendamento È Stato Chiuso ed È Ora Inattivo','L’emendamento per {{call_name}} ai sensi del protocollo n. {{protocol_number}} è stato chiuso ed è ora inattivo.','2024-12-19T10:16:26.472Z','2024-12-19T10:16:26.472Z','false'), +('NDG_GENERATION','È Stato Generato un Nuovo NDG per la Richiesta','È stato generato un nuovo NDG per {{call_name}} ai sensi del protocollo n. {{protocol_number}}.','2024-12-19T10:16:26.472Z','2024-12-19T10:16:26.472Z','false'), +('EVALUATION_CREATION','La Richiesta È Stata Assegnata per la Valutazione','La richiesta in {{call_name}} ai sensi del protocollo n. {{protocol_number}} è stata assegnata alla fase di valutazione.','2024-12-19T10:16:26.472Z','2024-12-19T10:16:26.472Z','false'), +('EVALUATION_EXPIRED','La Valutazione per la Richiesta È Scaduta','La valutazione per la richiesta in {{call_name}} ai sensi del protocollo n. {{protocol_number}} è scaduta.','2024-12-19T10:16:26.472Z','2024-12-19T10:16:26.472Z','false'); \ No newline at end of file diff --git a/src/main/resources/message_en.properties b/src/main/resources/message_en.properties index a644c61c..290f40af 100644 --- a/src/main/resources/message_en.properties +++ b/src/main/resources/message_en.properties @@ -336,3 +336,12 @@ appointment.created.successfully = Appointment created successfully. error.try.again = Service call error while performing the operation. Please try again. document.uploading.is.in.progress = Document uploading is in progress. all.document.checked.and.one.checklist.checked=All document should be checked and at least one checklist should be checked. + +#notification messsages +notification.already.in.state=Notification is already in provided status. +notification.fetched.successfully=Notification fetched successfully. +notification.not.found=Notification not found. +notification.sent.successfully=Notification sent successfully. +notification.deleted.successfully=Notification deleted successfully. +notification.updated.successfully=Notification updated successfully. +user.with.company.not.found = User with company not found for user or company. diff --git a/src/main/resources/message_it.properties b/src/main/resources/message_it.properties index e350546e..f41e4f1c 100644 --- a/src/main/resources/message_it.properties +++ b/src/main/resources/message_it.properties @@ -2,14 +2,14 @@ user.created.success=Utente creato con successo. user.updated.success=Utente aggiornato con successo. user.deleted.success=Utente eliminato con successo. user.not.found=Utente non trovato. -create_user_error_msg=Si � verificato un errore durante la creazione dell'utente. -update_user_error_msg=Si � verificato un errore durante l'aggiornamento dell'utente. -delete_user_error_msg=Si � verificato un errore durante l'eliminazione dell'utente. +create_user_error_msg=Si ? verificato un errore durante la creazione dell'utente. +update_user_error_msg=Si ? verificato un errore durante l'aggiornamento dell'utente. +delete_user_error_msg=Si ? verificato un errore durante l'eliminazione dell'utente. get_user_success_msg=Utente recuperato con successo. -get_user_error_msg=Si � verificato un errore durante il recupero dell'utente. +get_user_error_msg=Si ? verificato un errore durante il recupero dell'utente. user.not.active=Utente non attivo. Si prega di contattare il supporto. -user.already.exist.msg=L'utente esiste gi� per questo codice fiscale. -validate.email=L'email � obbligatoria e deve essere nel formato corretto. Si prega di verificare e riprovare. +user.already.exist.msg=L'utente esiste gi? per questo codice fiscale. +validate.email=L'email ? obbligatoria e deve essere nel formato corretto. Si prega di verificare e riprovare. validate.password=La password e confPassword sono obbligatorie. Verifica e riprova. # Role-related messages role.created.success=Ruolo creato con successo. @@ -20,7 +20,7 @@ create.role.error=Errore durante la creazione del ruolo. update.role.error=Errore durante l'aggiornamento del ruolo. role.fetch.success=Ruolo recuperato con successo. delete.role.error=Errore durante l'eliminazione del ruolo. -role.id.mandatory=L'ID del ruolo � obbligatorio. +role.id.mandatory=L'ID del ruolo ? obbligatorio. # Region-related messages region.created.success=Regione creata con successo. @@ -35,42 +35,42 @@ password.doesnt.match=La password e la conferma della password non corrispondono #call related messages user.not.exist=L'utente non esiste. region.not.found=Regione non trovata. -user.id.not.null=L'ID utente non pu� essere nullo. -question.not.empty=La domanda non pu� essere vuota. -name.not.empty=Il nome non pu� essere vuoto. -type.not.empty=Il tipo non pu� essere vuoto. -region.not.null=L'ID regione non pu� essere nullo. +user.id.not.null=L'ID utente non pu? essere nullo. +question.not.empty=La domanda non pu? essere vuota. +name.not.empty=Il nome non pu? essere vuoto. +type.not.empty=Il tipo non pu? essere vuoto. +region.not.null=L'ID regione non pu? essere nullo. amount.greater.than.zero=L'importo del finanziamento deve essere maggiore di zero. -look.up.data.not.valid=L'entit� dati di ricerca non � valida. +look.up.data.not.valid=L'entit? dati di ricerca non ? valida. files.uploaded=File caricati correttamente. call.created.successfully=Chiamata creata correttamente. file.deleted.successfully=File eliminato con successo. document.not.found=Documento non trovato. document.id.not.found=ID documento non trovato. call.invalid.date=Data di inizio o fine non valida. -call.id.not.null=L'ID della chiamata non pu� essere nullo. +call.id.not.null=L'ID della chiamata non pu? essere nullo. call.update.successfully=Chiamata aggiornata con successo. call.fetch.success=Dettagli della chiamata recuperati con successo. call.not.found=Chiamata non trovata. -score.not.null=Il punteggio non pu� essere nullo o zero. -field.not.null={0} non pu� essere nullo. -field.not.empty=la {0} non pu� essere vuota. -update_call_status_success_msg=Lo stato della chiamata � stato aggiornato con successo. -status.same.error=Lo stato � gi� impostato. -invalid.status.change.from.draft=Lo stato non pu� essere cambiato in READY_TO_PUBLISH o PUBLISH da DRAFT. -status.cannot.be.changed=Lo stato non pu� essere cambiato. -published.call.not.update=Il bando pubblicato non pu� essere aggiornato. -invalid.status.change.from.publish=Lo stato non pu� essere modificato in READY_TO_PUBLISH o DRAFT da PUBLISH. +score.not.null=Il punteggio non pu? essere nullo o zero. +field.not.null={0} non pu? essere nullo. +field.not.empty=la {0} non pu? essere vuota. +update_call_status_success_msg=Lo stato della chiamata ? stato aggiornato con successo. +status.same.error=Lo stato ? gi? impostato. +invalid.status.change.from.draft=Lo stato non pu? essere cambiato in READY_TO_PUBLISH o PUBLISH da DRAFT. +status.cannot.be.changed=Lo stato non pu? essere cambiato. +published.call.not.update=Il bando pubblicato non pu? essere aggiornato. +invalid.status.change.from.publish=Lo stato non pu? essere modificato in READY_TO_PUBLISH o DRAFT da PUBLISH. # Login-related messages login.successfully=Accesso effettuato con successo. pass.min.len.msg=La password deve essere lunga almeno 8 caratteri. -email.already.exists=Esiste gi� un utente con questa email. +email.already.exists=Esiste gi? un utente con questa email. invalid_user=Validazione utente fallita. Controlla le informazioni, lo stato dell'account e la scadenza del token. #Global messages -common_message=qualcosa é andato storto. Per favore riprova +common_message=qualcosa é andato storto. Per favore riprova invalid_signature=Gettone non valido. invalid_login=Nome utente o password errati req_validation_er=Errore di convalida @@ -119,26 +119,26 @@ lookupdata.created.successfully=LookUpData creato correttamente. lookupdata.fetched.successfully=LookUpData recuperato correttamente. lookupdata.updated.successfully=LookUpData aggiornato correttamente. lookupdata.deleted.successfully=LookUpData eliminato correttamente. -lookupdata.value.cannot.be.empty=Il campo valore non pu� essere vuoto +lookupdata.value.cannot.be.empty=Il campo valore non pu? essere vuoto #Document-related message document.updated.successfully=Documento aggiornato con successo. document.fetched.successfully=Documento recuperato con successo. # Password reset messages password.reset.initiated=Reimpostazione della password avviata. -password.reset.success=La password � stata reimpostata con successo. -invalid.token.msg=Il token fornito � invalido o scaduto. Si prega di richiedere un nuovo token. -current.password.incorrect = La password attuale non � corretta. +password.reset.success=La password ? stata reimpostata con successo. +invalid.token.msg=Il token fornito ? invalido o scaduto. Si prega di richiedere un nuovo token. +current.password.incorrect = La password attuale non ? corretta. success.password.changed=Password cambiata con successo. logout.successful.msg=Logout riuscito. Sei stato disconnesso con successo. -update.user.status.success=Lo stato dell'utente � stato aggiornato con successo. +update.user.status.success=Lo stato dell'utente ? stato aggiornato con successo. #Flow-related message flow.created.successfully=Flusso creato con successo. flow.fetched.successfully=Flusso recuperato con successo. -flow.already.exists= Il flusso esiste gi� per questa chiamata. -flow.request.not.complete=La richiesta di flusso non � completa. +flow.already.exists= Il flusso esiste gi? per questa chiamata. +flow.request.not.complete=La richiesta di flusso non ? completa. initial.and.final.form.cannot.null=La forma iniziale e finale non possono essere nulle. # Application related messages @@ -149,25 +149,25 @@ application.get.success=Dettagli dell'applicazione recuperati con successo. application.not.found=Applicazione non trovata con l'ID fornito. application.form.field.not.found=Campo del modulo di domanda non trovato. Form.not.matches.to.call.initial.form=L'ID del modulo non corrisponde all'ID del modulo iniziale della chiamata. -application.already.exists=L'applicazione esiste gi� per questa chiamata. -application.already.submitted=La domanda � gi� stata inviata. +application.already.exists=L'applicazione esiste gi? per questa chiamata. +application.already.submitted=La domanda ? gi? stata inviata. #Validation related messages -validation.field.required=Il campo {0} � obbligatorio. +validation.field.required=Il campo {0} ? obbligatorio. validation.field.min_length=Il campo {0} deve essere lungo almeno {1} caratteri. validation.field.max_length=Il campo {0} deve essere lungo al massimo {1} caratteri. validation.field.pattern=Il campo {0} non corrisponde al modello richiesto. validation.field.not_null=Il campo {0} non deve essere nullo. validation.field.not_empty=Il campo {0} non deve essere vuoto. -current.form.incomplete=il modulo corrente non � compilato +current.form.incomplete=il modulo corrente non ? compilato flow.not.found=Flow not found. validation.message=Messaggi di convalida. action.required=Campo azione obbligatorio. -call.not.published=La chiamata non � stata pubblicata. +call.not.published=La chiamata non ? stata pubblicata. application.form.not.found=Modulo di domanda non trovato. -application.is.incomplete = L'applicazione � incompleta. -updating.form.value.impact.on.flow=L'aggiornamento di questo valore del modulo {0} pu� avere un impatto sul flusso. +application.is.incomplete = L'applicazione ? incompleta. +updating.form.value.impact.on.flow=L'aggiornamento di questo valore del modulo {0} pu? avere un impatto sul flusso. validation.field.custom=Il valore per il campo {0} non soddisfa la regola di convalida personalizzata. validation.codice.fiscale=Il campo {0} deve essere un Codice Fiscale valido con esattamente 16 caratteri: 6 lettere, 2 cifre, 1 lettera, 2 cifre, 1 lettera, 3 cifre e 1 lettera. @@ -178,13 +178,13 @@ validation.email.pec=Il campo {0} deve essere un indirizzo email PEC valido. validation.url=Il campo {0} deve essere un URL valido. validation.marca.da.bollo=Il campo {0} deve essere una Marca Da Bollo valida con esattamente 14 cifre. validation.piva=Il numero di partita IVA per {0} deve essere lungo fino a 11 cifre. -valid.vat.number=Il numero di partita IVA non � valido per il campo {0}. +valid.vat.number=Il numero di partita IVA non ? valido per il campo {0}. failed.retain.field=Impossibile conservare campi specifici. token.validate.success=Token convalidato con successo. invalid.request=Richiesta non valida. -codice.fiscale.exists=Questo codice fiscale � gi� associato ad un altro utente. +codice.fiscale.exists=Questo codice fiscale ? gi? associato ad un altro utente. -total.steps.not.zero=Il totale dei passaggi non pu� essere zero. +total.steps.not.zero=Il totale dei passaggi non pu? essere zero. completed.steps.not.valid=I passaggi completati devono essere compresi tra 0 e il totale dei passaggi. field.id.not.found=L'ID campo {0} non esiste nella struttura del modulo. company.created.success=Azienda creata con successo. @@ -194,37 +194,37 @@ company.get.success=Azienda recuperata con successo. company.not.found=Azienda non trovata. check.vatnumber.success=Numero di partita IVA verificato con successo. 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�. +vatnumber.mandatory=Il numero di partita IVA ? obbligatorio. +vatnumber.already.exists=Il numero di partita IVA esiste gi?. invalid.email=Email non valida. -validation.error.missing.firstName=Il nome � obbligatorio. -validation.error.missing.lastName=Il cognome � obbligatorio. -validation.error.missing.codiceFiscale=Il Codice Fiscale � obbligatorio. +validation.error.missing.firstName=Il nome ? obbligatorio. +validation.error.missing.lastName=Il cognome ? obbligatorio. +validation.error.missing.codiceFiscale=Il Codice Fiscale ? obbligatorio. delegation.file.upload.success=File di delega caricato con successo. delegation.fetch.success=Delega recuperata con successo. -delegation.template.generation.error=Si � verificato un errore durante la generazione del modello di delega. -validation.error.file.empty=Il file caricato � vuoto. +delegation.template.generation.error=Si ? verificato un errore durante la generazione del modello di delega. +validation.error.file.empty=Il file caricato ? vuoto. validation.error.file.invalidType=Sono accettati solo file .p7m. upload.error.s3=Impossibile caricare il file su S3. -company.id.mandatory=L'ID dell'azienda � obbligatorio. -user.already.connected.to.company=L'utente � gi� collegato a questa azienda. -call.not.started.yet = La chiamata non � ancora iniziata. Attendere fino alla data e all'ora di inizio specificate. -call.already.ended = La chiamata � gi� terminata. Non � possibile inviare l'applicazione dopo la scadenza. +company.id.mandatory=L'ID dell'azienda ? obbligatorio. +user.already.connected.to.company=L'utente ? gi? collegato a questa azienda. +call.not.started.yet = La chiamata non ? ancora iniziata. Attendere fino alla data e all'ora di inizio specificate. +call.already.ended = La chiamata ? gi? terminata. Non ? possibile inviare l'applicazione dopo la scadenza. status.updated.successfully=Stato aggiornato con successo. application.status.updated.successfully = Stato dell'applicazione aggiornato con successo. -application.already.in.provided.status=L'applicazione � gi� nello stato fornito. +application.already.in.provided.status=L'applicazione ? gi? nello stato fornito. delegation.not.found=Delega non trovata. user.company.relation.not.found=Relazione utente con l'azienda specificata non trovata. delegation.delete.success=Delega eliminata con successo. user.not.authorized.create.application=L'utente deve essere un rappresentante legale o avere una delega. -application.submitted.cannot.change=La domanda inviata non pu� essere modificata. +application.submitted.cannot.change=La domanda inviata non pu? essere modificata. # Call Document Messages call.documents.fetch.success=Documenti recuperati con successo. call.documents.not.found=Nessun documento trovato per la chiamata specificata. # Beneficiary Preferred Call messages -beneficiary.preferred.call.status.updated.success=Lo stato della chiamata preferita del beneficiario � stato aggiornato con successo. +beneficiary.preferred.call.status.updated.success=Lo stato della chiamata preferita del beneficiario ? stato aggiornato con successo. beneficiary.preferred.calls.get.all.success=Tutte le chiamate preferite del beneficiario sono state recuperate con successo. beneficiary.preferred.call.created.success=Chiamata preferita del beneficiario creata con successo. beneficiary.preferred.call.get.success=Chiamata preferita del beneficiario recuperata con successo. @@ -233,7 +233,7 @@ beneficiary.preferred.calls.get.success=Tutte le chiamate preferite del benefici beneficiary.preferred.call.updated.success=Chiamata preferita del beneficiario aggiornata con successo. beneficiary.preferred.call.not.found=Chiamata preferita del beneficiario non trovata. either.user.or.beneficiary.id.required = ID utente o ID beneficiario non presente. -userId.and.beneficiaryId.error = Non � possibile fornire contemporaneamente sia userId che beneficiaryId. +userId.and.beneficiaryId.error = Non ? possibile fornire contemporaneamente sia userId che beneficiaryId. User.not.found.with.the.given.beneficiaryID=Utente non trovato con l'ID beneficiario fornito. permission.denied=Non sei autorizzato ad accedere a questi dati. signed.document.file.upload.success=File del documento firmato caricato con successo. @@ -244,10 +244,10 @@ delete.signed.document.file.success=Documento firmato eliminato con successo. dashboard.widget.fetched.successfully=Widget dashboard recuperato correttamente. login_attempt_successfully_created= Tentativo di login creato con successo. get_login_attempt_se_msg=Lista dei tentativi di accesso recuperata correttamente. -application.in.submit.status.cannot.delete.company=Non � possibile eliminare l'azienda perch� ci sono domande attive con stato SUBMITTED. +application.in.submit.status.cannot.delete.company=Non ? possibile eliminare l'azienda perch? ci sono domande attive con stato SUBMITTED. get.users.success.msg = Utenti recuperati con successo -cannot.create.beneficiary.user = La creazione di un utente beneficiario non � consentita. Si prega di assegnare il ruolo appropriato. +cannot.create.beneficiary.user = La creazione di un utente beneficiario non ? consentita. Si prega di assegnare il ruolo appropriato. evaluationCriteria.invalid=Questo criterio di valutazione non appartiene alla chiamata corrente. application.evaluation.not.found=Valutazione dell'applicazione non trovata con ID: {0} @@ -258,11 +258,11 @@ evaluation.deleted.successfully = Valutazione dell'applicazione eliminata con su evaluations.fetched.successfully = Tutte le valutazioni delle applicazioni recuperate con successo. application.evaluation.status.updated.successfully=Stato della valutazione dell'applicazione aggiornato con successo. assigned.application.not.found.with.id=Applicazione assegnata con questo ID dell'applicazione non trovata -either.application.or.assigned.application.id.required=� richiesto almeno uno tra applicationId o assignedApplicationId. -evaluation.already.exists=Una valutazione dell'applicazione esiste gi� per questo ID applicazione. +either.application.or.assigned.application.id.required=? richiesto almeno uno tra applicationId o assignedApplicationId. +evaluation.already.exists=Una valutazione dell'applicazione esiste gi? per questo ID applicazione. application.assigned.success.msg =Domanda assegnata con successo -application.already.assigned.msg =La domanda � gi� assegnata +application.already.assigned.msg =La domanda ? gi? assegnata aasigned.application.not.found = Applicazione assegnata non trovata con l'ID specificato. assigned.application.deleted.success =Applicazione assegnata eliminata con successo. assigned.application.get.success =Dettagli dell'applicazione assegnata recuperati correttamente. @@ -277,7 +277,7 @@ hub_get_all_success=Hub recuperati con successo hub_delete_success=Hub eliminato con successo hub_not_found=Hub non trovato -application.not.in.draft.status=La domanda non � in stato DRAFT. +application.not.in.draft.status=La domanda non ? in stato DRAFT. get.error.s3=Impossibile recuperare il file da S3. application.data.amendment.success = Recupero riuscito dei dati dell'applicazione per il processo di modifica @@ -292,19 +292,19 @@ added.comment.to.amendment.request.success = Commento aggiunto con successo alla comment.not.found = Commento non trovato. comment.updated.successfully = Commento aggiornato con successo. comment.deleted.successfully = Commento eliminato con successo. -comment.not.associate.with.amendment = Il commento non � associato alla richiesta di emendamento. +comment.not.associate.with.amendment = Il commento non ? associato alla richiesta di emendamento. amendment.found.success = Richiesta di emendamento trovata con successo. invalid.amendment.for.comment = Richiesta di emendamento non valida per il commento fornito. DD_MM_YYYY_HH_MM = dd_MM_yyyy HH:mm create.application.data.amendment.msg =Emendamento alla domanda inviato con successo -beneficiary.email.not.found.msg=L'indirizzo email per il beneficiario non � stato trovato. Si prega di assicurarsi che il beneficiario abbia un indirizzo email valido. +beneficiary.email.not.found.msg=L'indirizzo email per il beneficiario non ? stato trovato. Si prega di assicurarsi che il beneficiario abbia un indirizzo email valido. reminder.email.sent.success.msg=Email di promemoria inviata con successo! application.documents.not.found=Nessun documento trovato per la domanda. -beneficiary.call.duplicate = Una chiamata preferita con questo ID di chiamata e ID azienda esiste gi� per questo utente. +beneficiary.call.duplicate = Una chiamata preferita con questo ID di chiamata e ID azienda esiste gi? per questo utente. user.must.be.associated.with.company.to.create.application=Devi essere associato a un'azienda per poter presentare domanda per questa applicazione. company.id.required.for.preferred.call=ID azienda obbligatorio quando si richiedono solo chiamate preferite. 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. +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 @@ -324,5 +324,14 @@ appointment.creation.is.only.for.gepafin = La creazione degli appuntamenti ? con 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. -document.uploading.is.in.progress = Il documento � in fase di caricamento. +document.uploading.is.in.progress = Il documento ? in fase di caricamento. all.document.checked.and.one.checklist.checked=Tutti i documenti devono essere controllati e almeno una checklist deve essere controllata. + +#notification messsages +notification.already.in.state=La notifica è già nello stato fornito. +notification.fetched.successfully=Notifica recuperata con successo. +notification.not.found=Notifica non trovata. +notification.sent.successfully=Notifica inviata con successo. +notification.deleted.successfully=Notifica eliminata con successo. +notification.updated.successfully=Notifica aggiornata con successo. +user.with.company.not.found = Utente con azienda non trovato per utente o azienda. \ No newline at end of file