package cn.iocoder.yudao.module.tickets.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.iocoder.yudao.common.*;
import cn.iocoder.yudao.framework.common.util.number.MoneyUtils;
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
import cn.iocoder.yudao.module.base.entity.RepairWorker;
import cn.iocoder.yudao.module.base.service.RepairRecordsService;
import cn.iocoder.yudao.module.base.service.RepairWorkerService;
import cn.iocoder.yudao.module.base.vo.RepairRecordsPageReqVO;
import cn.iocoder.yudao.module.base.vo.RepairRecordsRespVO;
import cn.iocoder.yudao.module.booking.entity.DlRepairBooking;
import cn.iocoder.yudao.module.booking.service.DlRepairBookingService;
import cn.iocoder.yudao.module.company.entity.Company;
import cn.iocoder.yudao.module.company.service.CompanyService;
import cn.iocoder.yudao.module.conf.entity.BaseType;
import cn.iocoder.yudao.module.conf.service.BaseTypeService;
import cn.iocoder.yudao.module.custom.entity.*;
import cn.iocoder.yudao.module.custom.service.*;
import cn.iocoder.yudao.module.custom.vo.CarMainRespVO;
import cn.iocoder.yudao.module.custom.vo.CustomerMainRespVO;
import cn.iocoder.yudao.module.order.entity.RepairOrderInfo;
import cn.iocoder.yudao.module.order.service.RepairOrderInfoService;
import cn.iocoder.yudao.module.order.vo.RepairOrderInfoSaveReqVO;
import cn.iocoder.yudao.module.other.entity.DlRepairOther;
import cn.iocoder.yudao.module.other.service.DlRepairOtherService;
import cn.iocoder.yudao.module.project.entity.RepairProject;
import cn.iocoder.yudao.module.project.entity.RepairWares;
import cn.iocoder.yudao.module.project.service.RepairProjectService;
import cn.iocoder.yudao.module.project.service.RepairWaresService;
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
import cn.iocoder.yudao.module.system.api.dict.DictDataApi;
import cn.iocoder.yudao.module.system.api.dict.dto.DictDataRespDTO;
import cn.iocoder.yudao.module.system.api.permission.PermissionApi;
import cn.iocoder.yudao.module.system.api.permission.RoleApi;
import cn.iocoder.yudao.module.system.api.permission.dto.RoleReqDTO;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import cn.iocoder.yudao.module.system.api.user.dto.UserDTO;
import cn.iocoder.yudao.module.tickets.entity.DlRepairTickets;
import cn.iocoder.yudao.module.tickets.entity.DlRepairTitem;
import cn.iocoder.yudao.module.tickets.entity.DlTicketWares;
import cn.iocoder.yudao.module.tickets.entity.DlTwItem;
import cn.iocoder.yudao.module.tickets.mapper.DlRepairTicketsMapper;
import cn.iocoder.yudao.module.tickets.service.DlRepairTicketsService;
import cn.iocoder.yudao.module.tickets.service.DlRepairTitemService;
import cn.iocoder.yudao.module.tickets.service.DlTicketWaresService;
import cn.iocoder.yudao.module.tickets.service.DlTwItemService;
import cn.iocoder.yudao.module.tickets.tools.WordUtil;
import cn.iocoder.yudao.module.tickets.vo.*;
import cn.iocoder.yudao.util.CreateQRCodeUtil;
import cn.iocoder.yudao.util.SendSmsUtil;
import com.baomidou.dynamic.datasource.annotation.DSTransactional;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.deepoove.poi.XWPFTemplate;
import com.deepoove.poi.config.Configure;
import com.deepoove.poi.config.ConfigureBuilder;
import com.deepoove.poi.data.PictureRenderData;
import com.deepoove.poi.data.PictureType;
import com.deepoove.poi.plugin.table.HackLoopTableRenderPolicy;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.stream.Collectors;

import static cn.iocoder.yudao.common.BaseConstants.ORDER_TENANT_NAME;
import static cn.iocoder.yudao.common.RepairCons.*;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception0;

/**
 * 针对表【dl_repair_tickets(维修工单表)】的数据库操作Service实现
 *
 * @author 小李
 * @date 18:19 2024/9/13
 **/
