ContractServiceImpl.java 18.3 KB
package vion.service.impl;

import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.date.DateUnit;
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.JSONObject;
import cn.hutool.json.JSONUtil;
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.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import vion.dto.ContractDTO;
import vion.mapper.ContractMapper;
import vion.model.*;
import vion.service.*;
import vion.vo.*;

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

/**
 * @author HlQ
 * @date 2023/11/29
 */
@Service
@RequiredArgsConstructor
public class ContractServiceImpl extends MPJBaseServiceImpl<ContractMapper, Contract> implements IContractService {

    private final IFileService fileService;
    private final IContractPaymentService contractPaymentService;
    private final IRContractStoreService contractStoreService;
    private final IContractLogService contractLogService;
    private final Converter converter;

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

    @Override
    public Page<ContractVO> list(ContractDTO dto) {
        Contract contract = converter.convert(dto, new Contract());

        List<Long> contractIdList = new ArrayList<>();
        if (dto.getSwitchFlag() == 1) {
            contractIdList = contractStoreService.listObjs(Wrappers.<RContractStore>lambdaQuery()
                    .select(RContractStore::getContractId), o -> Long.valueOf(o.toString()));
        }

        UserVO userVO = (UserVO) StpUtil.getTokenSession().get("curLoginUser");
        String saleName = Opt.ofEmptyAble(userVO.getRoleVOList())
                .map(l -> l.stream().map(RoleVO::getCode).collect(Collectors.toList()))
                .map(roleCodeList -> {
                    if (!roleCodeList.contains("admin") && !roleCodeList.contains("shangwu") && !roleCodeList.contains("caiwu")) {
                        return userVO.getUsername();
                    } else {
                        return "";
                    }
                }).orElse("未知");

        Page<Contract> contractList = this.lambdaQuery(contract)
                .between(ArrayUtil.isAllNotNull(dto.getSignDateStart(), dto.getSignDateEnd()), Contract::getSignDate, dto.getSignDateStart(), dto.getSignDateEnd())
                .notIn(CollUtil.isNotEmpty(contractIdList), Contract::getId, contractIdList)
                .eq(StrUtil.isNotBlank(saleName), Contract::getSaleName, saleName)
                .orderByDesc(Contract::getEntryTime)
                .page(Page.of(dto.getPageNum(), dto.getPageSize()));
        List<ContractVO> contractVOList = converter.convert(contractList.getRecords(), ContractVO.class);
        // 查出合同关联的项目名
        completeStoreName(contractVOList);
        return Page.<ContractVO>of(contractList.getCurrent(), contractList.getSize(), contractList.getTotal()).setRecords(contractVOList);
    }

    @Override
    public Page<ContractVO> listPart(ContractDTO dto) {
        Contract contract = converter.convert(dto, new Contract());

        List<Long> ids = Opt.ofNullable(dto.getStoreId())
                .map(storeId -> contractStoreService.listObjs(Wrappers.<RContractStore>lambdaQuery().select(RContractStore::getContractId).eq(RContractStore::getStoreId, dto.getStoreId()), o -> Long.valueOf(o.toString())))
                .filter(CollUtil::isNotEmpty)
                .orElse(ListUtil.of(0L));

        Page<Contract> contractList = this.lambdaQuery(contract)
                .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(CollUtil.isNotEmpty(ids), Contract::getId, ids)
                .orderByDesc(Contract::getEntryTime)
                .page(Page.of(dto.getPageNum(), dto.getPageSize()));
        List<ContractVO> contractVOList = converter.convert(contractList.getRecords(), ContractVO.class);
        // 查出合同关联的项目名
        completeStoreName(contractVOList);
        return Page.<ContractVO>of(contractList.getCurrent(), contractList.getSize(), contractList.getTotal()).setRecords(contractVOList);
    }

