TaskServiceImpl.java 17.7 KB
package vion.service.impl;

import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.lang.Opt;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.*;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.github.yulichang.base.MPJBaseServiceImpl;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import io.github.linpeilie.Converter;
import lombok.RequiredArgsConstructor;
import me.chanjar.weixin.mp.bean.template.WxMpTemplateData;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import vion.dto.TaskDTO;
import vion.mapper.TaskMapper;
import vion.model.Dictionary;
import vion.model.*;
import vion.service.*;
import vion.third.DingMod;
import vion.third.WechatMod;
import vion.vo.RoleVO;
import vion.vo.TaskTempVO;
import vion.vo.TaskVO;
import vion.vo.UserVO;

import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

@Service
@RequiredArgsConstructor
public class TaskServiceImpl extends MPJBaseServiceImpl<TaskMapper, Task> implements ITaskService {

    private final ITaskTempService taskTempService;
    private final IFileService fileService;
    private final IFaultLogService faultLogService;
    private final IStoreService storeService;
    private final IUserService userService;
    private final IDictionaryService dictionaryService;
    private final DingMod dingMod;
    private final WechatMod wechatMod;
    private final Converter converter;
    @Value("${fileUrl:}")
    private String fileUrl;

    @Override
    public Page<TaskVO> getTaskList(TaskDTO data) {
        UserVO userVO = (UserVO) StpUtil.getTokenSession().get("curLoginUser");
        Set<Long> taskIdSet = getTaskIdSet(userVO);

        MPJLambdaWrapper<Task> wrapper = new MPJLambdaWrapper<>(converter.convert(data, Task.class))
                .selectAll(Task.class)
                .selectAs(Store::getName, TaskVO::getStoreName)
                .selectAs(Account::getName, TaskVO::getAccountName)
                .selectAssociation(ServiceOrder.class, TaskVO::getServiceOrder)
                .leftJoin(Store.class, Store::getId, Task::getStoreId)
                .leftJoin(Account.class, Account::getId, Task::getAccountId)
                .leftJoin(ServiceOrder.class, ServiceOrder::getTaskId, Task::getId)
                .between(ArrayUtil.isAllNotNull(data.getStartdate(), data.getEnddate()), Task::getRepairTime, data.getStartdate(), data.getEnddate())
                .lt(data.getCurDate() != null, Task::getExpDate, data.getCurDate());

        if (taskIdSet.size() == 1) {
            Long taskId = CollUtil.get(taskIdSet, 0);
            // 不是管理员,并且第一次处理工单,但当前处理人是他 || 管理员逻辑
            if (taskId.equals(-99L) || taskId.equals(0L)) {
                return this.selectJoinListPage(Page.of(data.getPageNum(), data.getPageSize()), TaskVO.class, wrapper);
            }
        }

        wrapper.in(Task::getId, taskIdSet);
        return this.selectJoinListPage(Page.of(data.getPageNum(), data.getPageSize()), TaskVO.class, wrapper);
    }

    /**
     * 根据当前登录用户,判断其权限和其参与过的工单,获取工单id集合
     *
     * @param userVO 当前登录用户
     * @return java.util.Set<java.lang.Long>
     */
    private Set<Long> getTaskIdSet(UserVO userVO) {
        Set<Long> taskIdSet = CollUtil.newHashSet();
        Opt.ofEmptyAble(userVO.getRoleVOList())
                .map(l -> l.stream().map(RoleVO::getCode).collect(Collectors.toList()))
                .map(roleCodeList -> {
                    if (CollUtil.contains(roleCodeList, "task_admin") || CollUtil.contains(roleCodeList, "admin")) {
                        FaultLog log = new FaultLog();
                        log.setTaskId(0L);
                        return ListUtil.of(log);
                    } else {
                        List<FaultLog> logList = faultLogService.lambdaQuery()
                                .select(FaultLog::getTaskId)
                                .in(FaultLog::getOperator, userVO.getId())
                                .list();
                        if (CollUtil.isEmpty(logList)) {
                            FaultLog log = new FaultLog();
                            log.setTaskId(-99L);
                            logList.add(log);
                        }
                        // 当前处理人的工单也要加入筛选条件
                        Long id = userVO.getId();
                        Opt.ofEmptyAble(this.lambdaQuery().eq(Task::getActiveUser, id).list())
                                .map(l -> l.stream().map(Task::getId).collect(Collectors.toList()))
                                .ifPresent(taskIdSet::addAll);
                        return logList;
                    }
                })
                .ifPresent(logs -> taskIdSet.addAll(logs.stream().map(FaultLog::getTaskId).collect(Collectors.toSet())));
        return taskIdSet;
    }

