StoreServiceImpl.java 21.8 KB
package vion.service.impl;

import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.map.MapUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.toolkit.Db;
import com.github.yulichang.base.MPJBaseServiceImpl;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import io.github.linpeilie.Converter;
import lombok.RequiredArgsConstructor;
import org.dromara.hutool.core.array.ArrayUtil;
import org.dromara.hutool.core.collection.CollUtil;
import org.dromara.hutool.core.collection.ListUtil;
import org.dromara.hutool.core.date.DateUtil;
import org.dromara.hutool.core.io.file.FileNameUtil;
import org.dromara.hutool.core.io.file.FileUtil;
import org.dromara.hutool.core.lang.Opt;
import org.dromara.hutool.core.text.StrUtil;
import org.dromara.hutool.core.util.ObjUtil;
import org.dromara.hutool.crypto.SecureUtil;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import vion.dto.StatusDTO;
import vion.dto.StoreDTO;
import vion.mapper.StoreMapper;
import vion.mapper.TaskMapper;
import vion.model.*;
import vion.service.*;
import vion.vo.ContractVO;
import vion.vo.StoreVO;
import vion.vo.TagVO;
import vion.vo.UserVO;

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

@Service
@RequiredArgsConstructor
public class StoreServiceImpl extends MPJBaseServiceImpl<StoreMapper, Store> implements IStoreService {

    private final IAccountService accountService;
    private final IRContractStoreService contractStoreService;
    private final IContractPaymentService contractPaymentService;
    private final IContractService contractService;
    private final IFileService fileService;
    private final IFaultLogService faultLogService;
    private final IFormService formService;
    private final IInspectService inspectService;
    private final ITagService tagService;
    private final IRStoreTagService storeTagService;
    private final IRStoreUserService storeUserService;
    private final Converter converter;
    // 引入 taskService 会循环依赖
    private final TaskMapper taskMapper;

    @Value("${fileUrl:}")
    private String fileUrl;