    private void completeStoreName(List<ContractVO> contractVOList) {
        Map<Long, List<Long>> contractStoreIdsMap = Opt.ofEmptyAble(contractVOList)
                .map(list -> list.stream().map(ContractVO::getId).collect(Collectors.toList()))
                .map(contractIds -> contractStoreService.list(Wrappers.<RContractStore>lambdaQuery().in(RContractStore::getContractId, contractIds)))
                .map(contractStoreList -> contractStoreList.stream().collect(Collectors.groupingBy(RContractStore::getContractId, Collectors.mapping(RContractStore::getStoreId, Collectors.toList()))))
                .orElse(MapUtil.empty());

        Opt.of(contractStoreIdsMap)
                .filter(MapUtil::isNotEmpty)
                .map(map -> map.values().stream().flatMap(List::stream).collect(Collectors.toList()))
                .map(storeIds -> Db.listByIds(storeIds, Store.class))
                .map(storeList -> storeList.stream().collect(Collectors.toMap(Store::getId, Function.identity())))
                .ifPresent(storeId2StoreMap -> contractVOList.forEach(contractVO -> {
                    if (contractStoreIdsMap.containsKey(contractVO.getId())) {
                        List<StoreVO> storeVOS = converter.convert(contractStoreIdsMap.get(contractVO.getId()).stream().map(storeId2StoreMap::get).collect(Collectors.toList()), StoreVO.class);
                        contractVO.setStoreVOS(storeVOS);
                    }
                }));
    }

    @Override
    public ContractVO getVOById(Long id) {
        MPJLambdaWrapper<Contract> wrapper = new MPJLambdaWrapper<Contract>()
                .selectAll(Contract.class)
                .selectCollection(ContractLog.class, ContractVO::getContractLogs)
                .leftJoin(ContractLog.class, ContractLog::getContractNo, Contract::getContractNo)
                .eq(Contract::getId, id);
        return this.selectJoinOne(ContractVO.class, wrapper);
    }

