From 67d3ab41f4989faf4db5f7f95f56b8595dd8eb3b Mon Sep 17 00:00:00 2001
From: tuqin <tuqin@tuqin.cn>
Date: 星期五, 11 九月 2026 09:39:44 +0800
Subject: [PATCH] feat(crm/erp): 新增采购申请、合同管理 Excel 导入(含菜单权限 SQL)
---
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractServiceImpl.java | 187 ++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 187 insertions(+), 0 deletions(-)
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractServiceImpl.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractServiceImpl.java
index 88705a1..448dae2 100644
--- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractServiceImpl.java
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractServiceImpl.java
@@ -1,9 +1,11 @@
package cn.iocoder.yudao.module.crm.service.contract;
import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.StrUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ObjUtil;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.number.MoneyUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
@@ -15,13 +17,17 @@
import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService;
import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.contract.CrmContractPageReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.contract.CrmContractSaveReqVO;
+import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.contract.CrmContractImportExcelVO;
+import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.contract.CrmContractImportRespVO;
import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.contract.CrmContractTransferReqVO;
import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractConfigDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractProductDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.quotation.CrmSaleQuotationDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.quotation.CrmSaleQuotationProductDO;
+import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
import cn.iocoder.yudao.module.crm.dal.mysql.contract.CrmContractMapper;
+import cn.iocoder.yudao.module.crm.dal.mysql.customer.CrmCustomerMapper;
import cn.iocoder.yudao.module.crm.dal.mysql.contract.CrmContractProductMapper;
import cn.iocoder.yudao.module.crm.dal.redis.no.CrmNoRedisDAO;
import cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum;
@@ -59,9 +65,11 @@
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.time.format.DateTimeFormatter;
import org.flowable.engine.repository.ProcessDefinition;
@@ -69,6 +77,7 @@
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*;
import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.*;
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
import static cn.iocoder.yudao.module.crm.util.CrmAuditStatusUtils.convertBpmResultToAuditStatus;
/**
@@ -98,6 +107,8 @@
@Resource
private CrmNoRedisDAO noRedisDAO;
+ @Resource
+ private CrmCustomerMapper customerMapper;
@Resource
private CrmPermissionService crmPermissionService;
@@ -259,6 +270,182 @@
}));
}
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public CrmContractImportRespVO importContractList(List<CrmContractImportExcelVO> importList) {
+ CrmContractImportRespVO respVO = CrmContractImportRespVO.builder()
+ .createList(new ArrayList<>())
+ .updateList(new ArrayList<>())
+ .failureList(new LinkedHashMap<>())
+ .build();
+ if (CollUtil.isEmpty(importList)) {
+ return respVO;
+ }
+
+ // 1. 棰勭儹鈥滃鎴峰悕绉� -> 瀹㈡埛缂栧彿鈥濇槧灏�
+ Map<String, Long> customerIdMap = new HashMap<>();
+ List<CrmCustomerDO> customers = customerMapper.selectList();
+ if (CollUtil.isNotEmpty(customers)) {
+ customers.forEach(customer -> {
+ if (StrUtil.isNotBlank(customer.getName())) {
+ customerIdMap.putIfAbsent(customer.getName().trim(), customer.getId());
+ }
+ });
+ }
+
+ // 2. 鎸夆�滃悎鍚岀紪鍙封�濆垎缁�
+ Map<String, List<CrmContractImportExcelVO>> groups = new LinkedHashMap<>();
+ for (int i = 0; i < importList.size(); i++) {
+ CrmContractImportExcelVO row = importList.get(i);
+ if (row == null || (StrUtil.isBlank(row.getName()) && StrUtil.isBlank(row.getCustomerName())
+ && StrUtil.isBlank(row.getNo()))) {
+ continue; // 璺宠繃绌鸿
+ }
+ String key = StrUtil.isNotBlank(row.getNo()) ? row.getNo().trim() : "__row_" + (i + 2);
+ groups.computeIfAbsent(key, k -> new ArrayList<>()).add(row);
+ }
+
+ // 3. 閫愬紶鍚堝悓瀵煎叆
+ for (Map.Entry<String, List<CrmContractImportExcelVO>> entry : groups.entrySet()) {
+ List<CrmContractImportExcelVO> rows = entry.getValue();
+ String label = StrUtil.isNotBlank(rows.get(0).getNo()) ? rows.get(0).getNo().trim()
+ : "绗� " + entry.getKey().substring("__row_".length()) + " 琛�";
+ try {
+ CrmContractDO contract = createContractByImport(rows, customerIdMap);
+ respVO.getCreateList().add(contract.getNo());
+ } catch (Exception ex) {
+ respVO.getFailureList().put(label, resolveImportErrorMessage(ex));
+ }
+ }
+ return respVO;
+ }
+
+ /**
+ * 渚濇嵁瀵煎叆鏁版嵁鍒涘缓涓�寮犲悎鍚岋紙鍚槑缁嗭級銆傝礋璐d汉=褰撳墠鐧诲綍鐢ㄦ埛锛涚姸鎬�=鏈彁浜わ紱閲戦鎸夋槑缁嗕笌浼樻儬鐜囪嚜鍔ㄨ绠椼��
+ */
+ private CrmContractDO createContractByImport(List<CrmContractImportExcelVO> rows, Map<String, Long> customerIdMap) {
+ CrmContractImportExcelVO head = rows.get(0);
+ // 1. 涓昏〃蹇呭~
+ if (StrUtil.isBlank(head.getName())) {
+ throw new IllegalArgumentException("鍚堝悓鍚嶇О涓嶈兘涓虹┖");
+ }
+ if (StrUtil.isBlank(head.getCustomerName())) {
+ throw new IllegalArgumentException("瀹㈡埛鍚嶇О涓嶈兘涓虹┖");
+ }
+ Long customerId = customerIdMap.get(head.getCustomerName().trim());
+ if (customerId == null) {
+ throw new IllegalArgumentException("瀹㈡埛涓嶅瓨鍦細" + head.getCustomerName());
+ }
+ customerService.validateCustomer(customerId);
+
+ // 2. 鏄庣粏
+ List<CrmContractSaveReqVO.Product> products = new ArrayList<>();
+ for (CrmContractImportExcelVO row : rows) {
+ if (StrUtil.isBlank(row.getProductCode())) {
+ throw new IllegalArgumentException("浜у搧缂栫爜涓嶈兘涓虹┖");
+ }
+ CommonResult<MdmItemRespDTO> itemResult = mdmItemApi.getItemByCode(row.getProductCode().trim());
+ MdmItemRespDTO item = itemResult == null ? null : itemResult.getData();
+ if (item == null) {
+ throw new IllegalArgumentException("浜у搧涓嶅瓨鍦細" + row.getProductCode());
+ }
+ if (row.getCount() == null || row.getCount() <= 0) {
+ throw new IllegalArgumentException("浜у搧[" + row.getProductCode() + "]鐨勬暟閲忓繀椤讳负姝f暣鏁�");
+ }
+ BigDecimal contractPrice = row.getContractPrice() != null ? row.getContractPrice()
+ : (item.getSalesPrice() != null ? item.getSalesPrice() : BigDecimal.ZERO);
+ CrmContractSaveReqVO.Product product = new CrmContractSaveReqVO.Product();
+ product.setItemId(item.getId());
+ product.setItemPrice(item.getSalesPrice() != null ? item.getSalesPrice() : contractPrice);
+ product.setContractPrice(contractPrice);
+ product.setCount(row.getCount());
+ products.add(product);
+ }
+
+ // 3. 鍚堝悓缂栧彿
+ String no = StrUtil.isNotBlank(head.getNo()) ? head.getNo().trim()
+ : noRedisDAO.generate(CrmNoRedisDAO.CONTRACT_NO_PREFIX);
+ if (contractMapper.selectByNo(no) != null) {
+ throw new IllegalArgumentException("鍚堝悓缂栧彿宸插瓨鍦細" + no);
+ }
+
+ // 4. 缁勮涓昏〃锛堣礋璐d汉=鐧诲綍鐢ㄦ埛锛涚姸鎬�=鏈彁浜わ級
+ CrmContractDO contract = new CrmContractDO();
+ contract.setNo(no);
+ contract.setName(head.getName().trim());
+ contract.setCustomerId(customerId);
+ contract.setOwnerUserId(getLoginUserId());
+ contract.setOrderDate(parseImportDateTime(head.getOrderDate()));
+ contract.setStartTime(parseImportDate(head.getStartTime()));
+ contract.setEndTime(parseImportDate(head.getEndTime()));
+ contract.setDiscountPercent(head.getDiscountPercent() != null ? head.getDiscountPercent() : BigDecimal.ZERO);
+ contract.setDepositPrice(head.getDepositPrice() != null ? head.getDepositPrice() : BigDecimal.ZERO);
+ contract.setRemark(head.getRemark());
+ contract.setAuditStatus(CrmAuditStatusEnum.DRAFT.getStatus());
+
+ // 5. 鏄庣粏钀藉簱 + 鑷姩绠椾环
+ List<CrmContractProductDO> productDOs = validateContractProducts(products);
+ calculateTotalPrice(contract, productDOs);
+ contractMapper.insert(contract);
+ productDOs.forEach(product -> product.setContractId(contract.getId()));
+ contractProductMapper.insertBatch(productDOs);
+
+ // 6. 鍒涘缓鏁版嵁鏉冮檺锛堝惁鍒欏綋鍓嶇敤鎴风湅涓嶅埌璇ュ悎鍚岋級
+ crmPermissionService.createPermission(new CrmPermissionCreateReqBO().setUserId(contract.getOwnerUserId())
+ .setBizType(CrmBizTypeEnum.CRM_CONTRACT.getType()).setBizId(contract.getId())
+ .setLevel(CrmPermissionLevelEnum.OWNER.getLevel()));
+ return contract;
+ }
+
+ private LocalDateTime parseImportDateTime(String text) {
+ if (StrUtil.isBlank(text)) {
+ return LocalDateTime.now();
+ }
+ String value = text.trim();
+ for (String pattern : new String[]{"yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm", "yyyy/MM/dd HH:mm:ss"}) {
+ try {
+ return LocalDateTime.parse(value, DateTimeFormatter.ofPattern(pattern));
+ } catch (Exception ignored) {
+ // 缁х画灏濊瘯涓嬩竴涓牸寮�
+ }
+ }
+ for (String pattern : new String[]{"yyyy-MM-dd", "yyyy/M/d"}) {
+ try {
+ return java.time.LocalDate.parse(value, DateTimeFormatter.ofPattern(pattern)).atStartOfDay();
+ } catch (Exception ignored) {
+ // 缁х画灏濊瘯涓嬩竴涓牸寮�
+ }
+ }
+ throw new IllegalArgumentException("涓嬪崟鏃堕棿鏍煎紡涓嶆纭細" + text + "锛岀ず渚� 2026-01-01 10:00:00");
+ }
+
+ private LocalDateTime parseImportDate(String text) {
+ if (StrUtil.isBlank(text)) {
+ return null;
+ }
+ String value = text.trim();
+ for (String pattern : new String[]{"yyyy-MM-dd", "yyyy/M/d"}) {
+ try {
+ return java.time.LocalDate.parse(value, DateTimeFormatter.ofPattern(pattern)).atStartOfDay();
+ } catch (Exception ignored) {
+ // 缁х画灏濊瘯涓嬩竴涓牸寮�
+ }
+ }
+ try {
+ return LocalDateTime.parse(value, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
+ } catch (Exception ignored) {
+ // 蹇界暐
+ }
+ throw new IllegalArgumentException("鏃ユ湡鏍煎紡涓嶆纭細" + text + "锛岀ず渚� 2026-01-01");
+ }
+
+ private String resolveImportErrorMessage(Exception ex) {
+ if (ex instanceof cn.iocoder.yudao.framework.common.exception.ServiceException serviceException) {
+ return serviceException.getMessage();
+ }
+ return StrUtil.blankToDefault(ex.getMessage(), "瀵煎叆澶辫触");
+ }
+
private void calculateTotalPrice(CrmContractDO contract, List<CrmContractProductDO> contractProducts) {
contract.setTotalProductPrice(getSumValue(contractProducts, CrmContractProductDO::getTotalPrice, BigDecimal::add, BigDecimal.ZERO));
BigDecimal discountPrice = MoneyUtils.priceMultiplyPercent(contract.getTotalProductPrice(), contract.getDiscountPercent());
--
Gitblit v1.9.3