    @Override
    public Page<StoreVO> getStoreList(StoreDTO data) {
        var storeIdListByTagId = Optional.ofNullable(data.getTagId())
                .map(tagId -> storeTagService.lambdaQuery().eq(RStoreTag::getTagId, tagId).list())
                .map(storeTagList -> storeTagList.stream().map(RStoreTag::getStoreId).collect(Collectors.toList()))
                .filter(CollUtil::isNotEmpty)
                .orElse(List.of(-1L));

        var storeIdListByMainUser = Optional.ofNullable(data.getMainUser())
                .map(mainUser -> storeUserService.lambdaQuery().eq(RStoreUser::getUserId, mainUser).eq(RStoreUser::getIsMain, 1).list())
                .map(storeUserList -> storeUserList.stream().map(RStoreUser::getStoreId).collect(Collectors.toList()))
                .filter(CollUtil::isNotEmpty)
                .orElse(List.of(-1L));

        var store = converter.convert(data, Store.class);
        var storeList = this.lambdaQuery(store)
                .in(ObjUtil.notEquals(storeIdListByTagId.get(0), -1L), Store::getId, storeIdListByTagId)
                .in(ObjUtil.notEquals(storeIdListByMainUser.get(0), -1L), Store::getId, storeIdListByMainUser)
                .orderByDesc(Store::getIsImportant, Store::getCreateTime)
                .page(Page.of(data.getPageNum(), data.getPageSize()));
        // todo 缓存
        List<Account> accountList = accountService.list();

        var storeIdList = Opt.ofEmptyAble(storeList.getRecords())
                .map(recs -> recs.stream().map(Store::getId).collect(Collectors.toList()))
                .orElse(ListUtil.empty());

        // storeId -> 文件数量
        var store2CntMap = Opt.ofEmptyAble(storeIdList)
                .map(l -> fileService.lambdaQuery()
                        .in(FileInfo::getSourceId, l)
                        .in(FileInfo::getStoreId, l)
                        .in(FileInfo::getSourceType, 1, 24, 25, 26, 27)
                        .list())
                .map(fileList -> fileList.stream().collect(Collectors.groupingBy(FileInfo::getStoreId, Collectors.counting())))
                .orElse(MapUtil.empty());
        // storeId -> 项目绑定的合同的 list
        var storeIdMap = Opt.ofEmptyAble(storeIdList)
                .map(l -> contractStoreService.lambdaQuery().in(RContractStore::getStoreId, l).list())
                .map(contractStoreList -> contractStoreList.stream().collect(Collectors.groupingBy(RContractStore::getStoreId)))
                .orElse(MapUtil.empty());
        // 项目绑定的合同的id
        var contractIdList = Opt.ofEmptyAble(storeIdMap.values())
                .map(values -> values.stream()
                        .flatMap(List::stream)
                        .map(RContractStore::getContractId)
                        .collect(Collectors.toList()))
                .orElse(Collections.emptyList());
        // contractId -> contract
        var id2ContractMap = Opt.ofEmptyAble(contractIdList)
                .map(l -> contractService.lambdaQuery()
                        .select(Contract::getId, Contract::getName, Contract::getContractNo, Contract::getType, Contract::getSignDate, Contract::getWarrantyPeriod, Contract::getFinalDate, Contract::getStatus, Contract::getSaleName, Contract::getCustomerName, Contract::getMaintainSdate, Contract::getMaintainEdate)
                        .in(Contract::getId, l).list())
                .map(contractList -> contractList.stream().collect(Collectors.toMap(Contract::getId, Function.identity())))
                .orElse(MapUtil.empty());

        // contractId -> 合同付款约定
        var contractId2CpMap = Opt.ofEmptyAble(contractIdList)
                .map(l -> contractPaymentService.lambdaQuery().in(ContractPayment::getContractId, l).in(ContractPayment::getPaymentType, 3, 4).isNotNull(ContractPayment::getNodeDate).list())
                .map(contractPaymentList -> contractPaymentList.stream().collect(Collectors.groupingBy(ContractPayment::getContractId, Collectors.collectingAndThen(Collectors.toList(), v -> {
                    v.sort(Comparator.comparing(ContractPayment::getPaymentType).reversed());
                    return v;
                }))))
                .orElse(MapUtil.empty());

        // storeId -> taskIdList
        var store2TaskIdMap = Opt.ofEmptyAble(storeIdList)
                .map(l -> taskMapper.selectList(Wrappers.lambdaQuery(Task.class).in(Task::getStoreId, l)))
                .map(taskList -> taskList.stream().collect(Collectors.groupingBy(Task::getStoreId, Collectors.mapping(Task::getId, Collectors.toList()))))
                .orElse(MapUtil.empty());
        var taskIds = store2TaskIdMap.values().stream()
                .flatMap(List::stream)
                .collect(Collectors.toList());
        var taskServiceOrderMap = Opt.ofEmptyAble(taskIds)
                .map(t -> Db.list(Wrappers.lambdaQuery(ServiceOrder.class).in(ServiceOrder::getTaskId, taskIds)))
                .map(serviceOrderList -> serviceOrderList.stream().collect(Collectors.groupingBy(ServiceOrder::getTaskId)))
                .orElse(MapUtil.empty());

        // storeId -> tagList
        var storeId2TagMap = Opt.ofEmptyAble(storeIdList)
                .map(l -> {
                    MPJLambdaWrapper<Tag> wrapper = new MPJLambdaWrapper<Tag>()
                            .select(Tag::getId, Tag::getName, Tag::getRemark)
                            .selectAs(RStoreTag::getStoreId, TagVO::getStoreId)
                            .leftJoin(RStoreTag.class, RStoreTag::getTagId, Tag::getId)
                            .in(RStoreTag::getStoreId, l);
                    return tagService.selectJoinList(TagVO.class, wrapper);
                })
                .filter(CollUtil::isNotEmpty)
                .map(tagVOS -> tagVOS.stream().collect(Collectors.groupingBy(TagVO::getStoreId)))
                .orElse(MapUtil.empty());


        // storeId -> userId(项目主要负责人 or 项目经理)
        var storeId2UserIdMap = Opt.ofEmptyAble(storeIdList)
                .map(l -> storeUserService.lambdaQuery().in(RStoreUser::getStoreId, l).eq(RStoreUser::getIsMain, 1).list())
                .map(suList -> suList.stream().collect(Collectors.toMap(RStoreUser::getStoreId, RStoreUser::getUserId)))
                .orElse(MapUtil.empty());

        List<StoreVO> storeVOList = new ArrayList<>();
        storeList.getRecords().forEach(item -> {
            var storeVO = converter.convert(item, StoreVO.class);
            storeVO.setAccountName(accountList.stream().filter(v -> v.getId().equals(item.getAccountId())).map(Account::getName).findFirst().orElse("--"));
            storeVO.setFileNum(store2CntMap.getOrDefault(item.getId(), 0L));

            Long storeId = item.getId();
            // 关联合同
            var contractStores = storeIdMap.get(storeId);
            storeVO.setContractCount(Opt.ofEmptyAble(contractStores).map(List::size).orElse(0));
            storeVO.setContractVOList(id2ContractMap.values().stream()
                    .filter(v -> CollUtil.isNotEmpty(contractStores) && contractStores.stream().map(RContractStore::getContractId).toList().contains(v.getId()))
                    .map(v -> converter.convert(v, ContractVO.class))
                    .collect(Collectors.toList()));

            var masterContract = id2ContractMap.getOrDefault(item.getMasterContract(), null);
            // 关联合同验收时间
            Opt.ofNullable(masterContract)
                    .map(c -> contractId2CpMap.getOrDefault(c.getId(), ListUtil.empty()))
                    .filter(CollUtil::isNotEmpty)
                    .map(l -> l.get(0).getNodeDate())
                    .ifPresent(nodeDate -> masterContract.setFinalDate(nodeDate));
            storeVO.setMainContract(Opt.ofNullable(masterContract).map(c -> converter.convert(c, ContractVO.class)).orElse(null));

            storeVO.setTaskCount(store2TaskIdMap.getOrDefault(storeId, ListUtil.empty()).size());

            // 补充服务单
            if (store2TaskIdMap.containsKey(item.getId())) {
                var cnt = store2TaskIdMap.get(item.getId())
                        .stream().mapToLong(taskId -> taskServiceOrderMap.getOrDefault(taskId, ListUtil.empty()).size()).sum();
                storeVO.setServiceOrderCount((int) cnt);
            }
            // 关联标签
            storeVO.setTagList(storeId2TagMap.getOrDefault(storeId, ListUtil.empty()));
            // 关联项目负责人 or 项目经理
            storeVO.setMainUser(storeId2UserIdMap.getOrDefault(storeId, null));
            storeVOList.add(storeVO);
        });
        return Page.<StoreVO>of(data.getPageNum(), data.getPageSize(), storeList.getTotal()).setRecords(storeVOList);
    }

