gaoluyang
6 天以前 d39e4f7d839167c16425086dfa2f8747bccfdff9
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/supplier/SrmSupplierServiceImpl.java
@@ -1,32 +1,44 @@
package cn.iocoder.yudao.module.srm.service.supplier;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.srm.controller.admin.supplier.vo.SrmSupplierPageReqVO;
import cn.iocoder.yudao.module.srm.controller.admin.supplier.vo.SrmSupplierSaveReqVO;
import cn.iocoder.yudao.module.srm.dal.dataobject.*;
import cn.iocoder.yudao.module.srm.dal.mysql.SrmBidAwardMapper;
import cn.iocoder.yudao.module.srm.dal.mysql.SrmBidEvaluationMapper;
import cn.iocoder.yudao.module.srm.dal.mysql.SrmSupplierApplyMapper;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.framework.operatelog.core.util.OperateLogUtils;
import cn.iocoder.yudao.module.srm.controller.admin.supplier.vo.*;
import cn.iocoder.yudao.module.srm.dal.dataobject.SrmSupplierBankDO;
import cn.iocoder.yudao.module.srm.dal.dataobject.SrmSupplierContactDO;
import cn.iocoder.yudao.module.srm.dal.dataobject.SrmSupplierDO;
import cn.iocoder.yudao.module.erp.api.purchase.ErpSupplierReferenceApi;
import cn.iocoder.yudao.module.srm.dal.mysql.SrmBidBondMapper;
import cn.iocoder.yudao.module.srm.dal.mysql.SrmInquiryOrderMapper;
import cn.iocoder.yudao.module.srm.dal.mysql.SrmInquiryQuoteMapper;
import cn.iocoder.yudao.module.srm.dal.mysql.SrmInquirySupplierMapper;
import cn.iocoder.yudao.module.srm.dal.mysql.SrmSupplierBankMapper;
import cn.iocoder.yudao.module.srm.dal.mysql.SrmSupplierCategoryMapper;
import cn.iocoder.yudao.module.srm.dal.mysql.SrmSupplierContactMapper;
import cn.iocoder.yudao.module.srm.dal.mysql.SrmSupplierCertificateMapper;
import cn.iocoder.yudao.module.srm.dal.mysql.SrmSupplierMapper;
import cn.iocoder.yudao.module.srm.dal.mysql.SrmTenderBidMapper;
import cn.iocoder.yudao.module.srm.enums.ErrorCodeConstants;
import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
import cn.iocoder.yudao.module.srm.enums.SupplierTypeEnum;
import com.mzt.logapi.starter.annotation.LogRecord;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.hutool.core.collection.CollUtil;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Objects;
import static cn.iocoder.yudao.module.srm.enums.LogRecordConstants.*;
@Service
@Validated
@@ -39,34 +51,43 @@
    private SrmSupplierContactMapper supplierContactMapper;
    @Resource
    private SrmSupplierBankMapper supplierBankMapper;
    @Resource
    private SrmSupplierCategoryMapper supplierCategoryMapper;
    @Resource
    private SrmSupplierApplyMapper supplierApplyMapper;
    private SrmInquirySupplierMapper inquirySupplierMapper;
    @Resource
    private SrmSupplierCertificateMapper supplierCertificateMapper;
    private SrmInquiryQuoteMapper inquiryQuoteMapper;
    @Resource
    private SrmTenderBidMapper tenderBidMapper;
    private SrmInquiryOrderMapper inquiryOrderMapper;
    @Resource
    private SrmBidEvaluationMapper bidEvaluationMapper;
    private SrmBidBondMapper bidBondMapper;
    @Resource
    private SrmBidAwardMapper bidAwardMapper;
    private ErpSupplierReferenceApi erpSupplierReferenceApi;
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long createSupplier(SrmSupplierSaveReqVO createReqVO) {
        // 校验供应商编码唯一性
        validateCodeUnique(createReqVO.getCode(), null);
        // 插入
        SrmSupplierDO supplier = BeanUtils.toBean(createReqVO, SrmSupplierDO.class);
        fillDefaultContactAndBankFromFlatFields(createReqVO);
        supplierMapper.insert(supplier);
        saveContacts(supplier.getId(), createReqVO.getContacts());
        saveBanks(supplier.getId(), createReqVO.getBanks());
        syncSupplierDefaultFields(supplier.getId(), createReqVO.getContacts(), createReqVO.getBanks());
        return supplier.getId();
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateSupplier(SrmSupplierSaveReqVO updateReqVO) {
        // 校验存在性
        validateSupplierExists(updateReqVO.getId());
@@ -75,17 +96,47 @@
        // 更新
        SrmSupplierDO updateObj = BeanUtils.toBean(updateReqVO, SrmSupplierDO.class);
        supplierMapper.updateById(updateObj);
        // 列表传 null 表示不修改;传空列表表示清空
        if (updateReqVO.getContacts() != null) {
            saveContacts(updateReqVO.getId(), updateReqVO.getContacts());
        }
        if (updateReqVO.getBanks() != null) {
            saveBanks(updateReqVO.getId(), updateReqVO.getBanks());
        }
        syncSupplierDefaultFields(updateReqVO.getId(), updateReqVO.getContacts(), updateReqVO.getBanks());
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteSupplier(Long id) {
        // 校验存在性
        validateSupplierExists(id);
        // 关联数据存在时,不允许删除
        validateNoReference(id);
        SrmSupplierDO supplier = validateSupplierExists(id);
        // 校验业务引用:询价、保证金、ERP 采购/付款等单据
        List<String> references = new ArrayList<>();
        if (inquirySupplierMapper.selectCountBySupplierId(id) > 0) {
            references.add("SRM询价单");
        }
        if (inquiryQuoteMapper.selectCountBySupplierId(id) > 0) {
            references.add("SRM询价报价");
        }
        if (inquiryOrderMapper.selectCountBySupplierId(id) > 0) {
            references.add("SRM询价转采购订单记录");
        }
        if (bidBondMapper.selectCountBySupplierId(id) > 0) {
            references.add("SRM投标保证金");
        }
        List<String> erpSources = erpSupplierReferenceApi.getSupplierReferenceSources(id).getCheckedData();
        if (CollUtil.isNotEmpty(erpSources)) {
            references.addAll(erpSources);
        }
        if (!references.isEmpty()) {
            throw new ServiceException(ErrorCodeConstants.SUPPLIER_EXISTS_REFERENCE.getCode(),
                    "供应商【" + supplier.getName() + "】已被业务引用(" + String.join("、", references)
                            + "),无法删除。请先处理相关单据,或将供应商状态改为停用。");
        }
        // 可直接清理的关联数据
        supplierContactMapper.deleteBySupplierId(id);
        supplierBankMapper.deleteBySupplierId(id);
        supplierCategoryMapper.deleteBySupplierId(id);
        // 逻辑删除
        supplierMapper.deleteById(id);
@@ -97,13 +148,81 @@
    }
    @Override
    public List<SrmSupplierContactDO> getSupplierContactList(Long supplierId) {
        return supplierContactMapper.selectListBySupplierId(supplierId);
    }
    @Override
    public List<SrmSupplierBankDO> getSupplierBankList(Long supplierId) {
        return supplierBankMapper.selectListBySupplierId(supplierId);
    }
    @Override
    public PageResult<SrmSupplierDO> getSupplierPage(SrmSupplierPageReqVO pageReqVO) {
        return supplierMapper.selectPage(pageReqVO);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    @LogRecord(type = LOG_SUPPLIER_TYPE, subType = LOG_SUPPLIER_IMPORT_SUB_TYPE, bizNo = "0",
            success = LOG_SUPPLIER_IMPORT_SUCCESS)
    public SrmSupplierImportRespVO importSupplierList(List<SrmSupplierImportExcelVO> importList, Boolean updateSupport) {
        if (CollUtil.isEmpty(importList)) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.SUPPLIER_IMPORT_LIST_IS_EMPTY);
        }
        SrmSupplierImportRespVO respVO = SrmSupplierImportRespVO.builder()
                .createList(new ArrayList<>())
                .updateList(new ArrayList<>())
                .failureList(new LinkedHashMap<>())
                .build();
        // 逐行导入:单行失败记录到 failureList,不影响其它行
        for (SrmSupplierImportExcelVO importVO : importList) {
            String code = StrUtil.trim(importVO.getCode());
            String name = StrUtil.trim(importVO.getName());
            if (StrUtil.isBlank(code) || StrUtil.isBlank(name)) {
                respVO.getFailureList().put(StrUtil.blankToDefault(name, code), "供应商编码和供应商名称不能为空");
                continue;
            }
            SrmSupplierSaveReqVO saveReqVO = toSaveReqVO(importVO);
            try {
                SrmSupplierDO existByCode = supplierMapper.selectByCode(code);
                if (existByCode != null) {
                    if (!Boolean.TRUE.equals(updateSupport)) {
                        respVO.getFailureList().put(name, "供应商编码已存在");
                        continue;
                    }
                    saveReqVO.setId(existByCode.getId());
                    updateSupplier(saveReqVO);
                    respVO.getUpdateList().add(name);
                } else {
                    createSupplier(saveReqVO);
                    respVO.getCreateList().add(name);
                }
            } catch (Exception ex) {
                // 单条失败,记录并继续
                String reason = ex instanceof ServiceException ? ((ServiceException) ex).getMessage() : ex.getMessage();
                respVO.getFailureList().put(name, reason == null ? "导入失败" : reason);
            }
        }
        // 记录导入结果汇总,供 @LogRecord 的 success 文案引用
        OperateLogUtils.putImportSummary(respVO.getCreateList(), respVO.getUpdateList(), respVO.getFailureList());
        return respVO;
    }
    @Override
    public List<SrmSupplierDO> getSupplierSimpleList() {
        return supplierMapper.selectApprovedList();
        // 准入功能已移除,下拉选择统一返回启用的供应商
        return supplierMapper.selectListByStatus(CommonStatusEnum.ENABLE.getStatus());
    }
    @Override
    public List<SrmSupplierDO> getSupplierAllList() {
        return supplierMapper.selectList(new LambdaQueryWrapperX<SrmSupplierDO>()
                // 过滤停用供应商,保留未设置状态与启用状态的档案供应商
                .and(w -> w.isNull(SrmSupplierDO::getStatus)
                        .or()
                        .ne(SrmSupplierDO::getStatus, CommonStatusEnum.DISABLE.getStatus()))
                .orderByAsc(SrmSupplierDO::getId));
    }
    @Override
@@ -114,29 +233,196 @@
        return supplierMapper.selectBatchIds(ids);
    }
    // ========== 联系人 / 银行账户 ==========
    /**
     * 导入场景没有联系人列表时,用主表扁平字段生成一条默认联系人
     */
    private void fillDefaultContactAndBankFromFlatFields(SrmSupplierSaveReqVO reqVO) {
        if (reqVO.getContacts() == null && StrUtil.isNotBlank(reqVO.getContactName())) {
            SrmSupplierContactSaveReqVO contact = new SrmSupplierContactSaveReqVO();
            contact.setName(reqVO.getContactName());
            contact.setMobile(reqVO.getContactMobile());
            contact.setEmail(reqVO.getContactEmail());
            contact.setIsDefault(Boolean.TRUE);
            reqVO.setContacts(Collections.singletonList(contact));
        }
        if (reqVO.getBanks() == null
                && (StrUtil.isNotBlank(reqVO.getBankName()) || StrUtil.isNotBlank(reqVO.getBankAccount()))) {
            SrmSupplierBankSaveReqVO bank = new SrmSupplierBankSaveReqVO();
            bank.setBankName(reqVO.getBankName());
            bank.setBankAccount(reqVO.getBankAccount());
            bank.setIsDefault(Boolean.TRUE);
            reqVO.setBanks(Collections.singletonList(bank));
        }
    }
    private void saveContacts(Long supplierId, List<SrmSupplierContactSaveReqVO> contacts) {
        supplierContactMapper.deleteBySupplierId(supplierId);
        if (CollUtil.isEmpty(contacts)) {
            return;
        }
        for (int i = 0; i < contacts.size(); i++) {
            SrmSupplierContactSaveReqVO vo = contacts.get(i);
            if (StrUtil.isBlank(vo.getName())) {
                throw new ServiceException(ErrorCodeConstants.SUPPLIER_CONTACT_NAME_REQUIRED.getCode(),
                        "第 " + (i + 1) + " 个联系人的姓名不能为空,请填写联系人姓名后重新保存");
            }
        }
        int defaultIndex = resolveDefaultIndex(contacts.size(),
                contacts.stream().map(SrmSupplierContactSaveReqVO::getIsDefault).toList());
        for (int i = 0; i < contacts.size(); i++) {
            SrmSupplierContactDO contact = BeanUtils.toBean(contacts.get(i), SrmSupplierContactDO.class);
            contact.setId(null);
            contact.setSupplierId(supplierId);
            contact.setSort(i);
            contact.setIsDefault(i == defaultIndex);
            supplierContactMapper.insert(contact);
        }
    }
    private void saveBanks(Long supplierId, List<SrmSupplierBankSaveReqVO> banks) {
        supplierBankMapper.deleteBySupplierId(supplierId);
        if (CollUtil.isEmpty(banks)) {
            return;
        }
        for (int i = 0; i < banks.size(); i++) {
            SrmSupplierBankSaveReqVO vo = banks.get(i);
            if (StrUtil.isBlank(vo.getBankName()) || StrUtil.isBlank(vo.getBankAccount())) {
                throw new ServiceException(ErrorCodeConstants.SUPPLIER_BANK_INFO_INCOMPLETE.getCode(),
                        "第 " + (i + 1) + " 个银行账户的开户行或银行账号为空,请补全开户行和银行账号后重新保存");
            }
        }
        int defaultIndex = resolveDefaultIndex(banks.size(),
                banks.stream().map(SrmSupplierBankSaveReqVO::getIsDefault).toList());
        for (int i = 0; i < banks.size(); i++) {
            SrmSupplierBankDO bank = BeanUtils.toBean(banks.get(i), SrmSupplierBankDO.class);
            bank.setId(null);
            bank.setSupplierId(supplierId);
            bank.setSort(i);
            bank.setIsDefault(i == defaultIndex);
            supplierBankMapper.insert(bank);
        }
    }
    /**
     * 解析默认项:优先取标记了默认的第一条,否则取第一条
     */
    private int resolveDefaultIndex(int size, List<Boolean> defaults) {
        for (int i = 0; i < size; i++) {
            if (Boolean.TRUE.equals(defaults.get(i))) {
                return i;
            }
        }
        return 0;
    }
    /**
     * 把默认联系人/默认银行回写主表扁平字段,兼容列表、搜索与导入导出
     */
    private void syncSupplierDefaultFields(Long supplierId, List<SrmSupplierContactSaveReqVO> contacts,
                                           List<SrmSupplierBankSaveReqVO> banks) {
        SrmSupplierDO updateObj = new SrmSupplierDO();
        updateObj.setId(supplierId);
        boolean changed = false;
        if (contacts != null) {
            SrmSupplierContactSaveReqVO defaultContact = pickDefault(contacts, SrmSupplierContactSaveReqVO::getIsDefault);
            updateObj.setContactName(defaultContact == null ? null : defaultContact.getName());
            updateObj.setContactMobile(defaultContact == null ? null : defaultContact.getMobile());
            updateObj.setContactEmail(defaultContact == null ? null : defaultContact.getEmail());
            changed = true;
        }
        if (banks != null) {
            SrmSupplierBankSaveReqVO defaultBank = pickDefault(banks, SrmSupplierBankSaveReqVO::getIsDefault);
            updateObj.setBankName(defaultBank == null ? null : defaultBank.getBankName());
            updateObj.setBankAccount(defaultBank == null ? null : defaultBank.getBankAccount());
            changed = true;
        }
        if (changed) {
            supplierMapper.updateById(updateObj);
        }
    }
    private <T> T pickDefault(List<T> list, java.util.function.Function<T, Boolean> isDefault) {
        if (CollUtil.isEmpty(list)) {
            return null;
        }
        return list.stream().filter(item -> Boolean.TRUE.equals(isDefault.apply(item)))
                .findFirst().orElse(list.get(0));
    }
    // ========== 导入辅助方法 ==========
    /**
     * 导入 VO -> 保存 VO,复用 create/update 逻辑;
     * 类型支持中文名称或编码(1~4),状态留空默认启用
     */
    private SrmSupplierSaveReqVO toSaveReqVO(SrmSupplierImportExcelVO importVO) {
        SrmSupplierSaveReqVO saveReqVO = new SrmSupplierSaveReqVO();
        saveReqVO.setCode(StrUtil.trim(importVO.getCode()));
        saveReqVO.setName(StrUtil.trim(importVO.getName()));
        saveReqVO.setShortName(StrUtil.trimToNull(importVO.getShortName()));
        saveReqVO.setType(parseTypeCode(importVO.getType()));
        saveReqVO.setCompanyNature(StrUtil.trimToNull(importVO.getCompanyNature()));
        saveReqVO.setRegisteredCapital(importVO.getRegisteredCapital());
        saveReqVO.setBusinessLicenseNo(StrUtil.trimToNull(importVO.getBusinessLicenseNo()));
        saveReqVO.setTaxNo(StrUtil.trimToNull(importVO.getTaxNo()));
        saveReqVO.setLegalPerson(StrUtil.trimToNull(importVO.getLegalPerson()));
        saveReqVO.setAddress(StrUtil.trimToNull(importVO.getAddress()));
        saveReqVO.setWebsite(StrUtil.trimToNull(importVO.getWebsite()));
        saveReqVO.setContactName(StrUtil.trimToNull(importVO.getContactName()));
        saveReqVO.setContactMobile(StrUtil.trimToNull(importVO.getContactMobile()));
        saveReqVO.setContactEmail(StrUtil.trimToNull(importVO.getContactEmail()));
        saveReqVO.setBankName(StrUtil.trimToNull(importVO.getBankName()));
        saveReqVO.setBankAccount(StrUtil.trimToNull(importVO.getBankAccount()));
        saveReqVO.setStatus(importVO.getStatus() != null ? importVO.getStatus() : CommonStatusEnum.ENABLE.getStatus());
        saveReqVO.setRemark(StrUtil.trimToNull(importVO.getRemark()));
        return saveReqVO;
    }
    /**
     * 供应商类型解析:中文名称或编码(1~4)均转换为编码字符串;
     * 无法识别时保留原值,避免导入被阻塞
     */
    private String parseTypeCode(String type) {
        String t = StrUtil.trimToNull(type);
        if (t == null) {
            return null;
        }
        for (SupplierTypeEnum e : SupplierTypeEnum.values()) {
            if (t.equalsIgnoreCase(e.getName()) || t.equals(String.valueOf(e.getCode()))) {
                return String.valueOf(e.getCode());
            }
        }
        // 兼容 Excel 数字单元格被读成 "1.0" 等带小数形式
        try {
            BigDecimal bd = new BigDecimal(t);
            if (bd.stripTrailingZeros().scale() <= 0) {
                int i = bd.intValueExact();
                if (i >= 1 && i <= 4) {
                    return String.valueOf(i);
                }
            }
        } catch (NumberFormatException ignored) {
            // 忽略:保持原值
        }
        return t;
    }
    // ========== 校验方法 ==========
    private void validateSupplierExists(Long id) {
        if (supplierMapper.selectById(id) == null) {
    private SrmSupplierDO validateSupplierExists(Long id) {
        SrmSupplierDO supplier = supplierMapper.selectById(id);
        if (supplier == null) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.SUPPLIER_NOT_EXISTS);
        }
        return supplier;
    }
    private void validateCodeUnique(String code, Long id) {
        SrmSupplierDO existing = supplierMapper.selectByCode(code);
        if (existing != null && !existing.getId().equals(id)) {
        if (existing != null && !Objects.equals(existing.getId(), id)) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.SUPPLIER_CODE_EXISTS);
        }
    }
    private void validateNoReference(Long id) {
        boolean hasReference = supplierApplyMapper.existsApprovedBySupplierId(id)
                || supplierCertificateMapper.selectCount(SrmSupplierCertificateDO::getSupplierId, id) > 0
                || tenderBidMapper.selectCount(SrmTenderBidDO::getSupplierId, id) > 0
                || bidEvaluationMapper.selectCount(SrmBidEvaluationDO::getSupplierId, id) > 0
                || bidAwardMapper.selectCount(SrmBidAwardDO::getSupplierId, id) > 0;
        if (hasReference) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.SUPPLIER_EXISTS_REFERENCE);
        }
    }