From 38d39b8c11096668d1a49eea84bbec298489d23e Mon Sep 17 00:00:00 2001
From: liu <2021943741@qq.com>
Date: 星期一, 07 九月 2026 16:44:27 +0800
Subject: [PATCH] Merge remote-tracking branch 'origin/dev_business' into dev_business
---
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/materialstock/MesWmMaterialStockServiceImpl.java | 629 +++++++++++++++++++++++++++++++++++++++++++++++++++++++-
1 files changed, 615 insertions(+), 14 deletions(-)
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/materialstock/MesWmMaterialStockServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/materialstock/MesWmMaterialStockServiceImpl.java
index 638bae4..cccd0c3 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/materialstock/MesWmMaterialStockServiceImpl.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/materialstock/MesWmMaterialStockServiceImpl.java
@@ -3,32 +3,59 @@
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ObjUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.common.exception.ErrorCode;
+import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
+import cn.iocoder.yudao.module.mes.controller.admin.wm.materialstock.vo.MesWmMaterialStockAdjustReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.materialstock.vo.MesWmMaterialStockFreezeReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.wm.materialstock.vo.MesWmMaterialStockImportExcelVO;
+import cn.iocoder.yudao.module.mes.controller.admin.wm.materialstock.vo.MesWmMaterialStockImportRespVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.materialstock.vo.MesWmMaterialStockListReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.wm.materialstock.vo.MesWmMaterialStockMoveReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.materialstock.vo.MesWmMaterialStockPageReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.mdm.MesMdmItemDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemTypeDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.materialstock.MesWmMaterialStockDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseAreaDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseLocationDO;
import cn.iocoder.yudao.module.mes.dal.mysql.wm.materialstock.MesWmMaterialStockMapper;
+import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants;
+import cn.iocoder.yudao.module.mes.enums.wm.MesWmTransactionTypeEnum;
+import cn.iocoder.yudao.module.mdm.api.item.MdmItemApi;
+import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemRespDTO;
+import cn.iocoder.yudao.module.srm.api.supplier.SrmSupplierApi;
+import cn.iocoder.yudao.module.srm.api.supplier.dto.SrmSupplierRespDTO;
import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService;
+import cn.iocoder.yudao.module.mes.service.mdm.MesMdmItemService;
import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemTypeService;
+import cn.iocoder.yudao.module.mes.service.wm.batch.MesWmBatchService;
+import cn.iocoder.yudao.module.mes.controller.admin.wm.batch.vo.MesWmBatchGenerateReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.wm.batch.MesWmBatchDO;
+import cn.iocoder.yudao.module.mes.service.wm.transaction.MesWmTransactionService;
+import cn.iocoder.yudao.module.mes.service.wm.transaction.dto.MesWmTransactionSaveReqDTO;
import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseAreaService;
+import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseLocationService;
import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseService;
import jakarta.annotation.Resource;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.validation.annotation.Validated;
import java.math.BigDecimal;
import java.time.LocalDateTime;
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.convertSet;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.filterList;
import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*;
/**
@@ -42,14 +69,31 @@
private MesWmMaterialStockMapper materialStockMapper;
@Resource
- private MesMdItemService itemService;
+ private MesMdmItemService mdmItemService;
+ @Resource
+ private MesMdItemService mdItemService;
@Resource
private MesMdItemTypeService itemTypeService;
@Resource
private MesWmWarehouseAreaService areaService;
@Resource
@Lazy
+ private MesWmWarehouseLocationService locationService;
+ @Resource
+ @Lazy
private MesWmWarehouseService warehouseService;
+ @Resource
+ @Lazy
+ private MesWmTransactionService wmTransactionService;
+ @Resource
+ @Lazy
+ private MesWmBatchService batchService;
+ @Resource
+ private MdmItemApi mdmItemApi;
+ @Resource
+ private SrmSupplierApi srmSupplierApi;
+ @Resource
+ private PlatformTransactionManager transactionManager;
@Override
public MesWmMaterialStockDO getMaterialStock(Long id) {
@@ -79,10 +123,13 @@
List<MesMdItemTypeDO> children = itemTypeService.getItemTypeChildrenList(pageReqVO.getItemTypeId());
itemTypeIds.addAll(convertSet(children, MesMdItemTypeDO::getId));
}
- // 1.2 瑙f瀽 itemId
+ // 1.2 瑙f瀽 itemId锛氬墠绔紶鐨勬槸 mes_md_item.id锛屽簱瀛樿〃瀛樼殑鏄� mdm_item_id锛岄渶瑕佽浆鎹�
Set<Long> itemIds = null;
if (pageReqVO.getItemId() != null) {
- itemIds = SetUtils.asSet(pageReqVO.getItemId());
+ MesMdItemDO item = mdItemService.getItem(pageReqVO.getItemId());
+ if (item != null && item.getMdmItemId() != null) {
+ itemIds = SetUtils.asSet(item.getMdmItemId());
+ }
}
// 1.3 瑙f瀽 virtualFilter锛氳浆鎹负铏氭嫙浠� warehouseId
Long virtualWarehouseId = null;
@@ -156,9 +203,16 @@
}
// 2. 涓嶅瓨鍦ㄥ垯鏂板缓
- MesMdItemDO item = itemService.validateItemExists(itemId);
+ return createMaterialStock(itemId, warehouseId, locationId, areaId, batchId, batchCode, vendorId, receiptTime);
+ }
+
+ @Override
+ public MesWmMaterialStockDO createMaterialStock(Long itemId, Long warehouseId, Long locationId, Long areaId,
+ Long batchId, String batchCode, Long vendorId, LocalDateTime receiptTime) {
+ // 濮嬬粓鍒涘缓鏂板簱瀛樿褰�
+ MesMdmItemDO item = mdmItemService.validateItemExists(itemId);
MesWmMaterialStockDO newStock = MesWmMaterialStockDO.builder()
- .itemId(itemId).itemTypeId(item.getItemTypeId())
+ .itemId(itemId).itemTypeId(item.getCategoryId())
.warehouseId(warehouseId).locationId(locationId).areaId(areaId)
.batchId(batchId).batchCode(batchCode).vendorId(vendorId)
.quantity(BigDecimal.ZERO) // 鍒濆鏁伴噺涓� 0锛岀敱 updateMaterialStockQuantity 鏇存柊
@@ -166,7 +220,15 @@
.frozen(false)
.build();
materialStockMapper.insert(newStock);
- return newStock;
+ // 閲嶆柊鏌ヨ锛岀‘淇濇嬁鍒版暟鎹簱鐢熸垚鐨勫畬鏁磋褰曪紙id銆乧reateTime 绛夛級
+ MesWmMaterialStockDO inserted = materialStockMapper.selectById(newStock.getId());
+ return inserted != null ? inserted : newStock;
+ }
+
+ @Override
+ public MesWmMaterialStockDO getMaterialStockByCompositeKey(Long itemId, Long warehouseId, Long locationId, Long areaId,
+ Long batchId, Long vendorId) {
+ return materialStockMapper.selectByCompositeKey(itemId, warehouseId, locationId, areaId, batchId, vendorId);
}
@Override
@@ -216,14 +278,13 @@
throw exception(WM_MATERIAL_STOCK_REQUIRED);
}
MesWmMaterialStockDO stock = validateMaterialStockExists(materialStockId);
- // 2. 鏍¢獙搴撳瓨璁板綍鐨勭墿鏂欍�佹壒娆°�佷粨搴撱�佸簱鍖恒�佸簱浣嶇瓑淇℃伅涓庡墠绔�夋嫨鐨勪竴鑷达紝閬垮厤涓插崟鎴栬秺鏉冩彁浜�
- if (ObjUtil.notEqual(stock.getItemId(), itemId)
- || ObjUtil.notEqual(stock.getBatchId(), batchId)
- || (batchCode != null && ObjUtil.notEqual(stock.getBatchCode(), batchCode))
- || ObjUtil.notEqual(stock.getWarehouseId(), warehouseId)
- || ObjUtil.notEqual(stock.getLocationId(), locationId)
- || ObjUtil.notEqual(stock.getAreaId(), areaId)) {
- throw exception(WM_MATERIAL_STOCK_SELECTION_MISMATCH);
+ // 2. 鏍¢獙搴撳瓨璁板綍鐨勭墿鏂欍�佹壒娆°�佷粨搴撱�佸簱鍖恒�佸簱浣嶇瓑淇℃伅涓庡崟鎹竴鑷达紝閬垮厤涓插崟鎴栬秺鏉冩彁浜ゃ��
+ // 閫愪釜缁村害姣斿锛屽懡涓椂鎻愮ず鍏蜂綋宸紓锛堝簱瀛樹晶 vs 鍗曟嵁渚э級骞剁粰鍑哄鐞嗗姙娉曪紝閬垮厤绗肩粺鎶�"淇℃伅涓嶄竴鑷�"
+ String mismatch = buildSelectedStockMismatch(
+ stock, itemId, batchId, batchCode, warehouseId, locationId, areaId);
+ if (mismatch != null) {
+ throw new ServiceException(WM_MATERIAL_STOCK_SELECTION_MISMATCH.getCode(),
+ "鎵�閫夊簱瀛樿褰曚笌鍗曟嵁涓嶄竴鑷达細" + mismatch + "銆傝閲嶆柊閫夋嫨璇ョ墿鏂欐纭殑搴撳瓨锛涜嫢浠撳簱/搴撳尯/搴撲綅/鎵规琚鐢ㄦ棤娉曚慨鏀癸紝璇峰垹闄ゆ湰鏉℃槑缁嗗悗閲嶆柊娣诲姞銆�");
}
// 3. 鏍¢獙搴撳瓨鏁伴噺鍏呰冻锛堝鏋滃墠绔紶浜� quantity锛屽垯蹇呴』淇濊瘉搴撳瓨鏁伴噺 >= quantity锛�
if (quantity != null && stock.getQuantity() != null && stock.getQuantity().compareTo(quantity) < 0) {
@@ -232,4 +293,544 @@
return stock;
}
+ /**
+ * 閫愮淮搴︽瘮瀵瑰緟閫夊簱瀛樹笌鍗曟嵁浼犲叆鐨勪俊鎭紝杩斿洖绗竴涓笉涓�鑷寸淮搴︾殑鍙鎻忚堪锛涘叏閮ㄤ竴鑷磋繑鍥� null
+ */
+ private String buildSelectedStockMismatch(MesWmMaterialStockDO stock, Long itemId, Long batchId, String batchCode,
+ Long warehouseId, Long locationId, Long areaId) {
+ if (ObjUtil.notEqual(stock.getItemId(), itemId)) {
+ return "鎵�閫夊簱瀛樺綊灞炵墿鏂橻" + itemIdText(itemId) + "]锛屽崟鎹墿鏂欎负[" + itemIdText(stock.getItemId()) + "]";
+ }
+ if (ObjUtil.notEqual(stock.getBatchId(), batchId)) {
+ return "鎵�閫夊簱瀛樻壒娆" + batchText(stock.getBatchId(), stock.getBatchCode())
+ + "]锛屽崟鎹壒娆′负[" + batchText(batchId, batchCode) + "]";
+ }
+ if (batchCode != null && ObjUtil.notEqual(stock.getBatchCode(), batchCode)) {
+ return "鎵�閫夊簱瀛樻壒娆″彿[" + stock.getBatchCode() + "]锛屽崟鎹壒娆″彿涓篬" + batchCode + "]";
+ }
+ if (ObjUtil.notEqual(stock.getWarehouseId(), warehouseId)) {
+ return "鎵�閫夊簱瀛樹粨搴揫" + warehouseText(stock.getWarehouseId())
+ + "]锛屽崟鎹粨搴撲负[" + warehouseText(warehouseId) + "]";
+ }
+ if (ObjUtil.notEqual(stock.getLocationId(), locationId)) {
+ return "鎵�閫夊簱瀛樺簱鍖篬" + locationText(stock.getLocationId())
+ + "]锛屽崟鎹簱鍖轰负[" + locationText(locationId) + "]";
+ }
+ if (ObjUtil.notEqual(stock.getAreaId(), areaId)) {
+ return "鎵�閫夊簱瀛樺簱浣峓" + areaText(stock.getAreaId())
+ + "]锛屽崟鎹簱浣嶄负[" + areaText(areaId) + "]";
+ }
+ return null;
+ }
+
+ private String itemIdText(Long itemId) {
+ return itemId == null ? "鏈�夋嫨" : ("鐗╂枡ID=" + itemId);
+ }
+
+ private String batchText(Long batchId, String batchCode) {
+ if (StrUtil.isNotBlank(batchCode)) {
+ return batchCode;
+ }
+ return batchId == null ? "鏈�夋嫨" : ("鎵规ID=" + batchId);
+ }
+
+ private String warehouseText(Long warehouseId) {
+ if (warehouseId == null) {
+ return "鏈�夋嫨";
+ }
+ MesWmWarehouseDO warehouse = warehouseService.getWarehouse(warehouseId);
+ return warehouse != null && StrUtil.isNotBlank(warehouse.getName())
+ ? warehouse.getName() : ("浠撳簱ID=" + warehouseId);
+ }
+
+ private String locationText(Long locationId) {
+ if (locationId == null) {
+ return "鏈�夋嫨";
+ }
+ MesWmWarehouseLocationDO location = locationService.getWarehouseLocation(locationId);
+ return location != null && StrUtil.isNotBlank(location.getName())
+ ? location.getName() : ("搴撳尯ID=" + locationId);
+ }
+
+ private String areaText(Long areaId) {
+ if (areaId == null) {
+ return "鏈�夋嫨";
+ }
+ MesWmWarehouseAreaDO area = areaService.getWarehouseArea(areaId);
+ return area != null && StrUtil.isNotBlank(area.getName())
+ ? area.getName() : ("搴撲綅ID=" + areaId);
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public Long adjustMaterialStockIn(MesWmMaterialStockAdjustReqVO reqVO) {
+ return doAdjustMaterialStock(reqVO, true);
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public Long adjustMaterialStockOut(MesWmMaterialStockAdjustReqVO reqVO) {
+ return doAdjustMaterialStock(reqVO, false);
+ }
+
+ /**
+ * 鎵ц搴撳瓨璋冩暣
+ *
+ * @param reqVO 璋冩暣璇锋眰
+ * @param isInbound 鏄惁涓哄叆搴擄紙true=鍏ュ簱锛宖alse=鍑哄簱锛�
+ * @return 搴撳瓨璁板綍ID
+ */
+ private Long doAdjustMaterialStock(MesWmMaterialStockAdjustReqVO reqVO, boolean isInbound) {
+ // 1. 鏍¢獙鐗╂枡瀛樺湪涓斿惎鐢�
+ MesMdmItemDO item = mdmItemService.validateItemExistsAndEnable(reqVO.getItemId());
+
+ // 2. 澶勭悊鎵规锛堜粎鍏ュ簱鏃堕渶瑕佸鐞嗘壒娆★級
+ Long batchId = reqVO.getBatchId();
+ String batchCode = reqVO.getBatchCode();
+ if (isInbound) {
+ MesWmBatchDO batch = resolveBatch(reqVO, item);
+ if (batch != null) {
+ batchId = batch.getId();
+ batchCode = batch.getCode();
+ }
+ } else {
+ // 鍑哄簱鏃舵牎楠屾壒娆″瓨鍦�
+ if (batchId != null) {
+ batchService.validateBatchExists(batchId, reqVO.getItemId());
+ }
+ }
+
+ // 3. 鏍¢獙搴撲綅娣锋斁瑙勫垯
+ checkAreaMixingRule(reqVO.getAreaId(), reqVO.getItemId(), batchId);
+
+ // 4. 鑾峰彇鎴栧垱寤哄簱瀛樿褰�
+ MesWmMaterialStockDO stock = getOrCreateMaterialStock(
+ reqVO.getItemId(),
+ reqVO.getWarehouseId(),
+ reqVO.getLocationId(),
+ reqVO.getAreaId(),
+ batchId,
+ batchCode,
+ reqVO.getVendorId(),
+ LocalDateTime.now()
+ );
+
+ // 5. 璁$畻鍙樺姩鏁伴噺锛氬叆搴撲负姝o紝鍑哄簱涓鸿礋
+ BigDecimal quantity = isInbound ? reqVO.getQuantity() : reqVO.getQuantity().negate();
+
+ // 6. 鍒涘缓搴撳瓨浜嬪姟娴佹按锛堝唴閮ㄤ細鏇存柊搴撳瓨鏁伴噺锛�
+ int bizType = isInbound ? MesBizTypeConstants.WM_STOCK_ADJUST_IN : MesBizTypeConstants.WM_STOCK_ADJUST_OUT;
+ MesWmTransactionSaveReqDTO transaction = new MesWmTransactionSaveReqDTO()
+ .setType(isInbound ? MesWmTransactionTypeEnum.IN.getType() : MesWmTransactionTypeEnum.OUT.getType())
+ .setBizType(bizType)
+ .setBizId(stock.getId())
+ .setBizCode("STOCK_ADJUST_" + stock.getId())
+ .setBizLineId(stock.getId())
+ .setItemId(reqVO.getItemId())
+ .setQuantity(quantity)
+ .setBatchId(batchId)
+ .setBatchCode(batchCode)
+ .setWarehouseId(reqVO.getWarehouseId())
+ .setLocationId(reqVO.getLocationId())
+ .setAreaId(reqVO.getAreaId())
+ .setVendorId(reqVO.getVendorId())
+ .setReceiptTime(LocalDateTime.now())
+ .setCheckFlag(!isInbound);
+ wmTransactionService.createTransaction(transaction);
+
+ return stock.getId();
+ }
+
+ /**
+ * 瑙f瀽鎵规
+ * <p>
+ * 鍏ュ簱鏃剁殑鎵规澶勭悊閫昏緫锛�
+ * 1. 鐗╂枡鏈惎鐢ㄦ壒娆$鐞嗭細杩斿洖 null
+ * 2. 浼犲叆 batchId锛氭牎楠屽苟浣跨敤宸叉湁鎵规
+ * 3. 鏈紶 batchId锛氭牴鎹� batchCode 绛夊弬鏁拌嚜鍔ㄥ垱寤烘壒娆�
+ *
+ * @param reqVO 鍏ュ簱璇锋眰
+ * @param item 鐗╂枡淇℃伅
+ * @return 鎵规璁板綍锛堢墿鏂欐湭鍚敤鎵规绠$悊鏃惰繑鍥� null锛�
+ */
+ private MesWmBatchDO resolveBatch(MesWmMaterialStockAdjustReqVO reqVO, MesMdmItemDO item) {
+ // 1. 鐗╂枡鏈惎鐢ㄦ壒娆$鐞嗭紝杩斿洖 null
+ if (Boolean.FALSE.equals(item.getIsBatchManaged())) {
+ return null;
+ }
+
+ // 2. 浼犲叆 batchId锛屾牎楠屽苟杩斿洖宸叉湁鎵规
+ if (reqVO.getBatchId() != null) {
+ return batchService.validateBatchExists(reqVO.getBatchId(), reqVO.getItemId());
+ }
+
+ // 3. 鏈紶 batchId锛岃嚜鍔ㄥ垱寤烘壒娆�
+ MesWmBatchGenerateReqVO batchReqVO = new MesWmBatchGenerateReqVO()
+ .setItemId(reqVO.getItemId())
+ .setBatchCode(reqVO.getBatchCode())
+ .setVendorId(reqVO.getVendorId())
+ .setProduceDate(reqVO.getProduceDate() != null ? reqVO.getProduceDate().atStartOfDay() : null)
+ .setExpireDate(reqVO.getExpireDate() != null ? reqVO.getExpireDate().atStartOfDay() : null)
+ .setReceiptDate(LocalDateTime.now())
+ .setLotNumber(reqVO.getLotNumber())
+ .setQualityStatus(parseQualityStatus(reqVO.getQualityStatus()));
+ return batchService.getOrGenerateBatchCode(batchReqVO);
+ }
+
+ /**
+ * 瑙f瀽璐ㄩ噺鐘舵�侊紙鏀寔鏁板瓧鎴栧瓧绗︿覆锛�
+ */
+ private Integer parseQualityStatus(Object qualityStatus) {
+ if (qualityStatus == null) {
+ return null;
+ }
+ if (qualityStatus instanceof Integer) {
+ return (Integer) qualityStatus;
+ }
+ if (qualityStatus instanceof String) {
+ String str = (String) qualityStatus;
+ // 鏀寔鏁板瓧瀛楃涓�
+ if (str.matches("\\d+")) {
+ return Integer.parseInt(str);
+ }
+ // 鏀寔甯歌瀛楃涓叉槧灏�
+ switch (str.toUpperCase()) {
+ case "PENDING":
+ case "寰呮楠�":
+ case "寰呮":
+ return 0;
+ case "PASS":
+ case "QUALIFIED":
+ case "鍚堟牸":
+ return 1;
+ case "FAIL":
+ case "UNQUALIFIED":
+ case "涓嶅悎鏍�":
+ return 2;
+ default:
+ throw new IllegalArgumentException("鏃犳晥鐨勮川閲忕姸鎬�: " + str);
+ }
+ }
+ throw new IllegalArgumentException("鏃犳晥鐨勮川閲忕姸鎬佺被鍨�: " + qualityStatus.getClass().getName());
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void moveMaterialStock(MesWmMaterialStockMoveReqVO reqVO) {
+ // 1. 鏌ヨ鍘熷簱瀛樿褰�
+ MesWmMaterialStockDO sourceStock = validateMaterialStockExists(reqVO.getMaterialStockId());
+
+ // 2. 鏍¢獙搴撳瓨鏁伴噺鍏呰冻锛堝彲鐢ㄩ噺 = 鍦ㄥ簱鏁伴噺 - 鍐荤粨鏁伴噺 - 鍗犵敤閲忥級
+ BigDecimal availableQty = sourceStock.getQuantity() != null ? sourceStock.getQuantity() : BigDecimal.ZERO;
+ if (sourceStock.getFrozenQuantity() != null) {
+ availableQty = availableQty.subtract(sourceStock.getFrozenQuantity());
+ }
+ if (sourceStock.getReservedQuantity() != null) {
+ availableQty = availableQty.subtract(sourceStock.getReservedQuantity());
+ }
+ if (availableQty.compareTo(reqVO.getQuantity()) < 0) {
+ throw exception(WM_MATERIAL_STOCK_INSUFFICIENT);
+ }
+
+ // 3. 鏍¢獙搴撳瓨鏈喕缁�
+ if (Boolean.TRUE.equals(sourceStock.getFrozen())) {
+ throw exception(WM_MATERIAL_STOCK_FROZEN);
+ }
+
+ // 4. 鏍¢獙鐩爣搴撲綅娣锋斁瑙勫垯
+ checkAreaMixingRule(reqVO.getToAreaId(), sourceStock.getItemId(), sourceStock.getBatchId());
+
+ // 5. 鐢熸垚绉诲簱鍗曞彿
+ String moveCode = "MOVE_" + System.currentTimeMillis();
+
+ // 6. 鍒涘缓璋冩嫧绉诲嚭浜嬪姟锛堣礋鏁帮級
+ MesWmTransactionSaveReqDTO outTransaction = new MesWmTransactionSaveReqDTO()
+ .setType(MesWmTransactionTypeEnum.MOVE_OUT.getType())
+ .setBizType(MesBizTypeConstants.WM_TRANSFER_OUT)
+ .setBizId(sourceStock.getId())
+ .setBizCode(moveCode)
+ .setBizLineId(sourceStock.getId())
+ .setItemId(sourceStock.getItemId())
+ .setQuantity(reqVO.getQuantity().negate())
+ .setBatchId(sourceStock.getBatchId())
+ .setBatchCode(sourceStock.getBatchCode())
+ .setWarehouseId(sourceStock.getWarehouseId())
+ .setLocationId(sourceStock.getLocationId())
+ .setAreaId(sourceStock.getAreaId())
+ .setVendorId(sourceStock.getVendorId())
+ .setMaterialStockId(sourceStock.getId())
+ .setCheckFlag(true);
+ Long outTransactionId = wmTransactionService.createTransaction(outTransaction);
+
+ // 7. 鏌ユ壘鐩爣搴撲綅鐨勫簱瀛樿褰曪紙蹇界暐渚涘簲鍟嗭級锛岀敤浜庡悎骞跺簱瀛�
+ MesWmMaterialStockDO targetStock = materialStockMapper.selectByCompositeKeyIgnoreVendor(
+ sourceStock.getItemId(),
+ reqVO.getToWarehouseId(),
+ reqVO.getToLocationId(),
+ reqVO.getToAreaId(),
+ sourceStock.getBatchId()
+ );
+
+ // 8. 鍒涘缓璋冩嫧绉诲叆浜嬪姟锛堟鏁帮級锛屽叧鑱斿嚭搴撲簨鍔�
+ // 浣跨敤鐩爣搴撲綅宸叉湁搴撳瓨璁板綍鐨� vendorId锛堝鏋滄湁锛夛紝鍚﹀垯涓嶄紶
+ MesWmTransactionSaveReqDTO inTransaction = new MesWmTransactionSaveReqDTO()
+ .setType(MesWmTransactionTypeEnum.MOVE_IN.getType())
+ .setBizType(MesBizTypeConstants.WM_TRANSFER_IN)
+ .setBizId(sourceStock.getId())
+ .setBizCode(moveCode)
+ .setBizLineId(sourceStock.getId())
+ .setItemId(sourceStock.getItemId())
+ .setQuantity(reqVO.getQuantity())
+ .setBatchId(sourceStock.getBatchId())
+ .setBatchCode(sourceStock.getBatchCode())
+ .setWarehouseId(reqVO.getToWarehouseId())
+ .setLocationId(reqVO.getToLocationId())
+ .setAreaId(reqVO.getToAreaId())
+ .setVendorId(targetStock != null ? targetStock.getVendorId() : null)
+ .setRelatedTransactionId(outTransactionId)
+ .setCheckFlag(false);
+ wmTransactionService.createTransaction(inTransaction);
+ }
+
+ @Override
+ public MesWmMaterialStockImportRespVO importMaterialStockList(List<MesWmMaterialStockImportExcelVO> importList) {
+ // 鎿嶄綔绫诲瀷鍙栧�硷細澧炲姞 / 鍑忓皯 / 瑕嗙洊
+ final String OP_ADD = "澧炲姞";
+ final String OP_REDUCE = "鍑忓皯";
+ final String OP_COVER = "瑕嗙洊";
+ // 1. 杩囨护鍏ㄧ┖琛�
+ importList = filterList(importList, item -> StrUtil.isNotBlank(item.getItemCode())
+ || StrUtil.isNotBlank(item.getBatchCode()) || item.getQuantity() != null);
+ if (CollUtil.isEmpty(importList)) {
+ throw exception(WM_MATERIAL_STOCK_IMPORT_LIST_IS_EMPTY);
+ }
+ // 2. 棰勫姞杞� 缂栫爜/鍚嶇О -> 瀹炰綋 鏄犲皠锛堢紪鐮佷紭鍏堬紝鍚嶇О鍏滃簳锛�
+ Map<String, MdmItemRespDTO> itemMap = mdmItemApi.getItemListByStatus(null).getCheckedData().stream()
+ .collect(Collectors.toMap(MdmItemRespDTO::getCode, item -> item, (v1, v2) -> v1));
+ Map<String, MesWmWarehouseDO> warehouseCodeMap = new HashMap<>();
+ Map<String, MesWmWarehouseDO> warehouseNameMap = new HashMap<>();
+ for (MesWmWarehouseDO warehouse : warehouseService.getWarehouseList()) {
+ warehouseCodeMap.putIfAbsent(warehouse.getCode(), warehouse);
+ warehouseNameMap.putIfAbsent(warehouse.getName(), warehouse);
+ }
+ Map<String, MesWmWarehouseLocationDO> locationCodeMap = new HashMap<>();
+ Map<String, MesWmWarehouseLocationDO> locationNameMap = new HashMap<>();
+ for (MesWmWarehouseLocationDO location : locationService.getWarehouseLocationList((Long) null)) {
+ locationCodeMap.putIfAbsent(location.getCode(), location);
+ locationNameMap.putIfAbsent(location.getName(), location);
+ }
+ Map<String, MesWmWarehouseAreaDO> areaCodeMap = new HashMap<>();
+ Map<String, MesWmWarehouseAreaDO> areaNameMap = new HashMap<>();
+ for (MesWmWarehouseAreaDO area : areaService.getWarehouseAreaList((Long) null)) {
+ areaCodeMap.putIfAbsent(area.getCode(), area);
+ areaNameMap.putIfAbsent(area.getName(), area);
+ }
+ Map<String, SrmSupplierRespDTO> supplierCodeMap = new HashMap<>();
+ Map<String, SrmSupplierRespDTO> supplierNameMap = new HashMap<>();
+ for (SrmSupplierRespDTO supplier : srmSupplierApi.getSupplierSimpleList().getCheckedData()) {
+ supplierCodeMap.putIfAbsent(supplier.getCode(), supplier);
+ supplierNameMap.putIfAbsent(supplier.getName(), supplier);
+ }
+ // 3. 閫愭潯澶勭悊
+ MesWmMaterialStockImportRespVO respVO = MesWmMaterialStockImportRespVO.builder()
+ .createNames(new ArrayList<>())
+ .updateNames(new ArrayList<>())
+ .failureNames(new LinkedHashMap<>())
+ .build();
+ TransactionTemplate importTxTemplate = new TransactionTemplate(transactionManager);
+ for (MesWmMaterialStockImportExcelVO importStock : importList) {
+ String key = StrUtil.blankToDefault(importStock.getItemCode(), "") + "-"
+ + StrUtil.blankToDefault(importStock.getWarehouse(), "");
+ try {
+ importTxTemplate.executeWithoutResult(status -> {
+ // 3.1 鐗╂枡
+ MdmItemRespDTO item = resolveImportItem(itemMap, importStock.getItemCode());
+ // 3.2 浠撳簱/搴撳尯/搴撲綅锛堢紪鐮佷紭鍏堛�佸悕绉板厹搴曪紝鍚埗瀛愬叧绯绘牎楠岋級
+ MesWmWarehouseDO warehouse = resolveImportWarehouse(warehouseCodeMap, warehouseNameMap,
+ importStock.getWarehouse());
+ Long locationId = null;
+ if (StrUtil.isNotBlank(importStock.getLocation())) {
+ MesWmWarehouseLocationDO location = resolveImportLocationOrArea(locationCodeMap,
+ locationNameMap, importStock.getLocation(), WM_MATERIAL_STOCK_IMPORT_LOCATION_NOT_EXISTS);
+ if (ObjUtil.notEqual(location.getWarehouseId(), warehouse.getId())) {
+ throw exception(WM_WAREHOUSE_AREA_WAREHOUSE_MISMATCH);
+ }
+ locationId = location.getId();
+ }
+ Long areaId = null;
+ if (StrUtil.isNotBlank(importStock.getArea())) {
+ MesWmWarehouseAreaDO area = resolveImportLocationOrArea(areaCodeMap,
+ areaNameMap, importStock.getArea(), WM_MATERIAL_STOCK_IMPORT_AREA_NOT_EXISTS);
+ if (ObjUtil.notEqual(area.getLocationId(),
+ locationId != null ? locationId : null)) {
+ throw exception(WM_WAREHOUSE_AREA_RELATION_INVALID);
+ }
+ areaId = area.getId();
+ }
+ // 3.3 鎵规锛堢墿鏂欏惎鐢ㄦ壒娆$鐞嗘椂蹇呭~锛屼笖鎵规闇�褰掑睘璇ョ墿鏂欙級
+ Long batchId = null;
+ String batchCode = null;
+ if (Boolean.TRUE.equals(item.getIsBatchManaged())) {
+ if (StrUtil.isBlank(importStock.getBatchCode())) {
+ throw exception(WM_TRANSACTION_BATCH_REQUIRED);
+ }
+ MesWmBatchDO batch = batchService.getBatchByCode(importStock.getBatchCode().trim());
+ if (batch == null || ObjUtil.notEqual(batch.getItemId(), item.getId())) {
+ throw exception(WM_MATERIAL_STOCK_IMPORT_BATCH_NOT_EXISTS,
+ importStock.getBatchCode().trim());
+ }
+ batchId = batch.getId();
+ batchCode = batch.getCode();
+ }
+ // 3.4 渚涘簲鍟嗭紙鍙�夛級
+ Long vendorId = resolveImportVendor(supplierCodeMap, supplierNameMap, importStock.getVendor());
+ // 3.5 鎿嶄綔绫诲瀷锛堝繀濉細澧炲姞/鍑忓皯/瑕嗙洊锛�
+ String operationType = importStock.getOperationType() == null
+ ? null : importStock.getOperationType().trim();
+ if (StrUtil.isBlank(operationType)) {
+ throw exception(WM_MATERIAL_STOCK_IMPORT_FIELD_REQUIRED, "鎿嶄綔绫诲瀷");
+ }
+ if (!OP_ADD.equals(operationType) && !OP_REDUCE.equals(operationType)
+ && !OP_COVER.equals(operationType)) {
+ throw exception(WM_MATERIAL_STOCK_IMPORT_OPERATION_INVALID, operationType);
+ }
+ // 3.6 鏁伴噺鏍¢獙锛氬鍔�/鍑忓皯涓哄彉鍔ㄩ噺椤讳负姝f暟锛涜鐩栦负鐩爣鐜版湁閲忛』 >= 0
+ BigDecimal quantity = importStock.getQuantity();
+ boolean cover = OP_COVER.equals(operationType);
+ if (quantity == null || quantity.compareTo(BigDecimal.ZERO) < 0
+ || (!cover && quantity.compareTo(BigDecimal.ZERO) == 0)) {
+ throw exception(WM_MATERIAL_STOCK_IMPORT_QUANTITY_INVALID,
+ quantity == null ? "绌�" : quantity);
+ }
+ // 3.7 鎸夋搷浣滅被鍨嬪畾浣嶅苟澶勭悊搴撳瓨璁板綍锛堜笉瀛樺湪鍒欏垱寤猴紝瀛樺湪鍒欒皟鏁存暟閲忥級
+ MesWmMaterialStockDO existStock = materialStockMapper.selectByCompositeKey(
+ item.getId(), warehouse.getId(), locationId, areaId, batchId, vendorId);
+ Long wId = warehouse.getId();
+ if (OP_REDUCE.equals(operationType)) {
+ // 鍑忓皯锛氬簱瀛樺繀椤诲瓨鍦紝鎵e噺涓嶈冻鏃剁敱鍑哄簱娴佹按鏍¢獙骞跺け璐�
+ if (existStock == null) {
+ throw exception(WM_MATERIAL_STOCK_IMPORT_STOCK_NOT_EXISTS);
+ }
+ createImportTransaction(existStock, item.getId(), batchId, batchCode,
+ wId, locationId, areaId, vendorId, quantity.negate());
+ respVO.getUpdateNames().add(key);
+ } else if (OP_ADD.equals(operationType)) {
+ // 澧炲姞锛氫笉瀛樺湪鍒欏缓搴撳瓨鍐嶅叆搴擄紱宸插瓨鍦ㄥ垯鐩存帴杩藉姞鍙樺姩閲�
+ if (existStock == null) {
+ MesWmMaterialStockDO newStock = getOrCreateMaterialStock(
+ item.getId(), wId, locationId, areaId,
+ batchId, batchCode, vendorId, LocalDateTime.now());
+ createImportTransaction(newStock, item.getId(), batchId, batchCode,
+ wId, locationId, areaId, vendorId, quantity);
+ respVO.getCreateNames().add(key);
+ } else {
+ createImportTransaction(existStock, item.getId(), batchId, batchCode,
+ wId, locationId, areaId, vendorId, quantity);
+ respVO.getUpdateNames().add(key);
+ }
+ } else {
+ // 瑕嗙洊锛氭暟閲忎负鐩爣鐜版湁閲忥紱涓嶅瓨鍦ㄥ垯鏂板缓骞剁疆閲忥紝宸插瓨鍦ㄦ寜宸鍑哄叆搴�
+ if (existStock == null) {
+ MesWmMaterialStockDO newStock = getOrCreateMaterialStock(
+ item.getId(), wId, locationId, areaId,
+ batchId, batchCode, vendorId, LocalDateTime.now());
+ if (quantity.compareTo(BigDecimal.ZERO) > 0) {
+ createImportTransaction(newStock, item.getId(), batchId, batchCode,
+ wId, locationId, areaId, vendorId, quantity);
+ }
+ respVO.getCreateNames().add(key);
+ } else {
+ BigDecimal currentQty = existStock.getQuantity() != null
+ ? existStock.getQuantity() : BigDecimal.ZERO;
+ BigDecimal diff = quantity.subtract(currentQty);
+ if (diff.compareTo(BigDecimal.ZERO) != 0) {
+ createImportTransaction(existStock, item.getId(), batchId, batchCode,
+ wId, locationId, areaId, vendorId, diff);
+ }
+ respVO.getUpdateNames().add(key);
+ }
+ }
+ });
+ } catch (Exception ex) {
+ respVO.getFailureNames().put(key, ex.getMessage());
+ }
+ }
+ return respVO;
+ }
+
+ private MdmItemRespDTO resolveImportItem(Map<String, MdmItemRespDTO> itemMap, String itemCode) {
+ if (StrUtil.isBlank(itemCode)) {
+ throw exception(WM_MATERIAL_STOCK_IMPORT_FIELD_REQUIRED, "鐗╂枡缂栫爜");
+ }
+ MdmItemRespDTO item = itemMap.get(itemCode.trim());
+ if (item == null) {
+ throw exception(WM_MATERIAL_STOCK_IMPORT_ITEM_NOT_EXISTS, itemCode.trim());
+ }
+ return item;
+ }
+
+ private MesWmWarehouseDO resolveImportWarehouse(Map<String, MesWmWarehouseDO> codeMap,
+ Map<String, MesWmWarehouseDO> nameMap, String value) {
+ if (StrUtil.isBlank(value)) {
+ throw exception(WM_MATERIAL_STOCK_IMPORT_FIELD_REQUIRED, "浠撳簱");
+ }
+ String trimmed = value.trim();
+ MesWmWarehouseDO warehouse = codeMap.get(trimmed) != null ? codeMap.get(trimmed) : nameMap.get(trimmed);
+ if (warehouse == null) {
+ throw exception(WM_MATERIAL_STOCK_IMPORT_WAREHOUSE_NOT_EXISTS, trimmed);
+ }
+ return warehouse;
+ }
+
+ private <T> T resolveImportLocationOrArea(Map<String, T> codeMap, Map<String, T> nameMap,
+ String value, ErrorCode errorCode) {
+ String trimmed = value.trim();
+ T result = codeMap.get(trimmed) != null ? codeMap.get(trimmed) : nameMap.get(trimmed);
+ if (result == null) {
+ throw exception(errorCode, trimmed);
+ }
+ return result;
+ }
+
+ private Long resolveImportVendor(Map<String, SrmSupplierRespDTO> codeMap,
+ Map<String, SrmSupplierRespDTO> nameMap, String value) {
+ if (StrUtil.isBlank(value)) {
+ return null;
+ }
+ String trimmed = value.trim();
+ SrmSupplierRespDTO supplier = codeMap.get(trimmed) != null ? codeMap.get(trimmed) : nameMap.get(trimmed);
+ if (supplier == null) {
+ throw exception(WM_MATERIAL_STOCK_IMPORT_VENDOR_NOT_EXISTS, trimmed);
+ }
+ return supplier.getId();
+ }
+
+ /**
+ * 鍒涘缓瀵煎叆浜х敓鐨勫簱瀛樹簨鍔℃祦姘达紙澶嶇敤搴撳瓨璋冩暣鐨勬祦姘存ā寮忥級
+ *
+ * @param quantity 鍙樺姩鏁伴噺锛堟鏁�=鍏ュ簱锛岃礋鏁�=鍑哄簱锛�
+ */
+ private void createImportTransaction(MesWmMaterialStockDO stock, Long itemId, Long batchId, String batchCode,
+ Long warehouseId, Long locationId, Long areaId, Long vendorId,
+ BigDecimal quantity) {
+ boolean inbound = quantity.compareTo(BigDecimal.ZERO) > 0;
+ MesWmTransactionSaveReqDTO transaction = new MesWmTransactionSaveReqDTO()
+ .setType(inbound ? MesWmTransactionTypeEnum.IN.getType() : MesWmTransactionTypeEnum.OUT.getType())
+ .setBizType(inbound ? MesBizTypeConstants.WM_STOCK_ADJUST_IN : MesBizTypeConstants.WM_STOCK_ADJUST_OUT)
+ .setBizId(stock.getId())
+ .setBizCode("STOCK_IMPORT_" + stock.getId())
+ .setBizLineId(stock.getId())
+ .setItemId(itemId)
+ .setQuantity(quantity.abs())
+ .setBatchId(batchId)
+ .setBatchCode(batchCode)
+ .setWarehouseId(warehouseId)
+ .setLocationId(locationId)
+ .setAreaId(areaId)
+ .setVendorId(vendorId)
+ .setReceiptTime(LocalDateTime.now())
+ .setCheckFlag(!inbound);
+ wmTransactionService.createTransaction(transaction);
+ }
+
}
--
Gitblit v1.9.3