    @Override
    public String updateStoreStage(StatusDTO statusDTO, String token) {
        this.update(Wrappers.<Store>lambdaUpdate()
                .set(Store::getProjectStage, statusDTO.getProjectStage())
                .eq(Store::getId, statusDTO.getStoreId()));
        UserVO userVO = (UserVO) StpUtil.getTokenSession().get("curLoginUser");

        Opt.ofNullable(statusDTO.getFiles()).ifPresent(tmpFiles -> {
            for (MultipartFile infile : tmpFiles) {
                //上传url地址
                String orgName = infile.getOriginalFilename();
                String mainName = FileNameUtil.mainName(orgName);
                String fileExt = FileNameUtil.extName(orgName);
                String filename = StrUtil.format("{}_{}.{}", mainName, DateUtil.format(new Date(), "yyyyMMdd_HHmmssSSS"), fileExt);
                String path = fileUrl + FileUtil.FILE_SEPARATOR + statusDTO.getStoreId() + FileUtil.FILE_SEPARATOR + statusDTO.getSourceId() + FileUtil.FILE_SEPARATOR + filename;
                File file = FileUtil.touch(path);
                try {
                    infile.transferTo(file);
                } catch (IOException e) {
                    log.error("保存文件出错", e);
                }
                // storeId = sourceId
                FileInfo tempFileInfo = new FileInfo();
                tempFileInfo.setStoreId(statusDTO.getStoreId());
                tempFileInfo.setSourceId(statusDTO.getSourceId());
                tempFileInfo.setSourceType(statusDTO.getSourceType());
                tempFileInfo.setName(filename);
                tempFileInfo.setUrl(path);
                tempFileInfo.setType(FileNameUtil.extName(file));
                tempFileInfo.setSha256(SecureUtil.sha256(file).toUpperCase());
                tempFileInfo.setUploader(userVO.getUsername());

                fileService.save(tempFileInfo);
            }
        });
        return "更新成功";
    }

