# LogbookNotificationServiceImpl.java

    package cloud.wconnect.frontoffice.service.logbook.impl;

    import cloud.wconnect.commonlib.component.BeanMapper;
    import cloud.wconnect.commonlib.cons.*;
    import cloud.wconnect.commonlib.exception.BadRequestException;
    import cloud.wconnect.commonlib.pojo.dto.*;
    import cloud.wconnect.commonlib.pojo.dto.internationalization.InternationalizationDateTimeDTO;
    import cloud.wconnect.commonlib.pojo.dto.line.PublicFlexMsgParm;
    import cloud.wconnect.commonlib.redis.PropertyI18nRedisService;
    import cloud.wconnect.commonlib.service.NotifyInternalService;
    import cloud.wconnect.commonlib.service.UserInternalService;
    import cloud.wconnect.commonlib.service.UsergroupInternalService;
    import cloud.wconnect.commonlib.util.*;
    import cloud.wconnect.frontoffice.cons.FunctionPointEnum;
    import cloud.wconnect.frontoffice.cons.MoodEnum;
    import cloud.wconnect.frontoffice.cons.PriorityEnum;
    import cloud.wconnect.frontoffice.cons.logbook.LogbookCons;
    import cloud.wconnect.frontoffice.cons.logbook.LogbookNotifyEnum;
    import cloud.wconnect.frontoffice.entity.logbook.*;
    import cloud.wconnect.frontoffice.mapper.logbook.LogbookNotificationMapper;
    import cloud.wconnect.frontoffice.pojo.dto.logbook.LogbookNotificationDTO;
    import cloud.wconnect.frontoffice.pojo.dto.logbook.LogbookNotificationTargetDTO;
    import cloud.wconnect.frontoffice.pojo.query.logbook.LogbookNotificationQuery;
    import cloud.wconnect.frontoffice.pojo.vo.logbook.LogbookEmailVO;
    import cloud.wconnect.frontoffice.service.logbook.ILogbookCategoryService;
    import cloud.wconnect.frontoffice.service.logbook.ILogbookNotificationService;
    import cloud.wconnect.frontoffice.service.logbook.ILogbookNotificationTargetService;
    import com.alibaba.fastjson.JSONObject;
    import com.baomidou.dynamic.datasource.annotation.DS;
    import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
    import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
    import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
    import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    import com.google.common.collect.Lists;
    import com.google.common.collect.Maps;
    import org.apache.commons.collections.CollectionUtils;
    import org.apache.commons.lang3.StringUtils;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.context.annotation.Lazy;
    import org.springframework.context.i18n.LocaleContextHolder;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;
    import org.thymeleaf.TemplateEngine;
    import org.thymeleaf.context.Context;

    import java.util.*;
    import java.util.stream.Collectors;

    /**
     * <p>
     *  服务实现类
     * </p>
     *
     * @author yyt
     * @since 2023-03-18
     */
    @Service
    @DS("logbook")
    public class LogbookNotificationServiceImpl extends ServiceImpl<LogbookNotificationMapper, LogbookNotification> implements ILogbookNotificationService {

        @Autowired
        private ILogbookNotificationTargetService logbookNotificationTargetService;
        @Lazy
        @Autowired
        private ILogbookCategoryService logbookCategoryService;
        @Autowired
        private UserInternalService userInternalService;
        @Autowired
        private UsergroupInternalService usergroupInternalService;
        @Autowired
        private PropertyI18nRedisService propertyI18nRedisService;
        @Autowired
        private TemplateEngine templateEngine;
        @Autowired
        private BeanMapper beanMapper;
        @Autowired
        private NotifyInternalService notifyInternalService;
        @Autowired
        private LineMessageUtil lineMessageUtil;
        @Autowired
        private I18nMessageUtils i18nMessageUtils;

        @Value("${here.link.logbook.detail}")
        private String logbookDetailPage;

        /**
         * 查询通知
         *
         * @param propertyId 资产 id
         * @param category   category
         * @param operation  operation
         * @return 通知
         * @author yyt
         * @date 2023/3/20
         */
        @Override
        public List<LogbookNotification> listNotification(Integer propertyId, Integer category, int operation) {
            return baseMapper.listNotification(propertyId, category, operation);
        }

        /**
         * 异步处理发送邮件
         *
         * @param operation 操作
         * @param logbook   logbook 内容
         * @author yyt
         * @date 2023/3/21
         */
        @Override
        public void handleSendMailAsync(int operation, Logbook logbook) {
            List<LogbookNotification> logbookNotifications = listNotification(logbook.getPropertyId(), logbook.getCategory(),
                    operation).stream().filter(LogbookNotification::getStatus).toList();
            if (logbookNotifications.isEmpty()) {
                return;
            }

            // 查询邮件配置用户，发送邮件
            List<Integer> notificationIds = logbookNotifications.stream().map(LogbookNotification::getId).collect(Collectors.toList());
            Map<Integer, List<LogbookNotificationTarget>> notificationTargetById =
                    logbookNotificationTargetService.getBaseMapper()
                            .selectList(new QueryWrapper<LogbookNotificationTarget>().lambda()
                                    .in(LogbookNotificationTarget::getNotificationId, notificationIds))
                            .stream().collect(Collectors.groupingBy(LogbookNotificationTarget::getNotificationId));

            Integer propertyId = logbook.getPropertyId();

            for (LogbookNotification notification : logbookNotifications) {
                NotifyDTO notifyDTO = new NotifyDTO();
                notifyDTO.setPropertyId(propertyId);
                notifyDTO.setFunctionPoint(FunctionPointEnum.LOGBOOK_OPERATION.getFunctionPoint());

                String subject = notification.getSubject();
                if (StringUtils.isBlank(subject)) {
                    subject = getDefaultSubject(propertyId, operation);
                }
                notifyDTO.setSubject(subject);

                List<LogbookNotificationTarget> emailTargets =
                        notificationTargetById.getOrDefault(notification.getId(), Collections.emptyList());
                notifyDTO.setIdentityList(beanMapper.mapList(emailTargets, NotifyIdentityDTO.class));

                JSONObject msg = generateLineMsg(logbook, operation);
                notifyDTO.setLineContent(Lists.newArrayList(msg));

                notifyDTO.setEmailContent(packageEmailContent(operation, logbook));
                notifyInternalService.sendNotify(notifyDTO);
            }
        }

        /**
         * 创建 line msg
         *
         * @param logbook   logbook
         * @param operation 操作（create、resolve、comment）
         * @return line msg
         * @author yyt
         * @date 2023/11/29
         */
        private JSONObject generateLineMsg(Logbook logbook, int operation) {
            LinkedHashMap<String, String> map = Maps.newLinkedHashMapWithExpectedSize(3);
            map.put("Guest Name", StringUtils.isEmpty(logbook.getCustomer()) ? "--" : logbook.getCustomer());
            map.put("Room Number", StringUtils.isEmpty(logbook.getRoom()) ? "--" : logbook.getRoom());
            map.put("Content", logbook.getContent());

            PropertyDTO property = propertyI18nRedisService.get(logbook.getPropertyId());
            String title = getLineTile(operation);
            return lineMessageUtil.generatePublicFlexMsg(new PublicFlexMsgParm(property.getName(), logbook.getPropertyId(),
                    i18nMessageUtils.message(FunctionCons.LOGBOOK, property.getLanguage()),
                    title, logbookDetailPage + "?id=" + logbook.getId() + "&property=" + property.getAbbr() +
                    "&tenant=" + property.getTenantSymbol(), map));
        }

        /**
         * line title
         *
         * @param operation 操作
         * @return line title
         * @author yyt
         * @date 2024/11/14
         **/
        private String getLineTile(int operation) {
            String title = "New logbook";
            if (operation == LogbookNotifyEnum.OPERATION_COMMENT.getCode()) {
                title = "New comment";
            } else if (operation == LogbookNotifyEnum.OPERATION_RESOLVED.getCode()) {
                title = "Resolve";
            }
            return title;
        }

        /**
         * 封装邮件内容
         *
         * @param operation 操作
         * @param logbook logbook
         * @return 邮件内容
         * @author yyt
         * @date 2023/3/21
         */
        private String packageEmailContent(int operation, Logbook logbook) {
            LogbookEmailVO emailVO = new LogbookEmailVO();
            emailVO.setCustomer(StringUtils.isEmpty(logbook.getCustomer()) ? "--" : logbook.getCustomer());
            emailVO.setRoom(StringUtils.isEmpty(logbook.getRoom()) ? "--" : logbook.getRoom());

            Integer propertyId = logbook.getPropertyId();
            PropertyDTO propertyDTO = propertyI18nRedisService.get(propertyId);
            String timezone = propertyDTO.getTimezone();
            InternationalizationDateTimeDTO dateTime = propertyDTO.getInternationalization().getDateTime();

            emailVO.setSendTime(InternationalizationUtils.formatDateTime(logbook.getCreateTime(), dateTime.getLongDate(),
                    dateTime.getHourClock(), timezone, propertyDTO.getLanguage()));

            LogbookCategory category = logbookCategoryService.getById(logbook.getCategory());
            emailVO.setCategory(category.getName());

            String language = propertyDTO.getLanguage();
            emailVO.setPriority(Objects.isNull(logbook.getPriority()) ? "--" : i18nMessageUtils.message(PriorityEnum.getInfoByCode(logbook.getPriority()), language));
            emailVO.setPriorityInt(logbook.getPriority());
            emailVO.setMood(Objects.isNull(logbook.getMood()) ? "--" : i18nMessageUtils.message(MoodEnum.getInfoByCode(logbook.getMood()), language));
            emailVO.setContent(EmailUtil.changeLine(logbook.getContent()));
            List<Integer> userIds = Lists.newArrayListWithExpectedSize(3);
            userIds.add(logbook.getCreateId());
            Integer complateId = logbook.getComplateId();
            if (Objects.nonNull(complateId) && complateId > 0) {
                userIds.add(complateId);
            }
            if (Objects.nonNull(logbook.getComment())) {
                userIds.add(logbook.getComment().getCreateId());
            }
            Map<Integer, String> userNameById = userInternalService.getNameMapByIds(propertyDTO.getTenantId(), userIds);
            emailVO.setSender(userNameById.get(logbook.getCreateId()));
            emailVO.setOperation(operation);

            if (Objects.nonNull(complateId) && complateId > 0) {
                emailVO.setResolveUserName(userNameById.get(complateId));
                emailVO.setResolveTime(InternationalizationUtils.formatDateTime(logbook.getComplateTime(),
                        dateTime.getLongDate(), dateTime.getHourClock(), timezone, propertyDTO.getLanguage()));
            } else {
                emailVO.setResolveTime("--");
                emailVO.setResolveUserName("--");
            }

            switch (operation) {
                case LogbookNotification.OPERATION_COMMENT:
                    LogbookComment comment = logbook.getComment();
                    emailVO.setCommentUserName(userNameById.get(comment.getCreateId()));
                    emailVO.setCommentTime(InternationalizationUtils.formatDateTime(comment.getCreateTime(),
                                    dateTime.getLongDate(), dateTime.getHourClock(), timezone, propertyDTO.getLanguage()));
                    emailVO.setCommentContent(comment.getCommentType() == LogbookComment.COMMENT_TYPE_WORD ? EmailUtil.changeLine(comment.getContent()) : comment.getContent());
                    emailVO.setCommentType(comment.getCommentType());
                    break;
                default:
            }

            Context context = new Context();
            context.setVariable("emailVO", emailVO);
            context.setVariable("propertyName", propertyDTO.getName());
            context.setVariable("functionName", i18nMessageUtils.message(FunctionCons.LOGBOOK, language));
            context.setVariable("hereLink", logbookDetailPage + "?id=" + logbook.getId() + "&property=" + propertyDTO.getAbbr() +
                    "&tenant=" + propertyDTO.getTenantSymbol());

            String template = "logbook/logbook-new_" + language;
            if (operation == LogbookNotifyEnum.OPERATION_COMMENT.getCode()) {
                template = "logbook/logbook-comment_" + language;
            }
            if (operation == LogbookNotifyEnum.OPERATION_RESOLVED.getCode()) {
                template = "logbook/logbook-resolve_" + language;
            }
            return EmailUtil.getFullEmailContent(getDefaultSubject(propertyId, operation), templateEngine.process(template, context), language);
        }

        /**
         * 新增 notification
         *
         * @param propertyId      资产 id
         * @param notificationDTO 配置内容
         * @param userId          操作人
         * @return notification id
         * @author yyt
         * @date 2023/3/22
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public Integer saveNotification(int propertyId, LogbookNotificationDTO notificationDTO, int userId) {
            LogbookNotification notification = beanMapper.map(notificationDTO, LogbookNotification.class);
            notification.setPropertyId(propertyId);
            notification.setUpdateBy(userId);
            notification.setUpdateTime(DateTimeUtils.nowTimestamp());
            save(notification);

            Integer notificationId = notification.getId();
            logbookNotificationTargetService.saveBatch(notificationId, propertyId, notificationDTO.getUsers());

            return notificationId;
        }

        /**
         * 校验通知存在
         *
         * @param id 通知 id
         * @return 通知
         * @author yyt
         * @date 2023/3/22
         */
        @Override
        public LogbookNotification checkExist(int id) {
            LogbookNotification notification = getById(id);
            if (Objects.isNull(notification)) {
                throw new BadRequestException(CommonErrorI18nConstant.DATA_NOT_FOUND,
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(), "the logbookNotification is not found;id:" + id);
            }
            return notification;
        }

        /**
         * 修改 notification
         *
         * @param updateBeforeLogbookNotification 修改之前的通知
         * @param notificationDTO                 配置内容
         * @param userId                          操作人
         * @author yyt
         * @date 2023/3/22
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void updateNotification(LogbookNotification updateBeforeLogbookNotification, LogbookNotificationDTO notificationDTO, int userId) {
            LogbookNotification updateContent = beanMapper.map(notificationDTO, LogbookNotification.class);
            updateContent.setId(updateBeforeLogbookNotification.getId());
            updateContent.setUpdateBy(userId);
            updateContent.setUpdateTime(DateTimeUtils.nowTimestamp());
            updateById(updateContent);

            logbookNotificationTargetService.clearNotificationTarget(updateBeforeLogbookNotification.getId());

            logbookNotificationTargetService.saveBatch(updateBeforeLogbookNotification.getId(), updateBeforeLogbookNotification.getPropertyId(),
                    notificationDTO.getUsers());
        }

        /**
         * 删除通知
         *
         * @param id 通知 id
         * @author yyt
         * @date 2023/3/22
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void deleteNotification(int id) {
            removeById(id);
            logbookNotificationTargetService.clearNotificationTarget(id);
        }

        /**
         * 分页查询数据
         *
         * @param propertyId 资产 id
         * @param query      查询条件
         * @return 通知配置
         * @author yyt
         * @date 2024/11/13
         **/
        @Override
        public Page<LogbookNotification> listPage(Integer propertyId, LogbookNotificationQuery query) {
            LambdaQueryWrapper<LogbookNotification> queryWrapper = new QueryWrapper<LogbookNotification>().lambda()
                    .eq(LogbookNotification::getPropertyId, propertyId);
            if (Objects.nonNull(query.getCategory())) {
                queryWrapper.eq(LogbookNotification::getCategory, query.getCategory());
            }
            return page(new Page<>(query.getPage(), query.getSize()), queryWrapper.orderByDesc(LogbookNotification::getId));
        }

        /**
         * 获取邮件通知默认主题
         *
         * @param propertyName 资产名称
         * @param locale       多语言
         * @return 默认主题
         * @author yyt
         * @date 2024/11/13
         **/
        @Override
        public String getDefaultSubject(String propertyName, Locale locale) {
            return i18nMessageUtils.message(LogbookCons.DEFAULT_SUBJECT, locale,
                    i18nMessageUtils.message(FunctionCons.LOGBOOK, locale), propertyName);
        }

        /**
         * 根据操作类型，获取邮件通知默认主题
         *
         * @param propertyId 资产 id
         * @param operation 操作
         * @return 默认主题
         * @author yyt
         * @date 2024/11/13
         **/
        @Override
        public String getDefaultSubject(Integer propertyId, int operation) {
            PropertyDTO propertyDTO = propertyI18nRedisService.get(propertyId);
            String language = propertyDTO.getLanguage();
            return i18nMessageUtils.message(LogbookNotifyEnum.getI18nCode(operation), language,
                    i18nMessageUtils.message(FunctionCons.LOGBOOK, language), propertyDTO.getName());
        }

        /**
         * 获取通知配置
         *
         * @param propertyId 资产 id
         * @param query      查询条件
         * @return 通知配置
         * @author yyt
         * @date 2026/1/16
         **/
        @Override
        public ListPagedDTO<LogbookNotificationDTO> listNotificationDTO(int propertyId, LogbookNotificationQuery query) {
            PropertyDTO propertyDTO = propertyI18nRedisService.get(propertyId);
            Locale locale = LocaleContextHolder.getLocale();
            String defaultSubject = getDefaultSubject(propertyDTO.getName(), locale);
            Page<LogbookNotification> notificationPage = listPage(propertyId, query);
            List<Integer> notificationIds = notificationPage.getRecords().stream().map(LogbookNotification::getId)
                    .collect(Collectors.toList());
            if (CollectionUtils.isEmpty(notificationIds)) {
                return new ListPagedDTO<>(BaseConstant.ZERO, Lists.newArrayList(new LogbookNotificationDTO(defaultSubject)));
            }

            List<LogbookNotificationTarget> notificationTargets = logbookNotificationTargetService.listByNotificationIds(notificationIds);

            List<Integer> userIds = notificationTargets.stream().filter(item -> item.getSource() == EmailConfigCons.SOURCE_USER)
                    .map(LogbookNotificationTarget::getIdentityId).distinct().toList();
            List<Integer> userGroupIds = notificationTargets.stream().filter(item -> item.getSource() == EmailConfigCons.SOURCE_USERGROUP)
                    .map(LogbookNotificationTarget::getIdentityId).distinct().toList();
            Integer tenantId = propertyDTO.getTenantId();
            Map<Integer, UserDTO> userById = userInternalService.getUserMapByIds(tenantId, userIds);
            Map<Integer, String> userGroupNameById = usergroupInternalService.getUserGroupNameByIds(tenantId, propertyId, userGroupIds);
            notificationTargets.forEach(item -> {
                if (item.getSource() == EmailConfigCons.SOURCE_USER) {
                    item.setIdentityName(userById.get(item.getIdentityId()).getName());
                    item.setEmail(userById.get(item.getIdentityId()).getEmail());
                }
                if (item.getSource() == EmailConfigCons.SOURCE_USERGROUP) {
                    item.setIdentityName(userGroupNameById.get(item.getIdentityId()));
                }
            });

            Map<Integer, List<LogbookNotificationTarget>> notificationTargetsById = notificationTargets.stream()
                    .collect(Collectors.groupingBy(LogbookNotificationTarget::getNotificationId));

            return new ListPagedDTO<>(notificationPage.getTotal(), beanMapper.mapList(notificationPage.getRecords(), LogbookNotificationDTO.class,
                    item -> {
                        item.setUsers(beanMapper.mapList(notificationTargetsById.get(item.getId()), LogbookNotificationTargetDTO.class));
                        item.setDefaultSubject(defaultSubject);
                    }));
        }

    }