    @Override
    public TaskVO getTaskById(Long taskId) {
        MPJLambdaWrapper<Task> wrapper = new MPJLambdaWrapper<Task>()
                .selectAll(Task.class)
                .selectAs(Store::getName, TaskVO::getStoreName)
                .selectCollection(FileInfo.class, TaskTempVO::getFileList)
                .leftJoin(Store.class, Store::getId, Task::getStoreId)
                .leftJoin(FileInfo.class, on -> on
                        .eq(FileInfo::getSourceId, Task::getId)
                        .eq(FileInfo::getStoreId, Task::getStoreId))
                .eq(Task::getId, taskId);
        return this.selectJoinOne(TaskVO.class, wrapper);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long circTask(TaskDTO data, String token) {
        UserVO user = (UserVO) StpUtil.getTokenSession().get("curLoginUser");
        handleTaskTemp(data);
        Task task = prepareTask(data, user);
        sendNotifications(data, task, user);
        handleFiles(data, task, user);
        return task.getId();
    }

    /**
     * <p>创建工单前,先判断预工单是否已经确认。</p>
     * <p>如果未确认,将预工单状态改为已确认。后面的流程进行创建工单。</p>
     *
     * @param data 前端传参
     */
    private void handleTaskTemp(TaskDTO data) {
        if (ObjUtil.isNotNull(data.getTaskTempId())) {
            Task existTask = this.lambdaQuery().eq(Task::getTaskTempId, data.getTaskTempId()).one();
            Assert.isNull(existTask, "工单已创建,无需再次确认");
            taskTempService.lambdaUpdate()
                    .set(TaskTemp::getStatus, 3)
                    .set(TaskTemp::getOperator, data.getActiveUser())
                    .set(TaskTemp::getStoreId, data.getStoreId())
                    .eq(TaskTemp::getId, data.getTaskTempId())
                    .update(new TaskTemp());
        }
    }

    /**
     * 根据前端是否传参工单id,来判断是更新工单还是新增工单,并创建对应的工单日志
     *
     * @param data 前端传参
     * @param user 当前登录用户
     * @return vion.model.Task
     */
    private Task prepareTask(TaskDTO data, UserVO user) {
        Task task = converter.convert(data, Task.class);
        if (task.getId() == null) {
            task.setCreateUser(user.getId());
            task.setUuid(IdUtil.nanoId());
            this.save(task);

            // 预工单文件在工单创建时,复制一份到工单文件夹下
            Opt.ofNullable(data.getTaskTempId())
                    .map(taskTempId -> fileService.lambdaQuery()
                            .eq(FileInfo::getSourceId, taskTempId)
                            .eq(FileInfo::getSourceType, 2)
                            .eq(FileInfo::getStoreId, 0L)
                            .list()
                    )
                    .filter(CollUtil::isNotEmpty)
                    .ifPresent(fileInfoList -> fileInfoList.forEach(fileInfo -> {
                        String path = fileUrl + FileUtil.FILE_SEPARATOR + task.getStoreId() + FileUtil.FILE_SEPARATOR + task.getId() + FileUtil.FILE_SEPARATOR + fileInfo.getName();
                        File tarFile = FileUtil.copy(fileInfo.getUrl(), path, true);

                        FileInfo newFileInfo = new FileInfo();
                        newFileInfo.setStoreId(task.getStoreId());
                        newFileInfo.setSourceId(task.getId());
                        newFileInfo.setSourceType(3);
                        newFileInfo.setName(fileInfo.getName());
                        newFileInfo.setUrl(path);
                        newFileInfo.setType(fileInfo.getType());
                        newFileInfo.setSha256(fileInfo.getSha256());
                        newFileInfo.setUploader(fileInfo.getUploader());
                        fileService.save(newFileInfo);
                    }));

            List<FaultLog> saveList = new ArrayList<>();
            // 预工单提交,客户名称不在系统用户表内。单独处理客户提交的预工单日志
            FaultLog faultLog = new FaultLog();
            faultLog.setTaskId(task.getId());
            faultLog.setStoreId(task.getStoreId());
            faultLog.setOperator(-1L);
            faultLog.setContent(data.getRepairPeople() + "|提交工单");
            faultLog.setCreateTime(task.getRepairTime());
            saveList.add(faultLog);

            // 添加工单处理日志
            // 预工单确认,变为工单,添加日志
            FaultLog faultLog1 = new FaultLog();
            faultLog1.setTaskId(task.getId());
            faultLog1.setStoreId(task.getStoreId());
            faultLog1.setOperator(user.getId());
            faultLog1.setContent("确认工单");
            faultLog1.setRemark(task.getRemark());
            saveList.add(faultLog1);
            faultLogService.saveBatch(saveList);
        } else {
            this.updateById(task);
            handleFaultLog(data, user, task);
        }
        return task;
    }

    /**
     * 工单日志处理
     *
     * @param data 前端传参
     * @param user 当前登录用户
     * @param task 工单
     */
    private void handleFaultLog(TaskDTO data, UserVO user, Task task) {
        FaultLog faultLog = new FaultLog();
        faultLog.setTaskId(task.getId());
        faultLog.setStoreId(task.getStoreId());
        faultLog.setOperator(user.getId());
        faultLog.setRemark(task.getRemark());
        faultLog.setManHour(data.getManHour());
        faultLog.setContent(getFaultLogContent(task.getStatus()));
        faultLogService.save(faultLog);
    }

    private String getFaultLogContent(int status) {
        switch (status) {
            case 2:
                return "工单正在处理中";
            case 3:
                return "工单处理完成";
            case 4:
                return "工单挂起";
            case 5:
                return "工单已关闭";
            default:
                return "";
        }
    }

    /**
     * 钉钉提醒和微信提醒
     *
     * @param data 前端传参
     * @param task 工单
     * @param user 当前登录用户
     */
    private void sendNotifications(TaskDTO data, Task task, UserVO user) {
        // todo 异步发送钉钉消息通知
        Store store = storeService.getById(task.getStoreId());
        Task existTask = this.getById(task.getId());
        Long activeUserId = data.getActiveUser();
        User activeUser = userService.lambdaQuery().eq(User::getId, activeUserId).one();
        Set<String> useridList = new HashSet<>();
        useridList.add(activeUser.getUserid());
        if (task.getStatus() == 3) {
            // 工单完成时,推送钉钉消息到预工单的确认人
            Opt.ofNullable(existTask.getTaskTempId())
                    .map(taskTempId -> taskTempService.getById(taskTempId).getOperator())
                    .map(userService::getById)
                    .map(User::getUserid)
                    .ifPresent(useridList::add);
        }
        JSONObject msg = buildMsg(String.join(",", useridList), store.getName(), existTask);
        String pushRes = dingMod.sendMessage(msg);

        // todo 异步微信公众号消息推送
        if (task.getStatus() == 3) {
            Opt.ofNullable(existTask.getTaskTempId())
                    .map(taskTempId -> taskTempService.getById(taskTempId).getOpenid())
                    .ifPresent(openid -> {
                        Map<Integer, String> statusMap = MapUtil.<Integer, String>builder()
                                .put(1, "待确认")
                                .put(2, "进行中")
                                .put(3, "已完成")
                                .put(4, "挂起")
                                .build();
                        List<WxMpTemplateData> wxMpTemplateDataList = ListUtil.of(
                                new WxMpTemplateData("character_string21", existTask.getUuid()),
                                new WxMpTemplateData("thing12", DesensitizedUtil.chineseName(user.getUsername())),
                                new WxMpTemplateData("time11", DateUtil.formatDateTime(new Date())),
                                new WxMpTemplateData("thing20", existTask.getFaultDescription().length() > 20 ? StrUtil.sub(existTask.getFaultDescription(), 0, 17) + "..." : existTask.getFaultDescription()),
                                new WxMpTemplateData("phrase25", statusMap.get(existTask.getStatus())));
                        String sentMsg = wechatMod.sendMsg("H3zWJysLWrcxrUlrgqPnjDV7LyWLaml_Ap9WsLuQDCs", openid, wxMpTemplateDataList, null);
                    });
        }
    }

    /**
     * 文件处理
     *
     * @param data 前端传参
     * @param task 工单
     * @param user 当前登录用户
     */
    private void handleFiles(TaskDTO data, Task task, UserVO user) {
        Opt.ofNullable(data.getFiles())
                .ifPresent(fileList ->
                        Arrays.stream(fileList).forEach(infile -> {
                            String orgName = infile.getOriginalFilename();
                            String mainName = FileUtil.mainName(orgName);
                            String fileExt = FileUtil.extName(orgName);
                            String filename = StrUtil.format("{}_{}.{}", mainName, DateUtil.format(new Date(), "yyyyMMdd_HHmmss"), fileExt);
                            String path = fileUrl + FileUtil.FILE_SEPARATOR + data.getStoreId() + FileUtil.FILE_SEPARATOR + task.getId() + FileUtil.FILE_SEPARATOR + filename;
                            File file = FileUtil.touch(path);
                            try {
                                infile.transferTo(file);
                            } catch (IOException e) {
                                log.error("保存文件出错", e);
                            }

                            FileInfo fileInfo = new FileInfo();
                            fileInfo.setStoreId(task.getStoreId());
                            fileInfo.setSourceId(task.getId());
                            fileInfo.setSourceType(3);
                            fileInfo.setName(filename);
                            fileInfo.setUrl(path);
                            fileInfo.setType(FileUtil.extName(file));
                            fileInfo.setSha256(SecureUtil.sha256(file).toUpperCase());
                            fileInfo.setUploader(user.getUsername());
                            fileService.save(fileInfo);
                        }));
    }

    @Override
    public String batchIns(List<TaskDTO> data) {
        return this.saveBatch(converter.convert(data, Task.class)) ? "成功" : "失败";
    }

    JSONObject buildMsg(String userid, String storeName, Task task) {
        List<Dictionary> orderStatus = dictionaryService.list(Wrappers.<Dictionary>lambdaQuery().eq(Dictionary::getType, "order_status"));
        Map<Integer, String> orderStatusMap = orderStatus.stream().collect(Collectors.toMap(Dictionary::getKey, Dictionary::getValue));

        JSONObject jsonObj = new JSONObject();
        jsonObj.set("agent_id", 2358374016L);
        jsonObj.set("userid_list", userid);

        JSONObject msg = new JSONObject();
        JSONObject content = new JSONObject();
        content.set("title", "您有一条新工单请及时处理哦~_~");
        String markdown = StrUtil.format("#### 门店信息: **{}** [FullOfVitality]" +
                        "  \n  #### 报修人:{}" +
                        "  \n  #### 联系方式:{}" +
                        "  \n  #### 当前工单状态:{}" +
                        "  \n  #### 故障描述:{}" +
                        "  \n  #### 发送时间:{}",
                storeName, task.getRepairPeople(), task.getRepairPhone(), orderStatusMap.get(task.getStatus()), task.getFaultDescription(), DateUtil.now());
        content.set("markdown", markdown);
        content.set("btn_orientation", "1");

        JSONArray jsonArray = new JSONArray();
        jsonArray.add(new JSONObject().set("title", "查看详情").set("action_url", "https://oapi.dingtalk.com/connect/oauth2/sns_authorize?appid=dingkrzwks0jpi2di3uo&response_type=code&scope=snsapi_auth&state=STATE&redirect_uri=https%3A%2F%2Fyunwei.vionyun.com%3A8443%2Fyunwei%2Fapi%2Fding%2Fcallback%2Finside%3FstoreId%3D" + task.getStoreId() + "%26taskId%3D" + task.getId()));
        content.set("btn_json_list", jsonArray);

        msg.set("msgtype", "action_card");
        msg.set("action_card", content);
        jsonObj.set("msg", msg);
        return jsonObj;
    }
}