# ServiceRequestOperaRequestServiceImpl.java

    package cloud.wconnect.it.service.servicerequestcenter.impl;

    import cloud.wconnect.commonlib.component.BeanMapper;
    import cloud.wconnect.commonlib.cons.BaseConstant;
    import cloud.wconnect.commonlib.cons.LanguageEnum;
    import cloud.wconnect.commonlib.pojo.dto.*;
    import cloud.wconnect.commonlib.pojo.dto.internationalization.InternationalizationDateTimeDTO;
    import cloud.wconnect.commonlib.service.NotifyInternalService;
    import cloud.wconnect.commonlib.service.TenantInternalService;
    import cloud.wconnect.commonlib.service.UserInternalService;
    import cloud.wconnect.commonlib.util.*;
    import cloud.wconnect.it.cons.FunctionPointEnum;
    import cloud.wconnect.it.cons.servicerequestcenter.*;
    import cloud.wconnect.it.entity.servicerequestcenter.ServiceRequestCenter;
    import cloud.wconnect.it.entity.servicerequestcenter.ServiceRequestCenterLog;
    import cloud.wconnect.it.entity.servicerequestcenter.ServiceRequestCenterProperty;
    import cloud.wconnect.it.pojo.bo.servicerequestcenter.ServiceRequestCenterNotifyBodyBO;
    import cloud.wconnect.it.pojo.dto.servicerequestcenter.*;
    import cloud.wconnect.it.pojo.vo.servicerequestcenter.ServiceRequestCenterEmailVO;
    import cloud.wconnect.it.service.servicerequestcenter.*;
    import cloud.wconnect.it.service.servicerequestcenter.notify.IServiceRequestCenterNotifyService;
    import com.alibaba.fastjson.JSONObject;
    import com.baomidou.dynamic.datasource.annotation.DS;
    import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.google.common.collect.Lists;
    import com.google.common.collect.Sets;
    import com.google.common.util.concurrent.RateLimiter;
    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.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;
    import org.thymeleaf.TemplateEngine;
    import org.thymeleaf.context.Context;

    import java.time.format.DateTimeFormatter;
    import java.util.*;
    import java.util.stream.Collectors;

    /**
     *
     * @author: yyt
     * 2025-12-18
     */
    @Service
    @DS("it_manage")
    public class ServiceRequestOperaRequestServiceImpl implements IServiceRequestOperaRequestService {

        @Autowired
        private IServiceRequestCenterLogService serviceRequestCenterLogService;
        @Autowired
        private IServiceRequestCenterService serviceRequestCenterService;
        @Autowired
        private IServiceRequestCenterNotifyService serviceRequestCenterChangeStatusOperation;
        @Autowired
        private IServiceRequestCenterNotifyService serviceRequestCenterChangeDutyUserOperation;
        @Autowired
        private IServiceRequestCenterNotifyService serviceRequestCenterChangeTypeOperation;
        @Autowired
        private IServiceRequestCenterNotifyConfigService serviceRequestCenterNotifyConfigService;
        @Autowired
        private IServiceRequestCenterPropertyService serviceRequestCenterPropertyService;
        @Autowired
        private UserInternalService userInternalService;
        @Autowired
        private NotifyInternalService notifyInternalService;
        @Autowired
        private TenantInternalService tenantInternalService;
        @Autowired
        private I18nMessageUtils i18nMessageUtils;
        @Autowired
        private TemplateEngine templateEngine;
        @Autowired
        private BeanMapper beanMapper;
        @Autowired
        private ObjectMapper objectMapper;

        @Value("${default.timezone}")
        private String timezone;

        /**
         * 限制速率（每秒5次）
         */
        private final RateLimiter rateLimiter = RateLimiter.create(5.0);

        /**
         * 保存 request
         *
         * @param tenantId       租户 id
         * @param propertyId     资产 id
         * @param saveRequestDTO request 信息
         * @param userId         操作人
         * @author yyt
         * @date 2025/12/16
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void save(int tenantId, int propertyId, SaveRequestDTO saveRequestDTO, int userId) {
            DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(BaseConstant.DATE_FORMAT_YYYYMMDD);
            // 保存 request
            long now = DateTimeUtils.nowTimestamp();
            ServiceRequestCenter request = beanMapper.map(saveRequestDTO, ServiceRequestCenter.class);
            request.setTenantId(tenantId)
                    // 设置 service 编号（TKT-yyyyMMdd0001）
                    .setServiceNumber(generateServiceNumber(tenantId))
                    .setCreatedBy(userId)
                    .setCreatedDate(Integer.parseInt(dateTimeFormatter.format(DateTimeUtils.dateNow(timezone))))
                    .setCreatedTime(now)
                    .setPendingAllocationTime(now);
            serviceRequestCenterService.save(request);

            Integer requestId = request.getId();

            // 维护资产关联关系
            serviceRequestCenterPropertyService.saveProperty(requestId, saveRequestDTO.getProperties());

            // 保存操作 log
            serviceRequestCenterLogService.save(requestId, ServiceRequestCenterLogCons.ADD, null, userId);
        }

        /**
         * 处理 propertyId
         *
         * @param propertyId     资产id
         * @param saveRequestDTO 保存 request dto
         * @return propertyId
         * @author yyt
         * @date 2026/1/8
         */
        private Integer handlePropertyId(int propertyId, SaveRequestDTO saveRequestDTO) {
            // 财务类型，则使用默认资产
            if (saveRequestDTO.getType() == ServiceRequestCenterTypeEnum.ACCOUNT.getCode()) {
                return propertyId;
            }

            // 其他类型，则使用页面选择的资产id（目前页面只能选择一个资产）
            return JsonUtils.convertStringToList(objectMapper, saveRequestDTO.getProperties(), PropertyBasicDTO.class).get(0).getId();
        }

        /**
         * 生成 service number
         *
         * @param tenantId 租户 id
         * @return service number
         * @author yyt
         * @date 2025/12/16
         */
        private String generateServiceNumber(int tenantId) {
            // 格式：TKT-yyyyMMdd0001
            // 前缀：TKT-
            String prefix = "TKT-";
            int padLength = 4;
            String date = DateTimeUtils.localDateToStr(DateTimeUtils.dateNow(timezone).toLocalDate(), BaseConstant.DATE_FORMAT_YYYYMMDD);
            // 查询最大 service number
            String maxServiceNumber = serviceRequestCenterService.selectMaxServiceNumber(tenantId, date);
            if (Objects.isNull(maxServiceNumber)) {
                return prefix + date + StringUtils.leftPad(String.valueOf(1), padLength, "0");
            }

            String indexStr = StringUtils.substring(maxServiceNumber, prefix.length() + date.length());
            int index = Integer.parseInt(indexStr) + 1;
            return prefix + date + StringUtils.leftPad(String.valueOf(index), padLength, "0");
        }

        /**
         * 关闭 request
         *
         * @param request request
         * @author yyt
         * @date 2025/12/17
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void closeRequest(ServiceRequestCenter request) {
            Integer id = request.getId();
            Integer createdBy = request.getCreatedBy();
            // 状态置关闭
            serviceRequestCenterService.update(new UpdateWrapper<ServiceRequestCenter>().lambda()
                    .eq(ServiceRequestCenter::getId, id)
                    .set(ServiceRequestCenter::getStatus, ServiceRequestCenterStatusEnum.CLOSED.getCode())
                    .set(ServiceRequestCenter::getClosedBy, createdBy)
                    .set(ServiceRequestCenter::getClosedTime, DateTimeUtils.nowTimestamp()));

            // 保存操作 log
            String expand = JsonUtils.convertObjToString(objectMapper, new ChangeValueBasicDTO(request.getStatus(), ServiceRequestCenterStatusEnum.CLOSED.getCode()));
            serviceRequestCenterLogService.save(id, ServiceRequestCenterLogCons.CHANGE_STATUS, expand, createdBy);
        }

        /**
         * 变更 request 状态
         *
         * @param tenantId        租户 id
         * @param request         request
         * @param changeStatusDTO 状态信息
         * @param userId          操作人
         * @author yyt
         * @date 2025/12/18
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void changeStatus(int tenantId, ServiceRequestCenter request, ChangeRequestStatusDTO changeStatusDTO, int userId) {
            Integer requestId = request.getId();
            Integer newStatus = changeStatusDTO.getNewStatus();

            ServiceRequestCenter requestCenter = new ServiceRequestCenter();
            requestCenter.setId(requestId);
            requestCenter.setStatus(newStatus);
            // 若用户第一次改状态，则记录第一次变为【In-progress】时间
            if (request.getStatus() == ServiceRequestCenterStatusEnum.OPEN.getCode()
                    && Lists.newArrayList(ServiceRequestCenterStatusEnum.IN_PROGRESS.getCode(),ServiceRequestCenterStatusEnum.RESOLVED.getCode()).contains(newStatus)
                    && Objects.isNull(request.getFirstInProgressTime())) {
                requestCenter.setFirstInProgressTime(DateTimeUtils.nowTimestamp());
            }
            // 状态变为【Resolved】，则记录【Resolved】时间
            if (newStatus == ServiceRequestCenterStatusEnum.RESOLVED.getCode()) {
                requestCenter.setResolvedTime(DateTimeUtils.nowTimestamp());
            }

            // 变更状态
            serviceRequestCenterService.updateById(requestCenter);
            // 记录操作log
            String expand = JsonUtils.convertObjToString(objectMapper, new ChangeValueBasicDTO(request.getStatus(), newStatus));
            serviceRequestCenterLogService.save(requestId, ServiceRequestCenterLogCons.CHANGE_STATUS, expand, userId);
            // 给用户发通知
            if (request.getCreatedBy() != userId) {
                serviceRequestCenterChangeStatusOperation.notify(new ServiceRequestCenterNotifyBodyBO(tenantId, requestId));
            }
        }

        /**
         * 变更 request type
         *
         * @param tenantId      租户 id
         * @param request       request
         * @param changeTypeDTO type 信息
         * @param userId        操作人
         * @author yyt
         * @date 2025/12/18
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void changeType(int tenantId, ServiceRequestCenter request, ChangeRequestTypeDTO changeTypeDTO, int userId) {
            // 变更type，变更状态，清空责任人，并重置 【第一次成 in progress 的时间】，重置待分配时间
            serviceRequestCenterService.update(new UpdateWrapper<ServiceRequestCenter>().lambda()
                    .eq(ServiceRequestCenter::getId, request.getId())
                    .set(ServiceRequestCenter::getType, changeTypeDTO.getNewType())
                    .set(ServiceRequestCenter::getStatus, ServiceRequestCenterStatusEnum.OPEN.getCode())
                    .set(ServiceRequestCenter::getDutyUser, BaseConstant.ZERO)
                    .set(ServiceRequestCenter::getFirstInProgressTime, null)
                    .set(ServiceRequestCenter::getPendingAllocationTime, DateTimeUtils.nowTimestamp()));

            // 通知用户
            if (request.getCreatedBy() != userId) {
                ServiceRequestCenterNotifyBodyBO notifyBodyBO = new ServiceRequestCenterNotifyBodyBO(tenantId, request.getId());
                notifyBodyBO.setOldType(request.getType());
                notifyBodyBO.setOperationId(userId);
                serviceRequestCenterChangeTypeOperation.notify(notifyBodyBO);
            }

            //记录操作log
            serviceRequestCenterLogService.save(request.getId(), ServiceRequestCenterLogCons.CHANGE_TYPE,
                    JsonUtils.convertObjToString(objectMapper, new ChangeValueBasicDTO(request.getType(), changeTypeDTO.getNewType())), userId);
        }

        /**
         * 变更 request 责任人
         *
         * @param tenantId          租户 id
         * @param request           request
         * @param changeDutyUserDTO 责任人信息
         * @param userId            操作人
         * @author yyt
         * @date 2025/12/18
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void changeDutyUser(int tenantId, ServiceRequestCenter request, ChangeDutyUserDTO changeDutyUserDTO, int userId) {
            // 重置责任人、状态
            Integer requestId = request.getId();
            Integer newDutyUser = changeDutyUserDTO.getNewDutyUser();
            serviceRequestCenterService.update(new UpdateWrapper<ServiceRequestCenter>().lambda()
                    .eq(ServiceRequestCenter::getId, requestId)
                    .set(ServiceRequestCenter::getDutyUser, newDutyUser)
                    .set(ServiceRequestCenter::getStatus, ServiceRequestCenterStatusEnum.OPEN.getCode()));

            // 给员工发送通知（分配给自己，不发通知）
            if (!Objects.equals(newDutyUser, userId)) {
                ServiceRequestCenterNotifyBodyBO notifyBodyBO = new ServiceRequestCenterNotifyBodyBO(tenantId, requestId);
                notifyBodyBO.setOldDutyUser(userId);
                serviceRequestCenterChangeDutyUserOperation.notify(notifyBodyBO);
            }

            // 记录操作log
            serviceRequestCenterLogService.save(requestId, ServiceRequestCenterLogCons.ASSIGN,
                    JsonUtils.convertObjToString(objectMapper, new AssignBasicDTO(newDutyUser)), userId);
        }

        /**
         * 自动关闭 request
         *
         * @param tenantId         租户
         * @param propertyDTOs     资产
         * @param unclosedRequests 未关闭的 request
         * @author yyt
         * @date 2025/12/18
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void autoClosedRequest(int tenantId, List<PropertyDTO> propertyDTOs, List<ServiceRequestCenter> unclosedRequests) {
            long now = DateTimeUtils.nowTimestamp();
            // 关闭 request
            serviceRequestCenterService.update(new UpdateWrapper<ServiceRequestCenter>().lambda()
                    .in(ServiceRequestCenter::getId, unclosedRequests.stream().map(ServiceRequestCenter::getId).collect(Collectors.toList()))
                    .set(ServiceRequestCenter::getStatus, ServiceRequestCenterStatusEnum.CLOSED.getCode())
                    .set(ServiceRequestCenter::getClosedTime, now)
                    .set(ServiceRequestCenter::getClosedBy, BaseConstant.ZERO));

            // 添加操作 log
            String expand = JsonUtils.convertObjToString(objectMapper, new ChangeValueBasicDTO(ServiceRequestCenterStatusEnum.RESOLVED.getCode(), ServiceRequestCenterStatusEnum.CLOSED.getCode()));
            serviceRequestCenterLogService.saveBatch(unclosedRequests.stream().map(request ->
                    new ServiceRequestCenterLog()
                            .setRequestId(request.getId())
                            .setType(ServiceRequestCenterLogCons.CHANGE_STATUS)
                            .setExpand(expand)
                            .setCreatedBy(BaseConstant.ZERO)
                            .setCreatedTime(now)
            ).toList());

            // 通知
            notify(tenantId, propertyDTOs, unclosedRequests);
        }

        /**
         * 通知用户
         *
         * @param tenantId         租户
         * @param propertyDTOs     资产
         * @param unclosedRequests 未关闭的 request
         * @author yyt
         * @date 2025/12/19
         */
        private void notify(int tenantId, List<PropertyDTO> propertyDTOs, List<ServiceRequestCenter> unclosedRequests) {
            propertyDTOs = propertyDTOs.stream().filter(PropertyDTO::getStatus).toList();
            if (CollectionUtils.isEmpty(propertyDTOs)) return;

            Set<Integer> userIds = Sets.newHashSetWithExpectedSize(unclosedRequests.size());
            for (ServiceRequestCenter unclosedRequest : unclosedRequests) {
                userIds.add(unclosedRequest.getCreatedBy());
                userIds.add(unclosedRequest.getDutyUser());
            }
            Map<Integer, UserDTO> userById = userInternalService.getUsers(tenantId, userIds)
                    .stream().collect(Collectors.toMap(UserDTO::getId, v -> v));

            Map<Integer, List<Integer>> propertyIdsByRequestId = serviceRequestCenterPropertyService.listPropertyIds(unclosedRequests.stream().map(ServiceRequestCenter::getId).toList())
                    .stream().collect(Collectors.groupingBy(ServiceRequestCenterProperty::getRequestId,
                            Collectors.mapping(ServiceRequestCenterProperty::getPropertyId, Collectors.toList())));

            TenantDTO tenant = tenantInternalService.getTenantById(tenantId);

            List<PropertyDTO> finalPropertyDTOs = propertyDTOs;
            unclosedRequests.forEach((request) -> {
                // 获取令牌后执行后续逻辑
                rateLimiter.acquire();

                if (request.getType() == ServiceRequestCenterTypeEnum.ACCOUNT.getCode()) {
                    notifyForAccount(tenant, request, userById);
                } else {
                    List<Integer> propertyIds = propertyIdsByRequestId.getOrDefault(request.getId(), Collections.emptyList());
                    List<PropertyDTO> propertyForRequest = finalPropertyDTOs.stream().filter(item-> propertyIds.contains(item.getId())).toList();
                    if (CollectionUtils.isEmpty(propertyForRequest)) {
                        return;
                    }
                    notifForOtherDepartment(tenantId, propertyForRequest, request, userById);
                }
            });
        }

        /**
         * 财务类型单据发送关闭通知
         *
         * @param tenant   租户
         * @param request  request
         * @param userById 用户信息
         * @author yyt
         * @date 2026/4/2
         **/
        private void notifyForAccount(TenantDTO tenant, ServiceRequestCenter request, Map<Integer, UserDTO> userById) {
            // 获取用户，如果离职，则跳过
            UserDTO userDTO = userById.get(request.getCreatedBy());
            if (!userDTO.getStatus()) return;

            // 封装邮件内容，发送通知
            NotifyDTO notifyDTO = new NotifyDTO();
            notifyDTO.setPropertyId(userDTO.getProperties().get(0).getId());
            notifyDTO.setFunctionPoint(FunctionPointEnum.SERVICE_REQUEST_CENTER_OPERATION.getFunctionPoint());

            // Ticket Automatically Closed: 【ticket.id】 - 【ticket.title】 - 租户名称
            String subject = i18nMessageUtils.message(ServiceRequestCenterNotifyEnum.AUTO_CLOSED.getI18nCode(), LanguageEnum.ENGLISH.getLanguage(),
                    request.getServiceNumber(), request.getTitle(), tenant.getName());
            notifyDTO.setSubject(subject);

            notifyDTO.setIdentityList(serviceRequestCenterNotifyConfigService.getNotifyCreator(request));

            int activeType = ServiceRequestCenterActiveTypeEnum.MY_TICKET.getType();
            JSONObject msg = serviceRequestCenterNotifyConfigService.generateLineMsg();
            notifyDTO.setLineContent(Lists.newArrayList(msg));
            notifyDTO.setEmailContent(generateEmailContent(tenant.getId(), request, subject, userById, activeType, request.getCreatedBy()));
            notifyInternalService.sendNotify(notifyDTO);
        }

        /**
         * 非财务类型单据发送关闭通知
         *
         * @param tenantId     租户 id
         * @param propertyDTOs request 绑定的资产
         * @param request      request
         * @param userById     用户信息
         * @author yyt
         * @date 2026/4/2
         **/
        private void notifForOtherDepartment(int tenantId, List<PropertyDTO> propertyDTOs, ServiceRequestCenter request, Map<Integer, UserDTO> userById) {
            // 获取用户，如果离职，则跳过
            UserDTO userDTO = userById.get(request.getCreatedBy());
            if (!userDTO.getStatus()) return;

            List<Integer> propertyIds = propertyDTOs.stream().map(PropertyDTO::getId).toList();
            Optional<PropertyBasicInfoDTO> first = userDTO.getProperties().stream().filter(item -> propertyIds.contains(item.getId())).findFirst();
            if (first.isEmpty()) return;

            // 封装邮件内容，发送通知
            NotifyDTO notifyDTO = new NotifyDTO();
            notifyDTO.setPropertyId(first.get().getId());
            notifyDTO.setFunctionPoint(FunctionPointEnum.SERVICE_REQUEST_CENTER_OPERATION.getFunctionPoint());

            // Ticket Automatically Closed: 【ticket.id】 - 【ticket.title】 - 酒店全称
            String subject = i18nMessageUtils.message(ServiceRequestCenterNotifyEnum.AUTO_CLOSED.getI18nCode(), LanguageEnum.ENGLISH.getLanguage(),
                    request.getServiceNumber(), request.getTitle(), serviceRequestCenterNotifyConfigService.getPropertyNameForEmail(propertyDTOs));
            notifyDTO.setSubject(subject);

            notifyDTO.setIdentityList(serviceRequestCenterNotifyConfigService.getNotifyCreator(request));

            int activeType = ServiceRequestCenterActiveTypeEnum.MY_TICKET.getType();
            JSONObject msg = serviceRequestCenterNotifyConfigService.generateLineMsg();
            notifyDTO.setLineContent(Lists.newArrayList(msg));
            notifyDTO.setEmailContent(generateEmailContent(tenantId, request, subject, userById, activeType, request.getCreatedBy()));
            notifyInternalService.sendNotify(notifyDTO);
        }

        /**
         * 邮件内容
         *
         * @param tenantId     租户 id
         * @param request      request
         * @param subject      邮件标题
         * @param userById     用户
         * @param activeType   active type（对应页面不同的 tab）
         * @param notifyUserId 通知人
         * @return 邮件内容
         * @author yyt
         * @date 2025/12/19
         */
        private String generateEmailContent(int tenantId, ServiceRequestCenter request, String subject, Map<Integer, UserDTO> userById, int activeType, int notifyUserId) {
            String language = LanguageEnum.ENGLISH.getLanguage();
            String longDate = InternationalizationDateTimeDTO.LONG_DATE_2;

            Context context = new Context();
            ServiceRequestCenterEmailVO emailVO = serviceRequestCenterNotifyConfigService.packageCommonEmailContent(tenantId, request, activeType, notifyUserId);
            // resolve by，close date，create date，resolve date, 当天，解决人
            emailVO.setReceiverName(userById.get(request.getCreatedBy()).getName());
            emailVO.setCloseDate(InternationalizationUtils.formatDateTime(DateTimeUtils.dateNowTimestamp(timezone), longDate, null, timezone, language));
            emailVO.setCreateDate(InternationalizationUtils.formatDateTime(request.getCreatedTime(), longDate, null, timezone, language));
            emailVO.setResolveDate(InternationalizationUtils.formatDateTime(request.getResolvedTime(), longDate, null, timezone, language));
            emailVO.setDutyUserName(userById.get(request.getDutyUser()).getName());
            context.setVariable("emailVO", emailVO);
            return EmailUtil.getFullEmailContent(subject, templateEngine.process("servicerequestcenter/auto-closed_" + language, context), language);
        }

    }