From 25a40b9c4e6a1b650610b8b13088d486699532ce Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期四, 05 十一月 2026 20:23:21 +0800
Subject: [PATCH] 采购申请批量提交、订单导入重复校验;修复考勤异常申请字段不匹配
---
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseOrderServiceImpl.java | 225 +++++++++++++++++++++++++++++++++++++++++++++++++------
1 files changed, 199 insertions(+), 26 deletions(-)
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseOrderServiceImpl.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseOrderServiceImpl.java
index 24f455b..c2ab475 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseOrderServiceImpl.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseOrderServiceImpl.java
@@ -16,12 +16,14 @@
import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService;
import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.order.ErpPurchaseOrderPageReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.order.ErpPurchaseOrderSaveReqVO;
+import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.order.ErpPurchaseOrderImportDuplicateVO;
import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.order.ErpPurchaseOrderImportExcelVO;
import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.order.ErpPurchaseOrderImportRespVO;
import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseOrderDO;
import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseOrderItemDO;
import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseRequestDO;
import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseRequestItemDO;
+import cn.iocoder.yudao.module.erp.dal.mysql.purchase.ErpPurchaseInvoiceOrderMapper;
import cn.iocoder.yudao.module.erp.dal.mysql.purchase.ErpPurchaseOrderItemMapper;
import cn.iocoder.yudao.module.erp.dal.mysql.purchase.ErpPurchaseOrderMapper;
import cn.iocoder.yudao.module.erp.dal.mysql.purchase.ErpPurchaseRequestItemMapper;
@@ -49,6 +51,7 @@
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
+import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
@@ -78,6 +81,8 @@
private ErpPurchaseOrderMapper purchaseOrderMapper;
@Resource
private ErpPurchaseOrderItemMapper purchaseOrderItemMapper;
+ @Resource
+ private ErpPurchaseInvoiceOrderMapper invoiceOrderMapper;
@Resource
private ErpNoRedisDAO noRedisDAO;
@@ -180,11 +185,12 @@
@Override
@Transactional(rollbackFor = Exception.class)
public ErpPurchaseOrderImportRespVO importPurchaseOrderList(List<ErpPurchaseOrderImportExcelVO> importList,
- Boolean directApprove) {
+ Boolean directApprove, Boolean overwrite) {
ErpPurchaseOrderImportRespVO respVO = ErpPurchaseOrderImportRespVO.builder()
.createList(new ArrayList<>())
.updateList(new ArrayList<>())
.failureList(new LinkedHashMap<>())
+ .duplicateList(new ArrayList<>())
.build();
if (CollUtil.isEmpty(importList)) {
return respVO;
@@ -204,7 +210,27 @@
});
}
- // 2. 鎸夆�滈噰璐崟缂栧彿鈥濆垎缁勶細鍚岀紪鍙峰琛� = 涓�寮犺鍗曠殑澶氭潯鏄庣粏锛涚紪鍙蜂负绌哄垯姣忚鐙珛鎴愬崟
+ // 2. 棰勭儹绯荤粺鍐呮湁鏁堥噰璐鍗曪細鎸夊崟鍙枫�佹寜鈥滀緵搴斿晢 + 鏄庣粏鈥濇寚绾瑰缓绱㈠紩锛岀敤浜庤瘑鍒噸澶嶅鍏�
+ List<ErpPurchaseOrderDO> existingOrders = purchaseOrderMapper.selectList();
+ Map<Long, List<ErpPurchaseOrderItemDO>> existingItemsMap = new HashMap<>();
+ if (CollUtil.isNotEmpty(existingOrders)) {
+ purchaseOrderItemMapper.selectListByOrderIds(
+ existingOrders.stream().map(ErpPurchaseOrderDO::getId).toList())
+ .forEach(item -> existingItemsMap.computeIfAbsent(item.getOrderId(), k -> new ArrayList<>()).add(item));
+ }
+ Map<String, ErpPurchaseOrderDO> existingNoMap = new HashMap<>();
+ Map<String, List<ErpPurchaseOrderDO>> existingFingerprintMap = new HashMap<>();
+ for (ErpPurchaseOrderDO order : existingOrders) {
+ if (StrUtil.isNotBlank(order.getNo())) {
+ existingNoMap.put(order.getNo(), order);
+ }
+ String fingerprint = buildOrderFingerprint(order.getSupplierId(), existingItemsMap.get(order.getId()));
+ if (fingerprint != null) {
+ existingFingerprintMap.computeIfAbsent(fingerprint, k -> new ArrayList<>()).add(order);
+ }
+ }
+
+ // 3. 鎸夆�滈噰璐崟缂栧彿鈥濆垎缁勶細鍚岀紪鍙峰琛� = 涓�寮犺鍗曠殑澶氭潯鏄庣粏锛涚紪鍙蜂负绌哄垯姣忚鐙珛鎴愬崟
Map<String, List<ErpPurchaseOrderImportExcelVO>> orderGroups = new LinkedHashMap<>();
for (int i = 0; i < importList.size(); i++) {
ErpPurchaseOrderImportExcelVO row = importList.get(i);
@@ -216,14 +242,50 @@
orderGroups.computeIfAbsent(key, k -> new ArrayList<>()).add(row);
}
- // 3. 閫愪釜璁㈠崟瀵煎叆锛堝崟寮犺鍗曞け璐ヤ笉褰卞搷鍏跺畠锛�
+ // 4. 閫愪釜璁㈠崟瀵煎叆锛堝崟寮犺鍗曞け璐ヤ笉褰卞搷鍏跺畠锛�
for (Map.Entry<String, List<ErpPurchaseOrderImportExcelVO>> entry : orderGroups.entrySet()) {
List<ErpPurchaseOrderImportExcelVO> rows = entry.getValue();
String label = StrUtil.isNotBlank(rows.get(0).getNo()) ? rows.get(0).getNo().trim()
: "绗� " + entry.getKey().substring("__row_".length()) + " 琛�";
try {
- ErpPurchaseOrderDO purchaseOrder = createPurchaseOrderByImport(rows, supplierIdMap, directApprove);
- respVO.getCreateList().add(purchaseOrder.getNo());
+ ImportOrderData importData = prepareImportOrder(rows, supplierIdMap);
+
+ // 4.1 鍏堟寜鍗曞彿鎵撅紱鍙湁鈥滃崟鍙蜂负绌恒�佺敱绯荤粺鐢熸垚缂栧彿鈥濈殑琛屾墠鎸夆�滀緵搴斿晢 + 瀹屾暣鏄庣粏鈥濇寚绾规壘銆�
+ // 鏂囦欢閲屽啓浜嗘槑纭崟鍙风殑锛岃涓虹敤鎴锋槑纭鏂板璇ュ崟鍙凤紝鎸囩汗鐩稿悓涔熶笉鎷︽埅锛岄伩鍏嶆甯哥殑閲嶅閲囪喘琚鍒ゃ��
+ ErpPurchaseOrderDO existing = importData.order.getNo() == null ? null
+ : existingNoMap.get(importData.order.getNo());
+ String duplicateReason = existing != null ? "閲囪喘鍗曠紪鍙峰凡瀛樺湪" : null;
+ boolean matchedByFingerprint = false;
+ List<ErpPurchaseOrderDO> fingerprintCandidates = null;
+ if (existing == null && importData.generatedNo && importData.fingerprint != null) {
+ fingerprintCandidates = existingFingerprintMap.get(importData.fingerprint);
+ if (CollUtil.isNotEmpty(fingerprintCandidates)) {
+ existing = fingerprintCandidates.get(0);
+ matchedByFingerprint = true;
+ duplicateReason = "渚涘簲鍟嗐�佹槑缁嗐�佹暟閲忋�佸崟浠枫�侀噾棰濅笌宸叉湁璁㈠崟涓�鑷�";
+ }
+ }
+
+ // 4.2 鍙戠幇閲嶅锛氶粯璁や笉瀵煎叆锛岃繑鍥炵粰鍓嶇寮规鎻愮ず锛涘嬀閫夎鐩栧悗鎵嶆洿鏂板凡鏈夎鍗�
+ if (existing != null) {
+ if (!Boolean.TRUE.equals(overwrite)) {
+ respVO.getDuplicateList().add(buildImportDuplicate(label, existing, duplicateReason));
+ continue;
+ }
+ if (matchedByFingerprint && fingerprintCandidates.size() > 1) {
+ String nos = fingerprintCandidates.stream().map(ErpPurchaseOrderDO::getNo)
+ .collect(Collectors.joining("銆�"));
+ throw new IllegalArgumentException("鎵惧埌澶氬紶鐩稿悓璁㈠崟锛�" + nos
+ + "锛夛紝鏃犳硶鑷姩瑕嗙洊锛岃鍏堜繚鐣欎竴寮犲悗鍐嶅鍏�");
+ }
+ updatePurchaseOrderByImport(existing, importData, directApprove);
+ respVO.getUpdateList().add(existing.getNo());
+ continue;
+ }
+
+ // 4.3 娌℃湁閲嶅锛屾甯告柊澧�
+ createPurchaseOrderByImport(importData, directApprove);
+ respVO.getCreateList().add(importData.order.getNo());
} catch (Exception ex) {
respVO.getFailureList().put(label, resolveImportErrorMessage(ex));
}
@@ -232,13 +294,10 @@
}
/**
- * 渚濇嵁瀵煎叆鏁版嵁鍒涘缓涓�寮犻噰璐鍗曪紙鍚槑缁嗭級銆�
- *
- * 娉ㄦ剰锛氭澶勪笉璋冪敤 submitPurchaseOrder锛屽洜姝や笉浼氬垱寤� BPM 宸ヤ綔娴侊紱璁㈠崟鐘舵�佺敱 directApprove 鐩存帴鍐冲畾锛�
- * 鍕鹃�夆�滄槸鍚﹀鏍糕�� => 宸插鏍�(20)锛屾湭鍕鹃�� => 鏈鏍�(10)銆�
+ * 鏍¢獙骞剁粍瑁呬竴寮犲鍏ヨ鍗曪紙涓嶈惤搴擄級銆�
*/
- private ErpPurchaseOrderDO createPurchaseOrderByImport(List<ErpPurchaseOrderImportExcelVO> rows,
- Map<String, Long> supplierIdMap, Boolean directApprove) {
+ private ImportOrderData prepareImportOrder(List<ErpPurchaseOrderImportExcelVO> rows,
+ Map<String, Long> supplierIdMap) {
// 1. 渚涘簲鍟嗭紙鍙栬缁勯琛岋級
ErpPurchaseOrderImportExcelVO head = rows.get(0);
if (StrUtil.isBlank(head.getSupplierName())) {
@@ -278,31 +337,145 @@
items.add(itemVO);
}
- // 3. 鍗曞彿锛氬~浜嗗氨鐢紙鏍¢獙鍞竴锛夛紝娌″~鑷姩鐢熸垚
- String no = StrUtil.isNotBlank(head.getNo()) ? head.getNo().trim()
- : noRedisDAO.generate(ErpNoRedisDAO.PURCHASE_ORDER_NO_PREFIX);
- if (purchaseOrderMapper.selectByNo(no) != null) {
- throw new IllegalArgumentException("閲囪喘鍗曠紪鍙峰凡瀛樺湪锛�" + no);
- }
+ // 3. 鍗曞彿锛氬~浜嗗氨鐢紱鐣欑┖鏃跺厛涓嶇敓鎴愶紝绛夌‘璁よ鏂板鏃跺啀鐢熸垚锛岄伩鍏嶉噸澶嶈琚烦杩囦篃娑堣�楁祦姘村彿
+ boolean generatedNo = StrUtil.isBlank(head.getNo());
- // 4. 缁勮涓昏〃锛氬嬀閫夆�滄槸鍚﹀鏍糕�� => 宸插鏍�(20)锛屽惁鍒欐湭瀹℃牳(10)
+ // 4. 缁勮涓昏〃锛堢姸鎬佺敱璋冪敤鏂瑰喅瀹氾級
ErpPurchaseOrderDO purchaseOrder = new ErpPurchaseOrderDO();
- purchaseOrder.setNo(no);
+ purchaseOrder.setNo(generatedNo ? null : head.getNo().trim());
purchaseOrder.setSupplierId(supplierId);
purchaseOrder.setOrderTime(parseImportOrderTime(head.getOrderTime()));
purchaseOrder.setRemark(head.getRemark());
- purchaseOrder.setStatus(Boolean.TRUE.equals(directApprove)
- ? ErpAuditStatus.APPROVE.getStatus() : ErpAuditStatus.PROCESS.getStatus());
purchaseOrder.setDepositPrice(BigDecimal.ZERO); // deposit_price 涓� NOT NULL 涓旀棤榛樿鍊硷紝蹇呴』鏄惧紡璧嬪��
- purchaseOrder.setInCount(BigDecimal.ZERO);
- purchaseOrder.setInStatus(ErpPurchaseOrderInStatusEnum.NOT_IN.getStatus());
List<ErpPurchaseOrderItemDO> itemDOs = validatePurchaseOrderItems(items);
calculateTotalPrice(purchaseOrder, itemDOs);
+ return new ImportOrderData(purchaseOrder, itemDOs,
+ buildOrderFingerprint(supplierId, itemDOs), generatedNo,
+ StrUtil.isNotBlank(head.getOrderTime()));
+ }
+
+ /**
+ * 鏂板瀵煎叆璁㈠崟锛堝惈鏄庣粏锛夈��
+ */
+ private void createPurchaseOrderByImport(ImportOrderData importData, Boolean directApprove) {
+ ErpPurchaseOrderDO purchaseOrder = importData.order;
+ if (StrUtil.isBlank(purchaseOrder.getNo())) {
+ purchaseOrder.setNo(noRedisDAO.generate(ErpNoRedisDAO.PURCHASE_ORDER_NO_PREFIX));
+ }
+ purchaseOrder.setStatus(Boolean.TRUE.equals(directApprove)
+ ? ErpAuditStatus.APPROVE.getStatus() : ErpAuditStatus.PROCESS.getStatus());
+ purchaseOrder.setInCount(BigDecimal.ZERO);
+ purchaseOrder.setInStatus(ErpPurchaseOrderInStatusEnum.NOT_IN.getStatus());
purchaseOrderMapper.insert(purchaseOrder);
- itemDOs.forEach(item -> item.setOrderId(purchaseOrder.getId()));
- purchaseOrderItemMapper.insertBatch(itemDOs);
- return purchaseOrder;
+ importData.items.forEach(item -> item.setOrderId(purchaseOrder.getId()));
+ purchaseOrderItemMapper.insertBatch(importData.items);
+ }
+
+ /**
+ * 瑕嗙洊宸叉湁瀵煎叆璁㈠崟锛堝惈鏄庣粏锛夈��
+ */
+ private void updatePurchaseOrderByImport(ErpPurchaseOrderDO existing, ImportOrderData importData,
+ Boolean directApprove) {
+ // 1. 鏈変笅娓稿崟鎹殑璁㈠崟涓嶅厑璁歌鐩栵紝閬垮厤鎶婂凡鍏ュ簱/宸插紑绁ㄧ殑鏁版嵁鏀规帀
+ if (existing.getInCount() != null && existing.getInCount().compareTo(BigDecimal.ZERO) > 0) {
+ throw new IllegalArgumentException("閲囪喘璁㈠崟[" + existing.getNo() + "]宸插叆搴擄紙宸插叆搴撴暟閲� "
+ + normalizeNumber(existing.getInCount()) + "锛夛紝涓嶈兘瑕嗙洊锛涜鍏堝垹闄ゅ搴斿叆搴撹褰曞悗鍐嶅鍏�");
+ }
+ if (existing.getReturnCount() != null && existing.getReturnCount().compareTo(BigDecimal.ZERO) > 0) {
+ throw new IllegalArgumentException("閲囪喘璁㈠崟[" + existing.getNo() + "]宸查��璐э紙宸查��璐ф暟閲� "
+ + normalizeNumber(existing.getReturnCount()) + "锛夛紝涓嶈兘瑕嗙洊锛涜鍏堝垹闄ゅ搴旈��璐ц褰曞悗鍐嶅鍏�");
+ }
+ if (CollUtil.isNotEmpty(invoiceOrderMapper.selectListByPurchaseOrderId(existing.getId()))) {
+ throw new IllegalArgumentException("閲囪喘璁㈠崟[" + existing.getNo()
+ + "]宸插叧鑱斿彂绁紝涓嶈兘瑕嗙洊锛涜鍏堣В闄ゅ彂绁ㄥ叧鑱斿悗鍐嶅鍏�");
+ }
+ if (StrUtil.isNotBlank(existing.getProcessInstanceId())) {
+ throw new IllegalArgumentException("閲囪喘璁㈠崟[" + existing.getNo()
+ + "]宸叉彁浜ゅ鎵规祦绋嬶紝涓嶈兘瑕嗙洊锛涜鍏堝湪鍗忓悓鍔炲叕鎾ゅ洖鎴栦綔搴熻瀹℃壒鍚庡啀瀵煎叆");
+ }
+ List<ErpPurchaseOrderItemDO> oldItems = purchaseOrderItemMapper.selectListByOrderId(existing.getId());
+ boolean linkedRequest = oldItems.stream().anyMatch(item -> item.getRequestItemId() != null);
+ if (linkedRequest) {
+ throw new IllegalArgumentException("閲囪喘璁㈠崟[" + existing.getNo()
+ + "]宸插叧鑱旈噰璐敵璇凤紝涓嶈兘瑕嗙洊锛涜鍏堝湪閲囪喘鐢宠涓挙閿�鎴栧垹闄ゅ搴旂敵璇峰悗鍐嶅鍏�");
+ }
+
+ // 2. 鏇存柊涓昏〃锛堜繚鐣欏師鍗曞彿銆佸師鍏ュ簱鐘舵�侊級
+ ErpPurchaseOrderDO updateObj = importData.order;
+ updateObj.setId(existing.getId());
+ updateObj.setNo(existing.getNo());
+ // 鏂囦欢娌″~閲囪喘鏃堕棿鏃朵繚鐣欏師璁㈠崟鏃堕棿锛岄伩鍏嶈鐩栨椂鎶婃棩鏈熸敼鎴愬綋鍓嶆椂闂�
+ if (!importData.orderTimeProvided) {
+ updateObj.setOrderTime(existing.getOrderTime());
+ }
+ updateObj.setStatus(Boolean.TRUE.equals(directApprove)
+ ? ErpAuditStatus.APPROVE.getStatus() : ErpAuditStatus.PROCESS.getStatus());
+ updateObj.setInCount(existing.getInCount() == null ? BigDecimal.ZERO : existing.getInCount());
+ updateObj.setInStatus(existing.getInStatus());
+ purchaseOrderMapper.updateById(updateObj);
+
+ // 3. 鏄庣粏鏁寸粍鏇挎崲
+ purchaseOrderItemMapper.deleteByOrderId(existing.getId());
+ importData.items.forEach(item -> item.setOrderId(existing.getId()));
+ purchaseOrderItemMapper.insertBatch(importData.items);
+ }
+
+ private ErpPurchaseOrderImportDuplicateVO buildImportDuplicate(String label, ErpPurchaseOrderDO existing,
+ String reason) {
+ return ErpPurchaseOrderImportDuplicateVO.builder()
+ .key(label)
+ .existingNo(existing.getNo())
+ .existingId(existing.getId())
+ .reason(reason)
+ .build();
+ }
+
+ /**
+ * 鐢熸垚鈥滀緵搴斿晢 + 瀹屾暣鏄庣粏鈥濈殑璁㈠崟鎸囩汗锛岀敤浜庤瘑鍒噸澶嶅鍏ャ��
+ * 鏄庣粏鎸夌墿鏂欍�佹暟閲忋�佸崟浠枫�佺◣鐜囨帓搴忓悗姣旇緝锛屼笉鍙楄椤哄簭褰卞搷銆�
+ */
+ private String buildOrderFingerprint(Long supplierId, List<ErpPurchaseOrderItemDO> items) {
+ if (supplierId == null || CollUtil.isEmpty(items)) {
+ return null;
+ }
+ List<String> parts = items.stream()
+ .map(item -> String.join(":",
+ String.valueOf(item.getProductId()),
+ normalizeNumber(item.getCount()),
+ normalizeNumber(item.getProductPrice()),
+ normalizeNumber(item.getTaxPercent())))
+ .sorted()
+ .toList();
+ return supplierId + "|" + String.join(",", parts);
+ }
+
+ private String normalizeNumber(BigDecimal value) {
+ return value == null ? "" : value.stripTrailingZeros().toPlainString();
+ }
+
+ private static class ImportOrderData {
+
+ private final ErpPurchaseOrderDO order;
+ private final List<ErpPurchaseOrderItemDO> items;
+ private final String fingerprint;
+ /**
+ * 閲囪喘鍗曠紪鍙锋槸鍚︾敱绯荤粺鑷姩鐢熸垚锛堟枃浠堕噷鐣欑┖锛�
+ */
+ private final boolean generatedNo;
+ /**
+ * 鏂囦欢閲屾槸鍚﹀~鍐欎簡閲囪喘鏃堕棿
+ */
+ private final boolean orderTimeProvided;
+
+ private ImportOrderData(ErpPurchaseOrderDO order, List<ErpPurchaseOrderItemDO> items, String fingerprint,
+ boolean generatedNo, boolean orderTimeProvided) {
+ this.order = order;
+ this.items = items;
+ this.fingerprint = fingerprint;
+ this.generatedNo = generatedNo;
+ this.orderTimeProvided = orderTimeProvided;
+ }
}
/**
--
Gitblit v1.9.3