    @Override
    public String calMaintainStatus(List<Long> storeIdList) {
        List<Store> storeList = this.lambdaQuery().in(CollUtil.isNotEmpty(storeIdList), Store::getId, storeIdList).list();
        Map<Long, List<Long>> storeId2ContractIdMap = Opt.ofEmptyAble(contractStoreService.list())
                .map(l -> l.stream().collect(Collectors.groupingBy(RContractStore::getStoreId, Collectors.mapping(RContractStore::getContractId, Collectors.toList()))))
                .orElse(MapUtil.empty());
        List<Contract> contractList = contractService.listByIds(storeId2ContractIdMap.values().stream().flatMap(List::stream).collect(Collectors.toList()));
        // storeId 为 key,List<Contract> 为 value 的 map
        Map<Long, List<Contract>> storeId2ContractListMap = storeList.stream()
                .collect(Collectors.toMap(Store::getId, store ->
                        storeId2ContractIdMap.getOrDefault(store.getId(), ListUtil.empty())
                                .stream()
                                .map(contractId -> contractList.stream().filter(contract -> contract.getId().equals(contractId)).findFirst().orElse(null))
                                .filter(Objects::nonNull)
                                .collect(Collectors.toList())));

        var contractId2CpMap = Opt.ofEmptyAble(contractPaymentService.lambdaQuery().in(ContractPayment::getPaymentType, 3, 4).isNotNull(ContractPayment::getNodeDate).list())
                .map(contractPaymentList -> contractPaymentList.stream().collect(Collectors.groupingBy(ContractPayment::getContractId, Collectors.collectingAndThen(Collectors.toList(), v -> {
                    v.sort(Comparator.comparing(ContractPayment::getPaymentType).reversed());
                    return v;
                }))))
                .orElse(MapUtil.empty());

        List<Store> updStoreList = new ArrayList<>();
        for (Store s : storeList) {
            s.setMaintainStatus("--");
            List<Contract> contracts = storeId2ContractListMap.getOrDefault(s.getId(), ListUtil.empty());
            if (CollUtil.isNotEmpty(contracts)) {
                // 1.项目关联的主合同在维保期内,那么这个项目就是在保
                Contract masterContract = contracts.stream().filter(contract -> ObjUtil.equals(contract.getId(), s.getMasterContract())).findFirst().orElse(null);
                if (ObjUtil.isNotNull(masterContract)) {
                    Integer warrantyPeriod = masterContract.getWarrantyPeriod();

                    Opt.ofNullable(contractId2CpMap.getOrDefault(masterContract.getId(), ListUtil.empty()))
                            .filter(CollUtil::isNotEmpty)
                            .map(l -> l.get(0).getNodeDate())
                            .ifPresent(masterContract::setFinalDate);
                    Date finalDate = masterContract.getFinalDate();

                    if (ArrayUtil.isAllNotNull(finalDate, warrantyPeriod)) {
                        Date finalDateEnd = DateUtil.offsetMonth(finalDate, warrantyPeriod);
                        boolean isIn = DateUtil.isIn(new Date(), finalDate, finalDateEnd);
                        if (isIn) {
                            s.setMaintainStatus("在保");
                            Store updStore = new Store();
                            updStore.setId(s.getId());
                            updStore.setMaintainStatus(s.getMaintainStatus());
                            updStoreList.add(updStore);
                            continue;
                        } else {
                            s.setMaintainStatus("脱保");
                        }
                    }
                }

                // 2.主合同脱保,那就判断维保合同
                Set<Boolean> maintainStatusSet = new HashSet<>();
                contracts.stream()
                        .filter(contract -> ObjUtil.equals(contract.getType(), 1))
                        .map(Contract::getMaintainEdate)
                        .filter(Objects::nonNull)
                        .forEach(maintainEdate -> maintainStatusSet.add(DateUtil.compare(maintainEdate, new Date()) >= 0));
                s.setMaintainStatus(CollUtil.isEmpty(maintainStatusSet) ? s.getMaintainStatus() : maintainStatusSet.contains(true) ? "在保" : "脱保");
            }
            Store updStore = new Store();
            updStore.setId(s.getId());
            updStore.setMaintainStatus(s.getMaintainStatus());
            updStoreList.add(updStore);
        }
        this.updateBatchById(updStoreList);
        return "计算成功";
    }