@Service
public class DlRepairTicketsServiceImpl extends ServiceImpl<DlRepairTicketsMapper, DlRepairTickets>
        implements DlRepairTicketsService {

    @Resource
    private DlRepairTitemService titemService;

    @Resource
    private DeptApi deptApi;

    @Resource
    private RepairProjectService projectService;

    @Resource
    private RepairWaresService waresService;

    @Resource
    private DlRepairOtherService otherService;

    @Resource
    private DictDataApi dictDataApi;

    @Resource
    private CustomerMainService customerMainService;

    @Resource
    @Lazy
    private RepairOrderInfoService repairOrderInfoService;

    @Resource
    @Lazy
    private DlRepairBookingService bookingService;

    @Resource
    @Lazy
    private CustomerMainService customerService;

    @Resource
    @Lazy
    private CarMainService carMainService;

    @Resource
    @Lazy
    private CustomerCarService customerCarService;

    @Resource
    @Lazy
    private AdminUserApi adminUserApi;

    @Resource
    @Lazy
    private PermissionApi permissionApi;

    @Resource
    @Lazy
    private RoleApi roleApi;

    @Resource
    @Lazy
    private RepairWorkerService repairWorkerService;

    @Resource
    private RepairRecordsService repairRecordsService;

    @Resource
    @Lazy
    private DlTicketWaresService ticketWaresService;

    @Resource
    @Lazy
    private DlTwItemService twItemService;

    @Resource
    @Lazy
    private CarBrandService carBrandService;

    @Resource
    @Lazy
    private CarModelService carModelService;

    @Resource
    @Lazy
    private BaseTypeService baseTypeService;

    @Resource
    @Lazy
    private CompanyService companyService;

    /**
     * 维修工单表 新增
     *
     * @param ticketsRespVO 新增对象
     * @author 小李
     * @date 11:33 2024/9/20
     **/
    @Override
    @DSTransactional
    public DlRepairTicketsRespVO createTickets(DlRepairTicketsRespVO ticketsRespVO) {
        // 验证
        if (ObjectUtil.isEmpty(ticketsRespVO.getCarId())) {
            throw exception0(500, "车辆信息为空");
        }
        if (ObjectUtil.isEmpty(ticketsRespVO.getAdviserId())) {
            throw exception0(500, "服务顾问为空");
        }
//        ticketsRespVO.getItemList().forEach(item -> {
//            if (ObjectUtil.isEmpty(item.getRepairIds()) || ObjectUtil.isEmpty(item.getSaleId())) {
//                String message = "";
//                switch (item.getItemType()) {
//                    case "01":
//                        message += "项目：";
//                        break;
//                    case "02":
//                        message += "配件：";
//                        break;
//                    case "03":
//                        message += "其他：";
//                }
//                message += (item.getItemName() + (ObjectUtil.isEmpty(item.getRepairIds()) ? "施工人员" : "销售人员") + "为空");
//                throw exception0(500, message);
//            }
//        });


        // 门店信息
        Long deptId = SecurityFrameworkUtils.getLoginUserDeptId();
        DeptRespDTO dept = deptApi.getDept(deptId);
        if (ObjectUtil.isNotEmpty(dept)) {
            ticketsRespVO.setCorpId(dept.getCorpId());
        }

        // 计算参考成本、参考毛利、领料状态、工单进行状态
        // TODO 参考成本 暂时为0
        ticketsRespVO.setCost(new BigDecimal("0"));
        // TODO 参考毛利 暂时为总金额
        ticketsRespVO.setProfit(ticketsRespVO.getTotalPrice());
        // 领料状态
        // 如果有配件就是未领料 没有配件就是无配件
        if (CollectionUtil.isEmpty(ticketsRespVO.getItemList())) {
            throw exception(RepairErrorCodeConstants.ITEM_IS_EMPTY);
        }
        List<DlRepairTitem> itemList = ticketsRespVO.getItemList();
        List<DlRepairTitem> collect = itemList.stream().filter(item -> item.getItemType().equals("02")).collect(Collectors.toList());
        ticketsRespVO.setPartStatus(CollectionUtil.isEmpty(collect) ? "01" : "02");
        // 工单进行状态 默认是等待接单
        ticketsRespVO.setTicketsWorkStatus(TicketsWorkStatusEnum.WAITING_RECEIVING.getCode());
        // 工单状态 默认是待派工
        ticketsRespVO.setTicketsStatus(TicketsStatusEnum.NO_WORK.getCode());

        // 新增主表
        baseMapper.insert(ticketsRespVO);

        // 新增子表
        itemList.forEach(item -> {
            item.setTicketId(ticketsRespVO.getId());
            if ("01".equals(item.getItemType())) {
                //维修项目,初始状态，待派工
                item.setItemStatus(TicketsItemStatusEnum.WAITING_WORK.getCode());
            } else if ("02".equals(item.getItemType())) {
                //维修配件，初始状态，待领料
                item.setItemStatus(TicketsItemStatusEnum.WAITING_RECEIVE.getCode());
            }
        });
        titemService.saveBatch(itemList);

        // 创建订单
        RepairOrderInfoSaveReqVO repairOrderInfo = new RepairOrderInfoSaveReqVO();
        // 订单号采用时间戳+工单ID和hash值取余后取3位
        repairOrderInfo.setOrderNo(System.currentTimeMillis() + Math.abs(ticketsRespVO.getId().hashCode() % 1000) + "");
        repairOrderInfo.setGoodsId(ticketsRespVO.getId());
        repairOrderInfo.setTenantName(ORDER_TENANT_NAME);
        DictDataRespDTO repairType = dictDataApi.getDictData("repair_type", ticketsRespVO.getRepairType());
        if (ObjectUtil.isNotEmpty(repairType)) {
            repairOrderInfo.setGoodsTitle(repairType.getLabel());
        } else {
            repairOrderInfo.setGoodsTitle("维修费用");
        }
        repairOrderInfo.setGoodsType("2");
        CustomerMainRespVO customerById = customerMainService.getCustomerById(ticketsRespVO.getUserId());
        if (ObjectUtil.isNotEmpty(customerById)) {
            repairOrderInfo.setUserId(customerById.getUserId());
        } else {
            repairOrderInfo.setUserId(0L);
        }
        repairOrderInfo.setCusId(customerById.getId());
        repairOrderInfo.setCusName(customerById.getCusName());
        repairOrderInfo.setCusPhone(customerById.getPhoneNumber());
        repairOrderInfo.setGoodsPrice(ticketsRespVO.getTotalPrice());
        repairOrderInfo.setOrderTime(ticketsRespVO.getCreateTime());
        repairOrderInfo.setOrderStatus("0");
        repairOrderInfo.setPayMoney(ticketsRespVO.getTotalPrice());
        if (ObjectUtil.isNotEmpty(deptId)) {
            repairOrderInfo.setDeptId(Integer.parseInt(deptId + ""));
        }

        // 如果选择了预约还要更新预约记录
        if (ObjectUtil.isNotEmpty(ticketsRespVO.getBookingId())) {
            DlRepairBooking dlRepairBooking = new DlRepairBooking();
            dlRepairBooking.setId(ticketsRespVO.getBookingId());
            dlRepairBooking.setTicketsId(ticketsRespVO.getId());
            dlRepairBooking.setBookingStatus("02");
            bookingService.updateById(dlRepairBooking);
        }
        repairOrderInfoService.saveOrderInfo(repairOrderInfo);
        //最后记录操作日志--创建工单
        repairRecordsService.saveRepairRecord(ticketsRespVO.getId(), null, RecordTypeEnum.CJGD.getCode(), "创建工单", ticketsRespVO.getImage());
        return ticketsRespVO;
    }

    /**
     * 维修工单表 分页
     *
     * @param repairTicketsReqVO 查询对象
     * @author 小李
     * @date 20:51 2024/9/20
     **/
    @Override
    public IPage<DlRepairTickets> getTicketsPage(DlRepairTicketsReqVO repairTicketsReqVO, Page<DlRepairTickets> page) {
        return baseMapper.getTicketsPage(repairTicketsReqVO, page);
    }

    /**
     * 获得一个工单的详细信息
     *
     * @param id 工单ID
     * @author 小李
     * @date 16:05 2024/9/21
     **/
    @Override
    public DlRepairTicketsRespVO getTicketsById(String id) {
        // 查工单主表
        DlRepairTickets dlRepairTickets = baseMapper.selectById(id);
        DlRepairTicketsRespVO result = BeanUtil.toBean(dlRepairTickets, DlRepairTicketsRespVO.class);
        //查车辆
        if (ObjectUtil.isNotEmpty(dlRepairTickets.getCarId())) {
            CarMainRespVO carInfo = carMainService.queryCarById(dlRepairTickets.getCarId());
            result.setCarInfo(carInfo);
        }

        //查用户信息
        CustomerMain customerInfo = customerService.getCustomerById(dlRepairTickets.getUserId());
        result.setCustomerInfo(customerInfo);
        // 查工单子表
        List<DlRepairTitem> itemList = titemService.list(new LambdaQueryWrapper<DlRepairTitem>().eq(DlRepairTitem::getTicketId, id));
        List<DlRepairTitemReqVO> items = itemList.stream().map(item -> BeanUtil.toBean(item, DlRepairTitemReqVO.class)).collect(Collectors.toList());
        // 取项目
        List<DlRepairTitemReqVO> projects = items.stream().filter(item -> item.getItemType().equals("01")).collect(Collectors.toList());
        if (CollectionUtil.isNotEmpty(projects)) {
            Set<String> ids = projects.stream().map(DlRepairTitemReqVO::getProjectId).collect(Collectors.toSet());
            List<RepairProject> repairProjects = projectService.listByIds(ids);
            items.forEach(item -> repairProjects.stream().filter(i -> i.getId().equals(item.getProjectId())).findFirst().ifPresent(item::setProject));
        }
        result.setProjects(projects);

        // 取配件
        List<DlRepairTitemReqVO> wares = items.stream().filter(item -> item.getItemType().equals("02")).collect(Collectors.toList());
        if (CollectionUtil.isNotEmpty(wares)) {
            // 单位字典
            List<DictDataRespDTO> recordTypeList = dictDataApi.getDictDataList(DICT_REPAIR_UNIT);
            Map<String, String> unitMap = recordTypeList.stream().collect(Collectors.toMap(DictDataRespDTO::getValue, DictDataRespDTO::getLabel));

            Set<String> ids = wares.stream().map(DlRepairTitemReqVO::getPartId).collect(Collectors.toSet());
            List<RepairWares> repairWares = waresService.listByIds(ids);
            items.forEach(item -> {
                repairWares.stream().filter(i -> i.getId().equals(item.getPartId())).findFirst().ifPresent(item::setWare);
                item.setItemUnit(unitMap.get(item.getItemUnit()));
            });
        }
        result.setWares(wares);
        // 取附加
        List<DlRepairTitemReqVO> others = items.stream().filter(item -> item.getItemType().equals("03")).collect(Collectors.toList());
        if (CollectionUtil.isNotEmpty(others)) {
            Set<String> ids = others.stream().map(DlRepairTitemReqVO::getOtherId).collect(Collectors.toSet());
            List<DlRepairOther> dlRepairOthers = otherService.listByIds(ids);
            items.forEach(item -> dlRepairOthers.stream().filter(i -> i.getId().equals(item.getOtherId())).findFirst().ifPresent(item::setOther));
        }
        result.setItems(items);
        // 查询工单操作记录
        RepairRecordsPageReqVO entity = new RepairRecordsPageReqVO();
        entity.setTicketId(id);
        List<RepairRecordsRespVO> records = repairRecordsService.queryList(entity);
        // 操作记录类型
        List<DictDataRespDTO> recordTypeList = dictDataApi.getDictDataList(DICT_REPAIR_RECORDS_TYPE);
        Map<String, String> typeMap = recordTypeList.stream().collect(Collectors.toMap(DictDataRespDTO::getValue, DictDataRespDTO::getLabel));
        result.setRecords(records.stream().peek(item -> item.setType(typeMap.get(item.getType()))).collect(Collectors.toList()));
        return result;
    }

    /**
     * 维修工单表 作废
     *
     * @param repairTicketsReqVO 工单对象
     * @author 小李
     * @date 19:46 2024/9/22
     **/
    @Override
    public void setTicketsVoid(DlRepairTicketsReqVO repairTicketsReqVO) {
        baseMapper.updateById(repairTicketsReqVO);
    }

    /**
     * 维修工单表 结算
     *
     * @param repairTicketsRespVO 工单
     * @author 小李
     * @date 8:50 2024/9/23
     **/
    @Override
    @DSTransactional
    public void setTicketsPaid(DlRepairTicketsRespVO repairTicketsRespVO) {
        // 更新订单状态
        RepairOrderInfo one = repairOrderInfoService.getOne(new LambdaQueryWrapper<RepairOrderInfo>().eq(RepairOrderInfo::getGoodsId, repairTicketsRespVO.getId()));
        if (ObjectUtil.isEmpty(one)) {
            throw exception0(500, "系统异常");
        }
        RepairOrderInfo repairOrderInfo = new RepairOrderInfo();
        repairOrderInfo.setId(one.getId());
        repairOrderInfo.setPayType(repairTicketsRespVO.getPayType());
        repairOrderInfoService.updateById(repairOrderInfo);
        // 更新工单
        baseMapper.updateById(repairTicketsRespVO);
    }

    /**
     * 客户信息和车辆信息 新增、修改
     *
     * @param customerAndCarVO 用户信息和车辆信息
     * @author 小李
     * @date 9:25 2024/10/8
     **/
    @Override
    @DSTransactional
    public void updateCustomerAndCar(CustomerAndCarVO customerAndCarVO) {
        // 新增用户信息,如果不存在
        if (ObjectUtil.isNotEmpty(customerAndCarVO.getUserInfo().getPhoneNumber()) && ObjectUtil.isNotEmpty(customerAndCarVO.getUserInfo().getCusName())) {
            AdminUserRespDTO userByMobile = adminUserApi.getUserByMobile(customerAndCarVO.getUserInfo().getPhoneNumber());
            if (ObjectUtil.isEmpty(userByMobile)) {
                UserDTO userDTO = new UserDTO();
                userDTO.setMobile(customerAndCarVO.getUserInfo().getPhoneNumber());
                userDTO.setNickname(customerAndCarVO.getUserInfo().getCusName());
                userDTO.setUsername(customerAndCarVO.getUserInfo().getPhoneNumber());
                userDTO.setPassword("123456");
                adminUserApi.createUser(userDTO);
                // 如果新增了数据，id没有回填
                if (ObjectUtil.isEmpty(userDTO.getId())) {
                    AdminUserRespDTO byMobile = adminUserApi.getUserByMobile(userDTO.getMobile());
                    userDTO.setId(byMobile.getId());
                }
                customerAndCarVO.getUserInfo().setUserId(userDTO.getId());
            }
        }
        // 客户信息
        if (ObjectUtil.isNotEmpty(customerAndCarVO.getUserInfo())) {
            if (ObjectUtil.isEmpty(customerAndCarVO.getUserInfo().getId())) {
                // 新增之前判断一下电话号码是否重复
                List<CustomerMain> list = customerService.list(new LambdaQueryWrapper<CustomerMain>().eq(CustomerMain::getPhoneNumber, customerAndCarVO.getUserInfo().getPhoneNumber()));
                if (CollectionUtil.isNotEmpty(list)) {
                    throw exception0(500, "电话号码重复");
                }
                customerService.save(customerAndCarVO.getUserInfo());
                // 如果新增了数据，id没有回填
                if (ObjectUtil.isEmpty(customerAndCarVO.getUserInfo().getId())) {
                    CustomerMain byPhoneNumber = customerService.getOne(new LambdaQueryWrapper<CustomerMain>().eq(CustomerMain::getPhoneNumber, customerAndCarVO.getUserInfo().getPhoneNumber()));
                    customerAndCarVO.getUserInfo().setId(byPhoneNumber.getId());
                }
            } else {
                customerService.updateById(customerAndCarVO.getUserInfo());
            }
        }
        // 新增车辆信息
        if (ObjectUtil.isNotEmpty(customerAndCarVO.getCarInfo())) {
            if (ObjectUtil.isEmpty(customerAndCarVO.getCarInfo().getId())) {
                customerAndCarVO.getCarInfo().setUserId(customerAndCarVO.getUserInfo().getUserId());
                // 新增之前判断一下车牌重复没
                List<CarMain> list = carMainService.list(new LambdaQueryWrapper<CarMain>().eq(CarMain::getLicenseNumber, customerAndCarVO.getCarInfo().getLicenseNumber()));
                if (CollectionUtil.isNotEmpty(list)) {
                    throw exception0(500, "车牌重复");
                }
                carMainService.createCarMain(customerAndCarVO.getCarInfo());
                // 如果新增了数据，id没有回填
                if (ObjectUtil.isEmpty(customerAndCarVO.getCarInfo().getId())) {
                    CarMain byLicenseNumber = carMainService.getOne(new LambdaQueryWrapper<CarMain>().eq(CarMain::getLicenseNumber, customerAndCarVO.getCarInfo().getLicenseNumber()));
                    customerAndCarVO.getCarInfo().setId(byLicenseNumber.getId());
                }
            } else {
                carMainService.updateCarMain(customerAndCarVO.getCarInfo());
            }
        }
        // 新增客户车辆信息关联
        String userId = customerAndCarVO.getUserInfo().getId();
        String carId = customerAndCarVO.getCarInfo().getId();
        if (ObjectUtil.isNotEmpty(userId) && ObjectUtil.isNotEmpty(carId)) {
            // 先查询有没有记录
            CustomerCar one = customerCarService.getOne(new LambdaQueryWrapper<CustomerCar>().and(item -> {
                item.eq(CustomerCar::getCusId, userId)
                        .eq(CustomerCar::getCarId, carId);
            }));
            // 没有就新增
            if (ObjectUtil.isEmpty(one)) {
                CustomerCar customerCar = new CustomerCar();
                customerCar.setCusId(userId);
                customerCar.setCarId(carId);
                customerCarService.saveOrUpdate(customerCar);
            }
        }
    }

    /**
     * 打印工单
     *
     * @param response HttpServletResponse
     * @param id       String
     * @author PQZ
     * @date 14:00 2024/10/12
     **/
    @Override
    @DSTransactional
    public void print(HttpServletResponse response, String id) {
        // 开始之前先计算一次工单
        boolean flag = computeTicket(id);
        if (!flag) {
            throw exception0(500, "系统异常");
        }
        // 预备工作
        // 先把这个工单和工单的所有子表信息取出来备用
        DlRepairTickets tickets = baseMapper.selectById(id);
        List<DlRepairTitem> titems = titemService.list(new LambdaQueryWrapper<DlRepairTitem>().in(DlRepairTitem::getTicketId, id));
        // 时间和日期格式化
        DateTimeFormatter time = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        DateTimeFormatter date = DateTimeFormatter.ofPattern("yyyy-MM-dd");
        // 构建数据对象
        Map<String, Object> params = new HashMap<>();
        String fileName = "结算单.docx";
        String tmpPath = "D:\\" + fileName;
        // 设置列表配置，如果有多个列表时需加.bind("list1", policy) 新列表配置即可
        ConfigureBuilder configureBuilder = Configure.builder();
        /*
            数据填入 以下带序号都是在模板的角度在分
         */

        /*
            1、工单基础信息（模板前四行）
         */
        // 工号（这里用的工单号）
        params.put("jobNumber", tickets.getTicketNo());

        // 客户信息（名称、送修人、送修人电话，用的工单中存的用户信息）
        params.put("customerName", tickets.getUserName());
        params.put("repairer", tickets.getUserName());
        params.put("repairerTelephone", tickets.getUserMobile());

        // 车辆信息（车牌、车架号、开单时间、已行里程、旧件处理，这些是工单中有的）
        params.put("plate", tickets.getCarNo());
        params.put("frameNumber", tickets.getCarVin());
        params.put("billingTime", time.format(tickets.getCreateTime()));
        // 已行里程
        params.put("mileage", tickets.getMileageTraveled());
        // 旧件处理
        // 获取字典数据
        List<DictDataRespDTO> dataList = dictDataApi.getDictDataList(DICT_REPAIR_PART_DISPOSAL);
        // 找到对应的处理方式
        DictDataRespDTO dictDataRespDTO = dataList.stream().filter(item -> item.getValue().equals(tickets.getPartDisposal())).findFirst().orElse(null);
        params.put("oldhandle", Objects.requireNonNull(dictDataRespDTO).getLabel());

        // 车辆其他信息（发动机号码、三包单位、保险名称、车型）
        CarMain carMain = carMainService.getById(tickets.getCarId());
        params.put("engineNumber", carMain.getEngineNumber());
        // 三包单位、保险名称
        params.put("tripleUnit", tickets.getThreePackUnits());
        params.put("insuranceName", tickets.getInsuranceName());
        // 先查车辆对应的品牌
        String brandAndModel = "";
        if (ObjectUtil.isNotEmpty(carMain.getCarBrand())) {
            CarBrand brand = carBrandService.getById(carMain.getCarBrand());
            if (ObjectUtil.isNotEmpty(brand)) {
                brandAndModel += brand.getBrandName();
                // 在查车辆对应的型号
                CarModel carModel = carModelService.getById(carMain.getCarModel());
                brandAndModel += " " + (carModel == null ? "" : carModel.getModelName());
            }
        }
        params.put("carType", brandAndModel);
        /*
            2、维修项目
         */
        // 取出维修工单子表的维修项目
        List<DlRepairTitem> projectList = titems.stream().filter(item -> item.getItemType().equals("01")).collect(Collectors.toList());
        // 构建导出对象
        List<Map<String, Object>> projects = new ArrayList<>();
        if (CollectionUtil.isNotEmpty(projectList)) {
            projectList.forEach(item -> {
                Map<String, Object> project = new HashMap<>();
                project.put("repRort", projects.size() + 1);
                project.put("repItem", item.getItemName());
                project.put("repPrice", item.getItemPrice());
                project.put("repCount", item.getItemCount());
                BigDecimal itemDiscount = item.getItemDiscount();
                if (ObjectUtil.isEmpty(item.getItemDiscount())) {
                    itemDiscount = new BigDecimal("1.00");
                }
                project.put("repDiscount", itemDiscount.equals(new BigDecimal("1.00")) ? "无折扣" : item.getItemDiscount());
                // 工时费 项目打折后的费用
                // 取不到先给0
                project.put("labourAmount", item.getItemMoney());
                project.put("major", item.getRepairNames());
                project.put("repRemark", item.getRemark());
                if (ObjectUtil.isNotEmpty(item.getItemTypeId())) {
                    BaseType baseType = baseTypeService.getById(item.getItemTypeId());
                    project.put("repNature", baseType == null ? "" : baseType.getName());
                }
                projects.add(project);
            });
        }
        configureBuilder.bind("projects", new HackLoopTableRenderPolicy());
        params.put("projects", projects);

        /*
            3、材料清单
         */
        // 取出维修工单子表的配件
        List<DlRepairTitem> waresList = titems.stream().filter(item -> item.getItemType().equals("02")).collect(Collectors.toList());
        List<Map<String, Object>> wares = new ArrayList<>();
        // 取出对应需要的其他表的数据备用
        List<DictDataRespDTO> unitDataList = dictDataApi.getDictDataList(DICT_REPAIR_UNIT);
        if (CollectionUtil.isNotEmpty(waresList)) {
            waresList.forEach(item -> {
                Map<String, Object> ware = new HashMap<>();
                ware.put("matRort", wares.size() + 1);
                // 配件号
                // 先取配件
                RepairWares wareById = waresService.getOne(new LambdaQueryWrapper<RepairWares>().eq(RepairWares::getId, item.getPartId()));
                String matNum = "";
                if (ObjectUtil.isNotEmpty(wareById)) {
                    matNum = wareById.getCode();
                }
                ware.put("matNum", matNum);
                ware.put("matName", item.getItemName());
                // 单位
                // 先取单位字典
                String matUnit = "";
                if (CollectionUtil.isNotEmpty(unitDataList) && ObjectUtil.isNotEmpty(item.getItemUnit())) {
                    DictDataRespDTO unit = unitDataList.stream().filter(i -> i.getValue().equals(item.getItemUnit())).findFirst().orElse(null);
                    if (unit != null) {
                        matUnit = unit.getLabel();
                    }
                }
                ware.put("matUnit", matUnit);
                ware.put("matPrice", item.getItemPrice());
                ware.put("matCount", item.getItemCount());
                BigDecimal itemDiscount = item.getItemDiscount();
                if (ObjectUtil.isEmpty(item.getItemDiscount())) {
                    itemDiscount = new BigDecimal("1.00");
                }
                ware.put("matDiscount", itemDiscount.equals(new BigDecimal("1.00")) ? "无折扣" : item.getItemDiscount());
                ware.put("matAmount", item.getItemMoney());
                ware.put("matRemark", item.getRemark());
                if (ObjectUtil.isNotEmpty(item.getItemTypeId())) {
                    BaseType baseType = baseTypeService.getById(item.getItemTypeId());
                    ware.put("matNature", baseType == null ? "" : baseType.getName());
                }
                wares.add(ware);
            });
        }
        configureBuilder.bind("wares", new HackLoopTableRenderPolicy());
        params.put("wares", wares);
        // 小计
        params.put("matTotalAmount", tickets.getPartPrice());

        /*
            4、其他信息
         */
        // 目前能取到：材料费、材料费优惠、总费用、应收费、大写、工单备注、工时费、工时费优惠、其他费、材料工时优惠
        // 计算配件没打折的总金额
        BigDecimal matCost = waresList.stream()
                .map(item -> item.getItemPrice().multiply(BigDecimal.valueOf(item.getItemCount())))
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        params.put("matCost", matCost);
        // 没打折的金额减去打折后的总金额得到优惠了多少钱
        BigDecimal matFavorable = matCost.subtract(ObjectUtil.isNotEmpty(tickets.getPartPrice()) ? tickets.getPartPrice() : BigDecimal.ZERO);
        params.put("matFavorable", matFavorable);
        // 计算没打折的工单总价
        BigDecimal matTotalCost = titems.stream().map(item -> item.getItemPrice().multiply(BigDecimal.valueOf(item.getItemCount()))).reduce(BigDecimal.ZERO, BigDecimal::add);
        // 加上特殊费用
        // 管理费
        BigDecimal managerMoney = ObjectUtil.isNotEmpty(tickets.getManagerMoney()) ? tickets.getManagerMoney() : new BigDecimal("0");
        // 救援费
        BigDecimal rescueMoney = ObjectUtil.isNotEmpty(tickets.getRescueMoney()) ? tickets.getRescueMoney() : new BigDecimal("0");
        // 三包费
        BigDecimal threePackMoney = ObjectUtil.isNotEmpty(tickets.getThreePackMoney()) ? tickets.getThreePackMoney() : new BigDecimal("0");
        // 定损费
        BigDecimal confirmFaultMoney = ObjectUtil.isNotEmpty(tickets.getConfirmFaultMoney()) ? tickets.getConfirmFaultMoney() : new BigDecimal("0");
        matTotalCost = matTotalCost.add(managerMoney).add(rescueMoney).add(threePackMoney).add(confirmFaultMoney);
        params.put("matTotalCost", matTotalCost);
        // 应收费目前是工单的打折后的总价
        params.put("allCost", tickets.getTotalPrice());
        // 应收费转大写
        String bigAllCost = MoneyUtils.toChinese(tickets.getTotalPrice());
        params.put("bigAllCost", bigAllCost);
        // 这个备注现在是工单备注
        params.put("paperRemark", tickets.getRemark());
        // 项目没打折的总价
        BigDecimal labourCost = projectList.stream().map(item -> item.getItemPrice().multiply(BigDecimal.valueOf(item.getItemCount()))).reduce(BigDecimal.ZERO, BigDecimal::add);
        params.put("labourCost", labourCost);
        // 项目没打折的总价减打折后的总价就是优惠
        BigDecimal labourFavorable = labourCost.subtract(ObjectUtil.isNotEmpty(tickets.getProjectPrice()) ? tickets.getProjectPrice() : BigDecimal.ZERO);
        params.put("labourFavorable", labourFavorable);
        // 其他费用 维修工单的其他子项的费用
        params.put("otherCost", ObjectUtil.isNotEmpty(tickets.getOtherPrice()) ? tickets.getOtherPrice() : 0.00);
        // 材料工时优惠 材料优惠+工时优惠
        params.put("matlabourFavorable", "0.00");

        // 管理费、救援费、三包费、定损费、结算备注（这个是因为和工单备注用的同一个）、todo 客户签字
        params.put("manageCost", ObjectUtil.isNotEmpty(tickets.getManagerMoney()) ? tickets.getManagerMoney() : "0.00");
        params.put("rescueCost", ObjectUtil.isNotEmpty(tickets.getRescueMoney()) ? tickets.getRescueMoney() : "0.00");
        params.put("tripleCost", ObjectUtil.isNotEmpty(tickets.getThreePackMoney()) ? tickets.getThreePackMoney() : "0.00");
        params.put("lossCost", ObjectUtil.isNotEmpty(tickets.getConfirmFaultMoney()) ? tickets.getConfirmFaultMoney() : "0.00");
        params.put("costRemark", tickets.getBillingRemark());
        /*
            5、最后结尾
         */
        // 目前能取到：服务顾问、联系电话、结算日期、付款情况
        params.put("serviceConsultant", tickets.getAdviserName());
        AdminUserRespDTO adviser = adminUserApi.getUser(Long.valueOf(tickets.getAdviserId()));
        if (ObjectUtil.isNotEmpty(adviser) && ObjectUtil.isNotEmpty(adviser.getMobile())) {
            params.put("serviceTelephone", adviser.getMobile());
        }
        // 结算日期
        params.put("settleDate", date.format(LocalDateTime.now()));
        // 取订单
        RepairOrderInfo orderInfo = repairOrderInfoService.getOne(new LambdaQueryWrapper<RepairOrderInfo>().eq(RepairOrderInfo::getGoodsId, tickets.getId()));
        if (ObjectUtil.isNotEmpty(orderInfo)) {
            // 付款情况
            // 先取支付字典
            List<DictDataRespDTO> payTypes = dictDataApi.getDictDataList(DICT_REPAIR_PAY_TYPE);
            String costCondition = "未支付";
            if (ObjectUtil.isNotEmpty(payTypes) && ObjectUtil.isNotEmpty(orderInfo.getPayType())) {
                DictDataRespDTO payType = payTypes.stream().filter(item -> item.getValue().equals(orderInfo.getPayType())).findFirst().orElse(null);
                if (payType != null) {
                    costCondition = payType.getLabel();
                }
            }
            params.put("costCondition", costCondition);
        }

        // 单位、开户行、地址、账号
        // 取当前租户的公司们
        Long tenantId = tickets.getTenantId();
        List<Company> companies = companyService.list(new LambdaQueryWrapper<Company>().in(Company::getTenantId, tenantId));
        // 得到属于当前业务的公司
        Company company = companies.stream().filter(item -> {
            String[] service = item.getServiceCodes().split(",");
            for (String s : service) {
                if (s.equals("weixiu")) {
                    return true;
                }
            }
            return false;
        }).findFirst().orElse(null);
        String corporation = "";
        String bank = "";
        String bankAddress = "";
        String account = "";
        if (company != null) {
            corporation = company.getCorpName();
            bankAddress = company.getAddress();
            bank = company.getBankAccount();
            account = company.getAccount();
        }
        // 取不到的先给未知
        params.put("corporation", corporation);
        params.put("bank", bank);
        params.put("bankAddress", bankAddress);
        params.put("account", account);

        // 调支付接口
        String payUrl = "";
        try {
            Map<String, Object> stringObjectMap = repairOrderInfoService.payTransactionsCode(orderInfo.getId());
            payUrl = (String) stringObjectMap.get("code_url");
            // 这个地方可能报错，报错就不生成二维码了，保证工单是能出来的
            // 生成收款二维码
            int width = 100, height = 100;
            byte[] texts = CreateQRCodeUtil.GenerateQRCode(payUrl, width, height);
            if (ObjectUtil.isNotEmpty(texts)) {
                params.put("qrCode", new PictureRenderData(width, height, PictureType.PNG, texts));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        Configure config = configureBuilder.build();
        try {
            InputStream inputStream = XWPFTemplate.class.getResourceAsStream("/templates/ticketTemplate.docx");
            XWPFTemplate template = XWPFTemplate.compile(inputStream, config);
            template.render(params);
            FileOutputStream fos = new FileOutputStream(tmpPath);
            template.write(fos);
            fos.flush();
            template.close();
            WordUtil.down(response, tmpPath, fileName);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 分类查询工单分页
     *
     * @param repairTicketsReqVO 查询对象
     * @author 小李
     * @date 16:26 2024/10/12
     **/
    @Override
    public IPage<DlRepairTickets> getPageType(DlRepairTicketsReqVO repairTicketsReqVO, Page<DlRepairTickets> page) {
        String userRoleCode = getUserRole();
        if (userRoleCode.equals(RepairRoleEnum.ADMIN.getCode())) {
            //维修管理员看所有数据
        } else if (userRoleCode.equals(RepairRoleEnum.INSPECTION.getCode())) {
            //总检
            if (RepairCons.TICKETS_WAITING.equals(repairTicketsReqVO.getSelectType())) {
                //查待处理的，查需要自己处理的，否则就是查所有
                repairTicketsReqVO.setUserIds(Collections.singletonList(SecurityFrameworkUtils.getLoginUserId()));
            }
        } else if (userRoleCode.equals(RepairRoleEnum.ADVISOR.getCode())) {
            //服务顾问
            repairTicketsReqVO.setAdviserId(String.valueOf(SecurityFrameworkUtils.getLoginUserId()));
        } else if (userRoleCode.equals(RepairRoleEnum.REPAIR_STAFF.getCode())) {
            //维修工，进一步判断是否是班组长
            boolean ifLeader = repairWorkerService.getIfLeader();
            repairTicketsReqVO.setRoleCode(RepairRoleEnum.REPAIR_STAFF.getCode());
            if (ifLeader) {
                //班组长，看本班组所有数据
                RepairWorker one = repairWorkerService.getOne(new LambdaQueryWrapper<RepairWorker>().eq(RepairWorker::getUserId, SecurityFrameworkUtils.getLoginUserId()));
                // 查自己班组的员工信息
                List<RepairWorker> list = repairWorkerService.list(new LambdaQueryWrapper<RepairWorker>().eq(RepairWorker::getWorkType, one.getWorkType()));
                // 所有的员工信息
                String userIdsStr = "";
                List<Long> userIds = new ArrayList<>();
                for (RepairWorker worker : list) {
                    userIds.add(worker.getUserId());
                    if ("".equals(userIdsStr)) {
                        userIdsStr = String.valueOf(worker.getUserId());
                    } else {
                        userIdsStr = "," + worker.getUserId();
                    }
                }
                repairTicketsReqVO.setUserIds(userIds);
                repairTicketsReqVO.setUserIdsStr(userIdsStr);
            } else {
                //普通维修工
                repairTicketsReqVO.setUserIds(Collections.singletonList(SecurityFrameworkUtils.getLoginUserId()));
            }
        } else {
            return null;
        }
        if (RepairCons.TICKETS_WAITING.equals(repairTicketsReqVO.getSelectType())) {
            //查待处理
            return baseMapper.getPageType(repairTicketsReqVO, page);
        } else {
            //查所有
            return baseMapper.getPageTypeAll(repairTicketsReqVO, page);
        }
    }

    /**
     * 判断登录用户的角色，针对维修工单中的四个角色
     *
     * @author 小李
     * @date 9:28 2024/10/14
     **/
    @Override
    public String getUserRole() {
        // 查看当前登录用户是什么角色
        // 当前登录用户的角色信息
        List<Long> byUserId = permissionApi.getRoleIdsByUserId(SecurityFrameworkUtils.getLoginUserId());
        // 所有的角色信息
        List<RoleReqDTO> roleList = roleApi.getRoleList();
        // 过滤出登录用户有的角色信息
        List<RoleReqDTO> collect = roleList.stream().filter(item -> byUserId.contains(item.getId())).collect(Collectors.toList());
        // 取出角色名称
        List<String> codes = collect.stream().map(RoleReqDTO::getCode).collect(Collectors.toList());
        // 维修业务管理员--可以看所有数据，但不能做操作
        if (codes.contains(RepairRoleEnum.ADMIN.getCode())) {
            return RepairRoleEnum.ADMIN.getCode();
        }
        // 总检
        if (codes.contains(RepairRoleEnum.INSPECTION.getCode())) {
            return RepairRoleEnum.INSPECTION.getCode();
        }
        // 服务顾问
        if (codes.contains(RepairRoleEnum.ADVISOR.getCode())) {
            return RepairRoleEnum.ADVISOR.getCode();
        }
        // 维修工（或班组长）
        if (codes.contains(RepairRoleEnum.REPAIR_STAFF.getCode())) {
            return RepairRoleEnum.REPAIR_STAFF.getCode();
        }
        // 维修仓库管理员
        if (codes.contains(RepairRoleEnum.WAREHOUSE.getCode())) {
            return RepairRoleEnum.WAREHOUSE.getCode();
        }
        // 维修财务
        if (codes.contains(RepairRoleEnum.WXCW.getCode())) {
            return RepairRoleEnum.WXCW.getCode();
        }
        // 啥也不是
        return null;
    }

    /**
     * 接单
     *
     * @param id 工单ID
     * @author 小李
     * @date 11:48 2024/10/14
     **/
    @Override
    public void updateTake(String id) {
        // 改变时看看是否已经被接了
        int update = baseMapper.update(new LambdaUpdateWrapper<DlRepairTickets>()
                .set(DlRepairTickets::getTicketsWorkStatus, TicketsWorkStatusEnum.RECEIVED.getCode())
                .and(item -> {
                    item.eq(DlRepairTickets::getId, id)
                            .eq(DlRepairTickets::getTicketsWorkStatus, TicketsWorkStatusEnum.WAITING_RECEIVING.getCode());
                }));
        if (update != 1) {
            throw exception0(500, "工单已被其他人接单了");
        }
        //最后记录操作日志--接单
        repairRecordsService.saveRepairRecord(id, null, RecordTypeEnum.jd.getCode(), "接单", null);
    }

    /**
     * 指派员工，通知施工
     *
     * @param reqVO 请求对象
     * @author 小李
     * @date 15:34 2024/10/14
     **/
    @Override
    public void updateRepair(DlRepairTicketsReqVO reqVO) {
        // 指派的时候看下是不是已经有人接单了----vinjor-m 服务顾问指派，不需要判断是否已有人接单
        int update = baseMapper.update(new LambdaUpdateWrapper<DlRepairTickets>()
                .set(DlRepairTickets::getNowRepairId, reqVO.getNowRepairId())
                .set(DlRepairTickets::getNowRepairName, reqVO.getNowRepairName())
                //工单状态设置为---施工中
                .set(DlRepairTickets::getTicketsStatus, TicketsStatusEnum.WORKING.getCode())
                //维修状态设置为待接单
                .set(DlRepairTickets::getTicketsWorkStatus, TicketsWorkStatusEnum.WAITING_RECEIVING.getCode())
                .and(item -> item.eq(DlRepairTickets::getId, reqVO.getId()))
        );
        if (update != 1) {
            throw exception0(500, "工单已开始");
        }
        repairWorkerService.sentMessage(reqVO.getNowRepairId(), "您有新的工单要处理");
        //最后记录操作日志--指派施工
        repairRecordsService.saveRepairRecord(reqVO.getId(), null, RecordTypeEnum.ZPSG.getCode(), "指派施工", null);
    }

    /**
     * 更新工单状态(针对开始施工、施工中记录、施工完成)
     *
     * @param respVO 请求对象
     * @author 小李
     * @date 15:46 2024/10/18
     **/
    @Override
    @DSTransactional
    public void updateStatus(DlRepairTicketsRespVO respVO) {
        // 如果是开始施工，但项目还在施工中，就不能施工
        if (RecordTypeEnum.KSSG.getCode().equals(respVO.getRecordType())) {
            DlRepairTitem titem = titemService.getById(respVO.getItem().getId());
            if (TicketsItemStatusEnum.WORKING.getCode().equals(titem.getItemStatus())) {
                throw exception0(500, "选择的项目在施工中，不能重新开始施工");
            }
        }

        if (StringUtils.isNotEmpty(respVO.getTicketsWorkStatus())) {
            // 更新工单主表
            LambdaUpdateWrapper<DlRepairTickets> wrapper = new LambdaUpdateWrapper<>();
            wrapper.eq(DlRepairTickets::getId, respVO.getId());
            wrapper.set(DlRepairTickets::getTicketsWorkStatus, respVO.getTicketsWorkStatus());
            // 这里注掉，不能让员工更新工单的状态为完成，后面还有总检、服务顾问
//            if (ObjectUtil.isNotEmpty(respVO.getIsFinish())) {
//                wrapper.set(DlRepairTickets::getIsFinish, respVO.getIsFinish());
//            }
            baseMapper.update(wrapper);
        }
        if (null != respVO.getItem() && StringUtils.isNotEmpty(respVO.getItem().getItemStatus())) {
            // 更新工单子表
            titemService.update(new LambdaUpdateWrapper<DlRepairTitem>()
                    .set(DlRepairTitem::getItemStatus, respVO.getItem().getItemStatus())
                    .eq(DlRepairTitem::getId, respVO.getItem().getId())
            );
        }
        // 记录日志
        repairRecordsService.saveRepairRecord(respVO.getId(), respVO.getItem().getId(), respVO.getRecordType(), respVO.getRemark(), respVO.getImage());

        // 通知服务顾问和维修总检
        DlRepairTickets tickets = baseMapper.selectById(respVO.getId());
        DlRepairTitem titem = titemService.getById(respVO.getItem().getId());
         /*
            消息组成：谁，对那个工单，那个项目，干了什么
            谁：工单的当前施工人
            对那个工单：工单号
            那个项目：操作的项目
            干了什么：提交的请求
            干了什么有三种情况：开始施工、施工中的记录上传、施工完成
            施工完成又分四种情况：阶段完成、完成并移交下一班组、完成并移交总检、完成工单，其中部分完成同开始施工、施工中的记录上传一致，只上传，不改变
          */
        // 消息生成
        String message = createMessage(tickets.getNowRepairName(), tickets.getTicketNo(), titem.getItemName(), respVO.getRecordType(), respVO.getFinishType(), respVO.getNextName());
        // 通知服务顾问
        // 如果员工完成了订单
        if (RecordTypeEnum.SGWCZJ.getCode().equals(respVO.getRecordType()) && "03".equals(respVO.getFinishType())) {
            repairWorkerService.sentMessage(Long.valueOf(tickets.getAdviserId()), "您有新的工单可以出厂检验");
            // 需要把工单的当前操作人移交服务顾问
            baseMapper.update(new LambdaUpdateWrapper<DlRepairTickets>()
                    .setSql("now_repair_id = adviser_id")
                    .setSql("now_repair_name = adviser_name")
                    .eq(DlRepairTickets::getId, respVO.getId()));

            // 因为目前的配件申请表在申请的时候就把配件加到工单中了，但后续的领取并没有同步到工单，所以如果这里是员工直接完成移交给服务顾问，就需要同步一下
            boolean flag = syncTicketWaresToTicket(respVO.getId());
            if (!flag) {
                throw exception0(500, "系统异常");
            }
        } else {
            repairWorkerService.sentMessage(Long.valueOf(tickets.getAdviserId()), message);
        }
        // 通知总检
        // 总检查角色包含总检的员工
        // 取这个角色的角色信息
        RoleReqDTO roleInfo = roleApi.getRoleInfo(RepairRoleEnum.INSPECTION.getCode());
        // 通过角色信息查有这个角色的人
        List<Long> ids = permissionApi.getUserIdByRoleId(roleInfo.getId());
        if (CollectionUtil.isEmpty(ids)) return;
        // 如果是移交给总检就需要通知总检并让其总检
        if (RecordTypeEnum.SGWCZJ.getCode().equals(respVO.getRecordType()) && "02".equals(respVO.getFinishType())) {
            // 工单移交给总检(理论上讲只有一个总检，就算有多个这里面也只给一个)
            AdminUserRespDTO user = adminUserApi.getUser(ids.get(0));
            baseMapper.update(new LambdaUpdateWrapper<DlRepairTickets>()
                    .set(DlRepairTickets::getNowRepairId, user.getId())
                    .set(DlRepairTickets::getNowRepairName, user.getNickname())
                    .set(DlRepairTickets::getTicketsWorkStatus, "05")
                    .eq(DlRepairTickets::getId, respVO.getId()));
            repairWorkerService.sentMessage(user.getId(), "您有新的工单需要总检");
        } else {
            ids.forEach(id -> {
                repairWorkerService.sentMessage(id, message);
            });
        }
    }

    private String createMessage(String who, String no, String projectName, String type, String finishType, String nextName) {
        String result = who;
        switch (type) {
            // 开始施工
            case "kssg":
                return result + "开始施工工单：" + no + "中" + projectName + "项目";
            // 施工记录
            case "sgz":
                return result + "上传了工单：" + no + "中" + projectName + "项目的维修记录";
            // 施工完成
            case "sgwczj":
                if (ObjectUtil.isNotEmpty(finishType)) {
                    result = result + "完成了工单：" + no + "中" + projectName + "项目";
                    switch (finishType) {
                        // 完成并移交下一班组
                        case "01":
                            return result + "并移交工单给了" + nextName;
                        // 完成并移交总检
                        case "02":
                            return result + "并移交给了总检";
                        // 完成工单
                        case "03":
                            return who + "完成了工单";
                        default:
                            return "异常消息";
                    }
                } else {
                    return result + "部分完成了工单：" + no + "中" + projectName + "项目";
                }
            default:
                return "异常消息";
        }
    }

    /**
     * 配件客户是否可见
     *
     * @param id   工单ID
     * @param show 是否可见 1可见， 0不可见
     * @author 小李
     * @date 21:52 2024/10/18
     **/
    @Override
    public void updateShow(String id, String show) {
        DlRepairTickets dlRepairTickets = new DlRepairTickets();
        dlRepairTickets.setId(id);
        dlRepairTickets.setPartShow(show);
        baseMapper.updateById(dlRepairTickets);
    }

    /**
     * 查待总检或待出厂检验的工单
     * 维修服务顾问和总检用的
     *
     * @param repairTicketsReqVO 请求对象
     * @author 小李
     * @date 11:40 2024/10/23
     **/
    @Override
    public IPage<DlRepairTickets> getPageByRole(DlRepairTicketsReqVO repairTicketsReqVO, Page<DlRepairTickets> page) {
        String userRoleCode = getUserRole();
        // 当前操作人的id
        Long loginUserId = SecurityFrameworkUtils.getLoginUserId();
        //设置查询类型---待总检或待出厂检验的工单
        repairTicketsReqVO.setSelectType("special");
        // 维修服务顾问
        if (userRoleCode.equals(RepairRoleEnum.ADVISOR.getCode())) {
            repairTicketsReqVO.setNowRepairId(loginUserId);
            repairTicketsReqVO.setIsFinish("1");
        } else if (userRoleCode.equals(RepairRoleEnum.INSPECTION.getCode())) {
            // 总检
            repairTicketsReqVO.setNowRepairId(loginUserId);
            repairTicketsReqVO.setIsFinish("0");
        } else {
            return null;
        }
        // 还是用分类查询，不过需要重新构造查询条件
        return baseMapper.getPageType(repairTicketsReqVO, page);
    }

    /**
     * 维修总检完成总检
     *
     * @param respVO 请求对象
     * @author 小李
     * @date 16:48 2024/10/23
     **/
    @Override
    @DSTransactional
    public void inspection(DlRepairTicketsRespVO respVO) {
        // 新增日志
        repairRecordsService.saveRepairRecord(respVO.getId(), null, RecordTypeEnum.ZJ.getCode(), respVO.getRemark(), respVO.getImage());
        // 更新工单
        baseMapper.update(new LambdaUpdateWrapper<DlRepairTickets>()
                .set(DlRepairTickets::getIsFinish, "1")
                .setSql("now_repair_id = adviser_id")
                .setSql("now_repair_name = adviser_name")
                .eq(DlRepairTickets::getId, respVO.getId())
        );

        boolean flag = syncTicketWaresToTicket(respVO.getId());
        if (!flag) {
            throw exception0(500, "系统异常");
        }

        // 通知服务顾问
        DlRepairTickets tickets = baseMapper.selectById(respVO.getId());
        repairWorkerService.sentMessage(Long.valueOf(tickets.getAdviserId()), "您有新的工单可以出厂检验");
    }

    /**
     * 用于同步一个工单的所有配件申请表的数据到维修工单，主要针对已领取数量小于申请数量的情况
     *
     * @param id 维修工单ID
     * @author 小李
     * @date 9:52 2024/10/26
     **/
    @Override
    public boolean syncTicketWaresToTicket(String id) {
        // 先查满足条件的申请表
        ArrayList<String> status = new ArrayList<>();
        status.add("01"); // 待审核
        status.add("05"); // 已驳回
        // 查工单所有已通过的配件申请单
        List<DlTicketWares> list = ticketWaresService.list(new LambdaQueryWrapper<DlTicketWares>().and(item -> {
            item.eq(DlTicketWares::getTicketId, id)
                    .notIn(DlTicketWares::getStatus, status);
        }));
        if (CollectionUtil.isNotEmpty(list)) {
            // 查配件申请表的所有已领取数量小于申请数量的子表信息
            List<String> ids = list.stream().map(DlTicketWares::getId).collect(Collectors.toList());
            List<DlTwItem> twItems = twItemService.list(new LambdaQueryWrapper<DlTwItem>().and(item -> {
                item.in(DlTwItem::getTwId, ids)
                        .apply("wares_already_count < wares_count");
            }));
            if (CollectionUtil.isNotEmpty(twItems)) {
                // 更新工单子表（主要是把已领料的字段同步给工单配件的数量字段）
                // 查询工单子表（三个查询条件，属于那个工单，属于配件类型，属于需要改变的（即申请表的已领取数量小于申请数量的））
                Set<String> waresId = twItems.stream().map(DlTwItem::getWaresId).collect(Collectors.toSet());
                List<DlRepairTitem> items = titemService.list(new LambdaQueryWrapper<DlRepairTitem>().and(item -> {
                    item.eq(DlRepairTitem::getTicketId, id)
                            .eq(DlRepairTitem::getItemType, "02")
                            .in(DlRepairTitem::getPartId, waresId);
                }));
                // 同一个配件可能申请过多次，属于是一个工单配件可能是对应多个申请表配件
                List<DlRepairTitem> AllItems = items.stream().map(item -> {
                    DlRepairTitem titem = new DlRepairTitem();
                    titem.setId(item.getId());
                    List<DlTwItem> wares = twItems.stream().filter(i -> i.getWaresId().equals(item.getPartId())).collect(Collectors.toList());
                    int sum = wares.stream().mapToInt(DlTwItem::getWaresAlreadyCount).sum();
                    titem.setItemCount(sum);
                    BigDecimal discount = item.getItemDiscount() == null ? BigDecimal.ONE : item.getItemDiscount();
                    titem.setItemMoney(item.getItemPrice().multiply(BigDecimal.valueOf(sum)).multiply(discount));
                    return titem;
                }).collect(Collectors.toList());

                // 可能出现配件申请单审核通过了没有任何领取或全部退了的情况，所以如果itemCount等于0，直接把这个数据删掉
                // 过滤出需要删除的数据
                List<DlRepairTitem> delItems = AllItems.stream().filter(item -> item.getItemCount() == 0).collect(Collectors.toList());
                if (CollectionUtil.isNotEmpty(delItems)) {
                    // 删除需要删的
                    titemService.removeBatchByIds(delItems);
                    Set<String> delIds = delItems.stream().map(DlRepairTitem::getId).collect(Collectors.toSet());
                    // 得到需要改的
                    List<DlRepairTitem> updateItems = AllItems.stream().filter(item -> delIds.contains(item.getId())).collect(Collectors.toList());
                    if (CollectionUtil.isNotEmpty(updateItems)) {
                        titemService.updateBatchById(updateItems);
                    }
                } else {
                    // 没有需要删的就是全部都需要改
                    titemService.updateBatchById(AllItems);
                }

                // 重新计算维修工单
                return computeTicket(id);
            }
        }
        return true;
    }

    /**
     * 服务顾问上传出厂检验日志
     *
     * @param respVO 请求对象
     * @author 小李
     * @date 17:47 2024/10/23
     **/
    @Override
    public void confirm(DlRepairTicketsRespVO respVO) {
        // 更新工单状态
        baseMapper.update(new LambdaUpdateWrapper<DlRepairTickets>()
                .set(DlRepairTickets::getTicketsWorkStatus, TicketsWorkStatusEnum.END.getCode())
                .set(DlRepairTickets::getTicketsStatus, TicketsStatusEnum.WAITING_NOTICE.getCode())
                .eq(DlRepairTickets::getId, respVO.getId())
        );

        // 记录日志
        repairRecordsService.saveRepairRecord(respVO.getId(), null, RecordTypeEnum.JSGD.getCode(), respVO.getRemark(), respVO.getImage());
    }

    /**
     * 从总检的角度差维修中、已完成的工单数量
     *
     * @return java.util.Map<java.lang.String, java.lang.Integer>
     * @author vinjor-M
     * @date 11:30 2024/10/24
     **/
    @Override
    public Map<String, Integer> getBossNum() {
        Map<String, Integer> rtnMap = new HashMap<>();
        int workingNum = 0;
        int doneNum = 0;
        List<DlRepairTickets> repairTickets = this.list();
        if (!repairTickets.isEmpty()) {
            Map<String, List<DlRepairTickets>> ifFinishMap = repairTickets.stream().collect(Collectors.groupingBy(DlRepairTickets::getIsFinish));
            if (ifFinishMap.containsKey("0")) {
                workingNum = ifFinishMap.get("0").size();
            }
            if (ifFinishMap.containsKey("1")) {
                doneNum = ifFinishMap.get("1").size();
            }
        }
        rtnMap.put("workingNum", workingNum);
        rtnMap.put("doneNum", doneNum);
        return rtnMap;
    }

    /**
     * 服务顾问通知客户取车
     *
     * @param noticeCusVO 请求对象
     * @author 小李
     * @date 22:40 2024/10/23
     **/
    @Override
    @DSTransactional
    public void noticeCus(NoticeCusVO noticeCusVO) {
        // 获取客户信息
        DlRepairTickets tickets = baseMapper.selectById(noticeCusVO.getId());
        // 更新工单状态为待结算（待取车）
        baseMapper.update(new LambdaUpdateWrapper<DlRepairTickets>()
                .set(DlRepairTickets::getTicketsStatus, TicketsStatusEnum.NO_PAY.getCode())
                .eq(DlRepairTickets::getId, noticeCusVO.getId())
        );
        CustomerMain cus = customerService.getById(tickets.getUserId());
        // 维修项目
        List<DictDataRespDTO> repairType = dictDataApi.getDictDataList(DICT_REPAIR_TYPE);
        // 默认是维修
        String type = "维修";
        DictDataRespDTO dictDataRespDTO = repairType.stream()
                .filter(item -> item.getValue().equals(tickets.getRepairType()))
                .findFirst()
                .orElse(null);
        if (dictDataRespDTO != null) {
            type = dictDataRespDTO.getLabel();
        }
        // 构建消息
        String message = String.format(
                "尊敬的客户您好：您的爱车%s已%s完毕，请您前来取车。若有问题可以联系%s，联系电话：%s"
                , tickets.getCarNo(), type, noticeCusVO.getName(), noticeCusVO.getMobile()
        );
        // todo 小程序通知客户
        if (null != cus && StringUtils.isNotEmpty(cus.getPhoneNumber())) {
            // 短信通知客户
            SendSmsUtil.sendMsgCommon(new String[]{message}, cus.getPhoneNumber(), "1400852709", "蓝安汽车小程序", "2143603");
        }
        // 记录日志
        String recordStr = noticeCusVO.getName()
                + "通知客户取车:" + message;
        repairRecordsService.saveRepairRecord(noticeCusVO.getId(), null, RecordTypeEnum.TZQC.getCode(), recordStr, null);
    }

    /**
     * 重新计算工单的一些数值，子表也要重新计算
     *
     * @param ticketId 工单ID
     * @author 小李
     * @date 15:47 2024/10/24
     **/
    @Override
    @DSTransactional
    public boolean computeTicket(String ticketId) {
        // 查子表
        List<DlRepairTitem> list = titemService.list(new LambdaQueryWrapper<DlRepairTitem>().eq(DlRepairTitem::getTicketId, ticketId));
        // 计算子表
        if (CollectionUtil.isEmpty(list)) {
            throw exception0(500, "没有项目、配件或其他");
        }
        List<DlRepairTitem> updateItems = list.stream().map(item -> {
            DlRepairTitem titem = new DlRepairTitem();
            titem.setId(item.getId());
            // 取出折扣
            BigDecimal discount = item.getItemDiscount() == null ? new BigDecimal("1") : item.getItemDiscount();
            titem.setItemMoney(item.getItemPrice().multiply(BigDecimal.valueOf(item.getItemCount())).multiply(discount));
            return titem;
        }).collect(Collectors.toList());
        // 更新
        titemService.updateBatchById(updateItems);

        // 计算主表
        // 查新的子表信息
        List<DlRepairTitem> newList = titemService.list(new LambdaQueryWrapper<DlRepairTitem>().eq(DlRepairTitem::getTicketId, ticketId));
        DlRepairTickets tickets = new DlRepairTickets();
        // 计算子项总数
        int count = newList.stream().mapToInt(DlRepairTitem::getItemCount).sum();
        tickets.setId(ticketId);
        tickets.setCount(count);
        // 分类计算各自的总价和最终工单总价
        Map<String, List<DlRepairTitem>> map = newList.stream().collect(Collectors.groupingBy(DlRepairTitem::getItemType));
        // 01项目，02，配件，03，其他
        tickets.setTotalPrice(BigDecimal.ZERO);
        map.forEach((k, v) -> {
            BigDecimal reduce = v.stream().map(DlRepairTitem::getItemMoney).reduce(BigDecimal.ZERO, BigDecimal::add);
            tickets.setTotalPrice(tickets.getTotalPrice().add(reduce));
            if (k.equals("01")) {
                tickets.setProjectPrice(reduce);
            } else if (k.equals("02")) {
                tickets.setPartPrice(reduce);
            } else if (k.equals("03")) {
                tickets.setOtherPrice(reduce);
            }
        });

        // 工单总价还需要变，把其他新加的费用也需要算上
        // 查主表
        DlRepairTickets oldTicket = baseMapper.selectById(ticketId);
        // 管理费
        BigDecimal managerMoney = ObjectUtil.isNotEmpty(oldTicket.getManagerMoney()) ? oldTicket.getManagerMoney() : new BigDecimal("0");
        // 救援费
        BigDecimal rescueMoney = ObjectUtil.isNotEmpty(oldTicket.getRescueMoney()) ? oldTicket.getRescueMoney() : new BigDecimal("0");
        // 三包费
        BigDecimal threePackMoney = ObjectUtil.isNotEmpty(oldTicket.getThreePackMoney()) ? oldTicket.getThreePackMoney() : new BigDecimal("0");
        // 定损费
        BigDecimal confirmFaultMoney = ObjectUtil.isNotEmpty(oldTicket.getConfirmFaultMoney()) ? oldTicket.getConfirmFaultMoney() : new BigDecimal("0");

        tickets.setTotalPrice(tickets.getTotalPrice().add(managerMoney).add(rescueMoney).add(threePackMoney).add(confirmFaultMoney));

        // 因为订单是在工单创建的时候就新增了，后面加的配件之类的金额没有同步到订单，这里同步一下
        RepairOrderInfo orderInfo = repairOrderInfoService.getOne(new LambdaQueryWrapper<RepairOrderInfo>().eq(RepairOrderInfo::getGoodsId, ticketId));
        RepairOrderInfo newOrderInfo = new RepairOrderInfo();
        newOrderInfo.setId(orderInfo.getId());
        // 更新商品原价和实付金额
        newOrderInfo.setGoodsPrice(tickets.getTotalPrice());
        newOrderInfo.setPayMoney(tickets.getTotalPrice());
        repairOrderInfoService.updateById(newOrderInfo);

        return baseMapper.updateById(tickets) > 0;
    }

    /**
     * 新增工单子项
     *
     * @param respVO 主要有两个参数，主表ID,itemList(这个需要前端处理成子表的对象能接收的数据)
     * @author 小李
     * @date 19:59 2024/10/24
     **/
    @Override
    @DSTransactional
    public void addItems(DlRepairTicketsRespVO respVO) {
        // 先判断是否施工人员和销售人员是否都设置了
        respVO.getItemList().forEach(item -> {
            if (ObjectUtil.isEmpty(item.getRepairIds())) {
                throw exception0(500, "施工人员尚未设置");
            }
            if (ObjectUtil.isEmpty(item.getSaleId())) {
                throw exception0(500, "销售人员尚未设置");
            }
        });
        // 新增子表
        List<DlRepairTitem> itemList = respVO.getItemList();
        itemList.forEach(item -> item.setTicketId(respVO.getId()));
        titemService.saveBatch(itemList);

        // 更新主表
        boolean flag = computeTicket(respVO.getId());
        if (!flag) {
            throw exception0(500, "系统错误");
        }
    }

    /**
     * 超时自动总检
     *
     * @author 小李
     * @date 16:35 2024/10/26
     **/
    @Override
    @DSTransactional
    public void autoInspection() {
        // 查所有需要总检但还没总检的工单
        List<DlRepairTickets> tickets = baseMapper.selectList(new LambdaQueryWrapper<DlRepairTickets>().eq(DlRepairTickets::getTicketsWorkStatus, "05"));
        // 如果没有，直接退出
        if (CollectionUtil.isEmpty(tickets)) {
            return;
        }
        // 获取超时自动总检配置的时间
        List<DictDataRespDTO> dataList = dictDataApi.getDictDataList(BaseConstants.AUTO_INSPECTION);
        // 理论上是只有一个的，但防止被删了或者别的什么原因，直接报错
        DictDataRespDTO dictDataRespDTO = dataList.stream().findFirst().orElse(null);
        if (ObjectUtil.isEmpty(dictDataRespDTO)) {
            log.error("超时自动总检时间没有配置");
            return;
        }
        // 计算超时的工单
        // 超时时间
        int time = Integer.parseInt(dictDataRespDTO.getValue());

        List<DlRepairTickets> list = tickets.stream().filter(item -> {
            // 当前时间---放到里面是因为如果数据量多，动态获取当前时间更能满足需求
            LocalDateTime now = LocalDateTime.now();
            return ChronoUnit.MINUTES.between(item.getUpdateTime(), now) > time;
        }).collect(Collectors.toList());

        // 同步工单的配件申请表到工单,如果都没超时，退出
        if (CollectionUtil.isEmpty(list)) {
            return;
        }
        // 先取总检们
        // 取这个角色的角色信息
        RoleReqDTO roleInfo = roleApi.getRoleInfo(RepairRoleEnum.INSPECTION.getCode());
        // 通过角色信息查有这个角色的人
        List<Long> ids = permissionApi.getUserIdByRoleId(roleInfo.getId());
        // 如果没有总检，直接报错退出
        if (CollectionUtil.isEmpty(ids)) {
            log.error("系统中没有总检");
            return;
        }
        // 在过滤一次，主要是过滤掉没法同步申请表到工单的数据
        List<DlRepairTickets> result = list.stream().filter(item -> {
            boolean flag = syncTicketWaresToTicket(item.getId());
            // 如果出现更新不到的情况
            if (!flag) {
                // 通知总检，让其手动操作
                ids.forEach(id -> {
                    repairWorkerService.sentMessage(id, "待总检工单：" + item.getTicketNo() + "已经超时，但无法自动总检，请手动处理");
                });
            }
            return flag;
        }).collect(Collectors.toList());
        // 如果没有记录同步成功，报错并退出
        if (CollectionUtil.isEmpty(result)) {
            log.error("自动总检失败，原因可能是无法同步申请表到工单");
        }
        // 移交服务顾问
        List<DlRepairTickets> updateTickets = result.stream().map(item -> {
            DlRepairTickets ticket = new DlRepairTickets();
            ticket.setId(item.getId());
            ticket.setNowRepairId(Long.valueOf(item.getAdviserId()));
            ticket.setNowRepairName(item.getAdviserName());
            return ticket;
        }).collect(Collectors.toList());
        baseMapper.updateById(updateTickets);
        // 通知总检和服务顾问
        result.forEach(item -> {
            repairWorkerService.sentMessage(Long.valueOf(item.getAdviserId()), "您有新的工单可以出厂检验");
            ids.forEach(id -> {
                repairWorkerService.sentMessage(id, "工单：" + item.getTicketNo() + "已由系统自动总检并移交服务顾问");
            });
            // 记录日志
            repairRecordsService.saveRepairRecord(item.getId(), null, RecordTypeEnum.ZJ.getCode(), "该工单由系统自动总检完成", null);
        });
    }

    /**
     * 修改工单主表，只是主表
     *
     * @param respVO 请求对象
     * @author 小李
     * @date 10:25 2024/10/30
     **/
    @Override
    @DSTransactional
    public void updateTicket(DlRepairTicketsRespVO respVO) {
        // 更新主表
        baseMapper.updateById(respVO);

        // 重新计算
        boolean flag = computeTicket(respVO.getId());
        if (!flag) {
            throw exception0(500, "系统异常");
        }
    }

    /**
     * 删除工单
     *
     * @param id 工单ID
     * @author 小李
     * @date 20:05 2024/11/1
     **/
    @Override
    @DSTransactional
    public void removeTicketById(String id) {
        // 删除订单
        repairOrderInfoService.remove(new LambdaQueryWrapper<RepairOrderInfo>().eq(RepairOrderInfo::getGoodsId, id));
        // 删除工单
        baseMapper.deleteById(id);
        // 删除工单子表
        titemService.remove(new LambdaQueryWrapper<DlRepairTitem>().eq(DlRepairTitem::getTicketId, id));
    }
}




