package net.gepafin.tendermanagement.dao; import jakarta.persistence.criteria.CriteriaBuilder; import jakarta.persistence.criteria.Predicate; import jakarta.persistence.criteria.Root; import lombok.extern.slf4j.Slf4j; import net.gepafin.tendermanagement.config.Translator; import net.gepafin.tendermanagement.constants.GepafinConstant; import net.gepafin.tendermanagement.entities.*; 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.request.NotificationRequestBean; import net.gepafin.tendermanagement.model.response.NotificationResponse; import net.gepafin.tendermanagement.model.response.PageableResponseBean; import net.gepafin.tendermanagement.model.util.SortBy; 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.service.CompanyService; 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.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.jpa.domain.Specification; 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.Objects; import java.util.stream.Collectors; import static org.apache.commons.lang3.StringUtils.isEmpty; 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; @Autowired private CompanyService companyService; 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() || companyIds.stream().allMatch(Objects::isNull)) { 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()); 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(), null); 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(), null); sendNotification(notificationreq); } } public void sendNotificationToInstructorManager(Map placeHolders, ApplicationEvaluationEntity applicationEvaluationEntity, NotificationTypeEnum notificationTypeEnum) { List userEntities=userRepository.findByRoleEntity_RoleTypeAndHubId(RoleStatusEnum.ROLE_INSTRUCTOR_MANAGER.getValue(),applicationEvaluationEntity.getAssignedApplicationsEntity().getApplication().getHubId()); for (UserEntity user:userEntities) { Long instructorId=user.getId(); ApplicationEntity application = applicationService.validateApplication(applicationEvaluationEntity.getApplicationId()); if (instructorId != null) { NotificationReq notificationreq = createNotificationReq(notificationTypeEnum.getValue(), placeHolders, instructorId, application.getUserWithCompany(), null); 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, List statuses) { List notificationEntities = notificationRepository.findByUserIdAndIsDeletedFalse(userId); UserWithCompanyEntity userWithCompany = null; List statusStrings; if (companyId != null) { userWithCompany = companyDao.validateUserWithCompny(userId, companyId); } if (statuses != null) { statusStrings = statuses.stream().map(NotificationEnum::name) .toList(); notificationEntities = notificationRepository.findByUserIdAndIsDeletedFalseAndStatusIn(userId, statusStrings); if (userWithCompany != null) { notificationEntities = notificationRepository.findByUserIdAndUserWithCompanyIdAndIsDeletedFalseAndStatusIn(userId, userWithCompany.getId(), statusStrings); } } return notificationEntities.stream().map(this::convertNotificationEntityToNotificationResponse).toList(); } public PageableResponseBean> getAllNotification(Long userId, NotificationRequestBean notificationRequestBean) { Integer pageNo = null; Integer pageLimit = null; if (notificationRequestBean.getGlobalFilters() != null) { pageNo = notificationRequestBean.getGlobalFilters().getPage(); pageLimit = notificationRequestBean.getGlobalFilters().getLimit(); } if (pageLimit == null || pageLimit <= 0) { pageLimit = GepafinConstant.DEFAULT_PAGE_LIMIT; } if (pageNo == null || pageNo <= 0) { pageNo = GepafinConstant.DEFAULT_PAGE; } Specification spec = search(userId, notificationRequestBean); Page entityPage = notificationRepository.findAll(spec, PageRequest.of(pageNo - 1, pageLimit)); // Prepare the response List notificationResponses = entityPage.getContent().stream() .map(notification -> { NotificationResponse response = convertNotificationEntityToNotificationResponse(notification); return response; }) .collect(Collectors.toList()); PageableResponseBean> pageableResponseBean = new PageableResponseBean<>(); pageableResponseBean.setBody(notificationResponses); pageableResponseBean.setCurrentPage(entityPage.getNumber() + 1); // Page numbers typically start from 0, so add 1 for user-friendly indexing pageableResponseBean.setTotalPages(entityPage.getTotalPages()); pageableResponseBean.setTotalRecords(entityPage.getTotalElements()); pageableResponseBean.setPageSize(entityPage.getSize()); return pageableResponseBean; } public Specification search(Long userId, NotificationRequestBean notificationRequestBean) { return (root, query, criteriaBuilder) -> { List predicates = getPredicates(notificationRequestBean, criteriaBuilder, root, userId); SortBy sortBy = new SortBy(GepafinConstant.CREATED_DATE, true); if (notificationRequestBean.getGlobalFilters() != null && notificationRequestBean.getGlobalFilters().getSortBy() != null && notificationRequestBean.getGlobalFilters().getSortBy().getColumnName() != null && Boolean.FALSE.equals( isEmpty(notificationRequestBean.getGlobalFilters().getSortBy().getColumnName()))) { sortBy.setColumnName(notificationRequestBean.getGlobalFilters().getSortBy().getColumnName()); sortBy.setSortDesc(true); if (notificationRequestBean.getGlobalFilters().getSortBy().getSortDesc() != null) { sortBy.setSortDesc(notificationRequestBean.getGlobalFilters().getSortBy().getSortDesc()); } } query.orderBy(criteriaBuilder.desc(root.get(sortBy.getColumnName()))); if (Boolean.FALSE.equals(sortBy.getSortDesc())) { query.orderBy(criteriaBuilder.asc(root.get(sortBy.getColumnName()))); } return query.where(criteriaBuilder.and(predicates.toArray(new Predicate[0]))).getRestriction(); }; } private List getPredicates(NotificationRequestBean notificationRequestBean, CriteriaBuilder criteriaBuilder, Root root, Long userId) { Integer year = null; String search = null; if (notificationRequestBean.getGlobalFilters() != null) { year = notificationRequestBean.getGlobalFilters().getYear(); search = notificationRequestBean.getGlobalFilters().getSearch(); } List predicates = new ArrayList<>(); if (year != null && year > 0) { int filterYear = notificationRequestBean.getGlobalFilters().getYear(); // Create LocalDateTime boundaries for the start and end of the year LocalDateTime startOfYear = LocalDateTime.of(filterYear, 1, 1, 0, 0); LocalDateTime endOfYear = LocalDateTime.of(filterYear, 12, 31, 23, 59, 59); // Add the range comparison to filter records within the year predicates.add(criteriaBuilder.between(root.get(GepafinConstant.CREATED_DATE), startOfYear, endOfYear)); } // Search in `title` and `message` (if search term is provided) if (search != null && !search.isEmpty()) { Predicate titlePredicate = criteriaBuilder.like( criteriaBuilder.upper(root.get(GepafinConstant.TITLE)), "%" + search.toUpperCase() + "%" ); Predicate messagePredicate = criteriaBuilder.like( criteriaBuilder.upper(root.get(GepafinConstant.MESSAGE)), "%" + search.toUpperCase() + "%" ); predicates.add(criteriaBuilder.or(titlePredicate, messagePredicate)); } // Filter by `status` (if status list is provided) if (notificationRequestBean.getStatus() != null && !notificationRequestBean.getStatus().isEmpty()) { List statusValues = notificationRequestBean.getStatus().stream() .map(NotificationEnum::name) // Convert enum to string .toList(); predicates.add(root.get(GepafinConstant.STATUS).in(statusValues)); } predicates.add(criteriaBuilder.isFalse(root.get(GepafinConstant.IS_DELETED))); predicates.add(criteriaBuilder.equal(root.get(GepafinConstant.USER_ID), userId)); return predicates; } public void sendNotificationToBeneficiaryForDocumentExpiration(CompanyDocumentEntity companyDocumentEntity, NotificationTypeEnum notificationTypeEnum) { CompanyEntity companyEntity = companyService.validateCompany(companyDocumentEntity.getCompanyId()); Map placeHolders = new HashMap<>(); placeHolders.put("{{file_name}}", companyDocumentEntity.getFileName()); placeHolders.put("{{company_name}}", companyEntity.getCompanyName()); placeHolders.put("{{expiration_date}}", companyDocumentEntity.getExpirationDate().toString()); NotificationReq notificationReq = createNotificationReq(notificationTypeEnum.getValue(), placeHolders, companyDocumentEntity.getUserWithCompany().getUserId(), companyDocumentEntity.getUserWithCompany(), listOf(companyDocumentEntity.getCompanyId())); sendNotification(notificationReq); } public PageableResponseBean> getNotificationsByUserIdAndCompanyIdByPagination(Long userId, Long companyId, NotificationRequestBean notificationRequestBean) { UserWithCompanyEntity userWithCompany; if (companyId != null) { userWithCompany = companyDao.validateUserWithCompny(userId, companyId); }else { userWithCompany=null; } Integer pageNo = null; Integer pageLimit = null; if (notificationRequestBean.getGlobalFilters() != null) { pageNo = notificationRequestBean.getGlobalFilters().getPage(); pageLimit = notificationRequestBean.getGlobalFilters().getLimit(); } if (pageLimit == null || pageLimit <= 0) { pageLimit = GepafinConstant.DEFAULT_PAGE_LIMIT; } if (pageNo == null || pageNo <= 0) { pageNo = GepafinConstant.DEFAULT_PAGE; } Specification spec = search(userId, notificationRequestBean); if(userWithCompany!=null){ spec = spec.and((root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get("userWithCompany").get("id"), userWithCompany.getId())); } Page entityPage = notificationRepository.findAll(spec, PageRequest.of(pageNo - 1, pageLimit)); // Prepare the response List notificationResponses = entityPage.getContent().stream() .map(notification -> { NotificationResponse response = convertNotificationEntityToNotificationResponse(notification); return response; }) .collect(Collectors.toList()); PageableResponseBean> pageableResponseBean = new PageableResponseBean<>(); pageableResponseBean.setBody(notificationResponses); pageableResponseBean.setCurrentPage(entityPage.getNumber() + 1); // Page numbers typically start from 0, so add 1 for user-friendly indexing pageableResponseBean.setTotalPages(entityPage.getTotalPages()); pageableResponseBean.setTotalRecords(entityPage.getTotalElements()); pageableResponseBean.setPageSize(entityPage.getSize()); return pageableResponseBean; } }