    @Override
    public ContractVO getByNo(String no) {
        Contract contract = this.lambdaQuery()
                .select(Contract::getId, Contract::getName, Contract::getContractNo, Contract::getType, Contract::getSignDate, Contract::getWarrantyPeriod, Contract::getFinalDate, Contract::getStatus, Contract::getSaleName, Contract::getCustomerName)
                .eq(Contract::getContractNo, no)
                .one();
        ContractVO contractVO = converter.convert(contract, new ContractVO());
        Assert.notNull(contractVO, "合同不存在");
        MPJLambdaWrapper<RContractStore> wrapper = new MPJLambdaWrapper<RContractStore>()
                .select(Store::getId, Store::getName)
                .leftJoin(Store.class, Store::getId, RContractStore::getStoreId)
                .eq(RContractStore::getContractId, contract.getId());
        List<StoreVO> storeVOS = contractStoreService.selectJoinList(StoreVO.class, wrapper);
        contractVO.setStoreVOS(storeVOS);
        return contractVO;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public String updateById(Long id, String contractNo, ContractDTO dto) {
        // todo 合同状态变更时,添加 nodeDate,未上线待联调
        Contract existContract = new Contract();
        if (ObjUtil.isNotNull(id)) {
            existContract = this.getById(id);
        } else if (StrUtil.isNotBlank(contractNo)) {
            existContract = this.lambdaQuery().eq(Contract::getContractNo, contractNo).one();
        }
        Assert.isFalse(BeanUtil.isEmpty(existContract), "合同不存在");

        // todo 如果当前合同进度在要修改的进度前,此时不能修改合同进度。e.g 当前合同进度是项目验收,此时传参过来到货,那么不能修改

        Contract contract = converter.convert(dto, new Contract());
        Long contractId = existContract.getId();
        contract.setId(contractId);
        contractPaymentService.calMoney(existContract, contract);
        if (this.updateById(contract)) {
            Opt.ofNullable(dto.getNodeDate())
                    .ifPresent(date -> {
                        contractPaymentService.lambdaUpdate()
                                .set(ContractPayment::getPaymentDate, date)
                                .eq(ContractPayment::getContractId, contractId)
                                .eq(ContractPayment::getPaymentType, dto.getStatus())
                                .update(new ContractPayment());
                    });

            UserVO userVO = (UserVO) StpUtil.getTokenSession().get("curLoginUser");

            ContractLog contractLog = new ContractLog();
            contractLog.setContractNo(existContract.getContractNo());
            if (dto.getStatus() == 1) {
                contractLog.setContent("合同:已签订");
            } else if (dto.getStatus() == 2) {
                contractLog.setContent("合同:已到货");
            } else if (dto.getStatus() == 3) {
                contractLog.setContent("合同:系统验收");
            } else if (dto.getStatus() == 4) {
                contractLog.setContent("合同:项目验收");
            } else if (dto.getStatus() == 5) {
                contractLog.setContent("合同:质保");
            } else if (dto.getStatus() == 6) {
                contractLog.setContent("合同:第一笔维保款");
            } else if (dto.getStatus() == 7) {
                contractLog.setContent("合同:第二笔维保款");
            } else if (dto.getStatus() == 8) {
                contractLog.setContent("合同:第三笔维保款");
            }
            contractLog.setOperator(userVO.getId());
            contractLogService.save(contractLog);

            Opt.ofNullable(dto.getFiles())
                    .ifPresent(fileList ->
                            Arrays.stream(fileList).forEach(infile -> {
                                //上传url地址
                                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 + "contract" + FileUtil.FILE_SEPARATOR + contract.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(-1L);
                                fileInfo.setSourceId(contract.getId());
                                fileInfo.setSourceType(dto.getSourceType());
                                fileInfo.setContractId(contract.getId());
                                fileInfo.setName(filename);
                                fileInfo.setUrl(path);
                                fileInfo.setType(fileExt);
                                fileInfo.setSha256(SecureUtil.sha256(file).toUpperCase());
                                fileInfo.setUploader(userVO.getUsername());
                                fileService.save(fileInfo);
                            }));
            return "更新成功";
        } else {
            return "更新失败";
        }
    }

    @Override
    public JSONObject calAmount(ContractDTO dto) {
        List<Contract> contracts = this.list(Wrappers.lambdaQuery(converter.convert(dto, Contract.class))
                .between(ArrayUtil.isAllNotNull(dto.getSignDateStart(), dto.getSignDateEnd()), Contract::getSignDate, dto.getSignDateStart(), dto.getSignDateEnd())
        );

        JSONObject obj = JSONUtil.createObj()
                .set("totalAmount", 0)
                .set("paidAmount", 0)
                .set("recAmount", 0)
                .set("outAmount", 0)
                .set("invoiceAmount", 0);
        Opt.ofEmptyAble(contracts)
                .ifPresent(list -> {
                    BigDecimal totalAmount = list.stream().map(Contract::getTotalAmount).reduce(BigDecimal.ZERO, BigDecimal::add);
                    BigDecimal paidAmount = list.stream().map(v -> NumberUtil.max(BigDecimal.ZERO, v.getPaidAmount())).reduce(BigDecimal.ZERO, BigDecimal::add);
                    BigDecimal recAmount = list.stream().map(v -> NumberUtil.max(BigDecimal.ZERO, v.getReceivableAmount())).reduce(BigDecimal.ZERO, BigDecimal::add);
                    BigDecimal outAmount = list.stream().map(Contract::getOutstandingAmount).reduce(BigDecimal.ZERO, BigDecimal::add);

                    List<String> noList = list.stream().map(Contract::getContractNo).collect(Collectors.toList());
                    List<BigDecimal> invoices = Db.listObjs(Wrappers.lambdaQuery(Invoice.class).select(Invoice::getInvoiceAmount).in(BeanUtil.isNotEmpty(dto), Invoice::getContractNo, noList), Invoice::getInvoiceAmount);
                    BigDecimal invoiceAmount = invoices.stream().reduce(BigDecimal.ZERO, BigDecimal::add);

                    obj.set("totalAmount", totalAmount)
                            .set("paidAmount", paidAmount)
                            .set("recAmount", recAmount)
                            .set("outAmount", outAmount)
                            .set("invoiceAmount", invoiceAmount);
                });
        return obj;
    }

    @Override
    public Object analyze(ContractDTO dto) {
        List<Contract> contractList = this.lambdaQuery(converter.convert(dto, Contract.class))
                .ge(Contract::getReceivableAmount, 0)
                .between(ArrayUtil.isAllNotNull(dto.getSignDateStart(), dto.getSignDateEnd()), Contract::getSignDate, dto.getSignDateStart(), dto.getSignDateEnd())
                .list();
        if (CollUtil.isEmpty(contractList)) {
            return ListUtil.empty();
        }

        // todo 优化查询,当下是查询所有
        Map<Long, List<ContractPayment>> id2PaymentMap = Opt.ofEmptyAble(contractPaymentService.lambdaQuery().gt(ContractPayment::getPaymentRatio, 0).list())
                .map(list -> list.stream().collect(Collectors.groupingBy(ContractPayment::getContractId)))
                .orElse(MapUtil.empty());
        if (MapUtil.isEmpty(id2PaymentMap)) {
            return ListUtil.empty();
        }
        List<FinancialAgeVO> financialAgeVOList = new ArrayList<>();

        contractList.forEach(c -> {
            List<ContractPayment> contractPaymentList = id2PaymentMap.get(c.getId());
            if (CollUtil.isEmpty(contractPaymentList)) {
                return;
            }
            BigDecimal totalAmount = c.getTotalAmount();
            if (NumberUtil.equals(totalAmount, BigDecimal.ZERO)) {
                return;
            }
            BigDecimal paidAmount = c.getPaidAmount();
            Map<Integer, Date> type2DateMap = contractPaymentList.stream().collect(HashMap::new, (m, v) -> m.put(v.getPaymentType(), v.getPaymentDate()), HashMap::putAll);

            Map<Integer, BigDecimal> type2AmountMap = contractPaymentList.stream().collect(Collectors.toMap(ContractPayment::getPaymentType, v -> NumberUtil.mul(v.getPaymentRatio(), totalAmount)));
            TreeMap<Integer, BigDecimal> sortMap = MapUtil.sort(type2AmountMap, Comparator.comparingInt(Integer::intValue));

            for (Map.Entry<Integer, BigDecimal> entry : sortMap.entrySet()) {
                Integer type = entry.getKey();
                BigDecimal curAmount = entry.getValue();
                if (NumberUtil.isGreater(paidAmount, BigDecimal.ZERO)) {
                    paidAmount = NumberUtil.sub(paidAmount, curAmount);
                    if (NumberUtil.equals(paidAmount, BigDecimal.ZERO)) {
                        continue;
                    }
                }
                if (NumberUtil.isLessOrEqual(paidAmount, BigDecimal.ZERO)) {
                    FinancialAgeVO financialAgeVO = new FinancialAgeVO();
                    financialAgeVO.setContractNo(c.getContractNo());
                    financialAgeVO.setContractName(c.getName());
                    financialAgeVO.setStatus(type);
                    financialAgeVO.setAmount(NumberUtil.equals(paidAmount, BigDecimal.ZERO) ? curAmount : BigDecimal.valueOf(Math.abs(paidAmount.doubleValue())));
                    Date payableDate = type2DateMap.get(type);
                    Opt.ofNullable(type2DateMap.get(type)).ifPresent(
                            date -> {
                                financialAgeVO.setPayableDate(date);
                                long age = DateUtil.between(new Date(), payableDate, DateUnit.DAY, false);
                                financialAgeVO.setAge(((int) age));
                            }
                    );
                    financialAgeVOList.add(financialAgeVO);
                    paidAmount = BigDecimal.ZERO;
                }
            }
        });
        // 手动分页
        int[] transToStartEnd = PageUtil.transToStartEnd(dto.getPageNum() - 1, dto.getPageSize());
        return Page.<FinancialAgeVO>of(dto.getPageNum(), dto.getPageSize(), financialAgeVOList.size()).setRecords(CollUtil.sub(financialAgeVOList, transToStartEnd[0], transToStartEnd[1]));
    }
}