    @Override
    public List<StoreVO> storeScreen() {
        MPJLambdaWrapper<Store> wrapper = new MPJLambdaWrapper<Store>()
                .select(Store::getId, Store::getName, Store::getTaskDetail, Store::getFinishDate, Store::getPercentage, Store::getStuckPoint, Store::getIsImportant)
                .selectCollection(User.class, StoreVO::getUserNameList, map -> map.result(User::getUsername))
                .leftJoin(RStoreUser.class, on -> on.eq(RStoreUser::getStoreId, Store::getId).eq(RStoreUser::getIsMain, 1))
                .leftJoin(User.class, User::getId, RStoreUser::getUserId)
                .eq(Store::getIsImportant, 1)
                .orderByAsc(Store::getFinishDate);
        return this.selectJoinList(StoreVO.class, wrapper);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public String mergeStore(StoreDTO dto) {
        // targetId 合并到 sourceId
        Long sourceId = dto.getSourceId();
        Store store = this.getById(sourceId);
        Long targetId = dto.getTargetId();
        // 项目关联合同合并
        Opt.ofEmptyAble(contractStoreService.lambdaQuery().select(RContractStore::getContractId).eq(RContractStore::getStoreId, targetId).list())
                .ifPresent(l -> {
                    List<RContractStore> existList = contractStoreService.lambdaQuery().select(RContractStore::getContractId).eq(RContractStore::getStoreId, sourceId).list();

                    List<RContractStore> insList = l.stream().filter(v -> !existList.contains(v)).map(v -> {
                        RContractStore contractStore = new RContractStore();
                        contractStore.setContractId(v.getContractId());
                        contractStore.setStoreId(sourceId);
                        return contractStore;
                    }).collect(Collectors.toList());
                    contractStoreService.saveBatch(insList);
                    contractStoreService.lambdaUpdate().eq(RContractStore::getStoreId, targetId).remove();
                });

        // 工单相关合并
        taskMapper.update(new Task(), Wrappers.lambdaUpdate(Task.class).set(Task::getStoreId, sourceId).eq(Task::getStoreId, targetId));
        fileService.lambdaUpdate().set(FileInfo::getStoreId, sourceId).eq(FileInfo::getStoreId, targetId).eq(FileInfo::getSourceType, 3).update();
        faultLogService.lambdaUpdate().set(FaultLog::getStoreId, sourceId).eq(FaultLog::getStoreId, targetId).update();
        formService.lambdaUpdate().set(Form::getStoreId, sourceId).set(Form::getAccountId, store.getAccountId()).eq(Form::getStoreId, targetId).update(new Form());
        inspectService.lambdaUpdate().set(Inspect::getStoreId, sourceId).set(Inspect::getAccountId, store.getAccountId()).eq(Inspect::getStoreId, targetId).update(new Inspect());
        // 删除 targetId 对应的项目
        this.removeById(targetId);
        return "合并成功";
    }

    @Override
    public String addTag(Long id, List<Long> tagIdList) {
        storeTagService.lambdaUpdate().eq(RStoreTag::getStoreId, id).remove();
        Opt.ofEmptyAble(tagIdList).ifPresent(l -> {
            List<RStoreTag> list = l.stream().map(tagId -> {
                RStoreTag storeTag = new RStoreTag();
                storeTag.setStoreId(id);
                storeTag.setTagId(tagId);
                return storeTag;
            }).collect(Collectors.toList());
            storeTagService.saveBatch(list);
        });
        return "成功";
    }

    @Override
    public List<Map<String, Number>> fileDetail(List<Long> storeIdList) {
        var fileList = fileService.lambdaQuery()
                .in(FileInfo::getSourceId, storeIdList)
                .in(FileInfo::getStoreId, storeIdList)
                .in(FileInfo::getSourceType, 1, 24, 25, 26, 27)
                .list();
        var storeId2FileMap = fileList.stream().collect(Collectors.groupingBy(FileInfo::getStoreId,Collectors.groupingBy(FileInfo::getSourceType)));
        return storeIdList.stream().map(storeId -> {
            var fileMap = storeId2FileMap.getOrDefault(storeId, MapUtil.empty());
            Map<String, Number> map = new HashMap<>();
            map.put("storeId", storeId);
            map.put("24", fileMap.getOrDefault(24, List.of()).size());
            map.put("25", fileMap.getOrDefault(25, List.of()).size());
            map.put("26", fileMap.getOrDefault(26, List.of()).size());
            map.put("27", fileMap.getOrDefault(27, List.of()).size());
            return map;
        }).collect(Collectors.toList());
    }
}