From 1b3fd46c7ccbf636208bcf098c5588441bb71f78 Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期二, 25 八月 2026 17:53:53 +0800
Subject: [PATCH] feat(mes): 添加销售出库扫码拣货功能
---
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productsales/MesWmProductSalesServiceImpl.java | 146 ++++++++++++++++++++++++++++++++++++++++++++++--
1 files changed, 138 insertions(+), 8 deletions(-)
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productsales/MesWmProductSalesServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productsales/MesWmProductSalesServiceImpl.java
index baec481..c2a2cc6 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productsales/MesWmProductSalesServiceImpl.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productsales/MesWmProductSalesServiceImpl.java
@@ -9,25 +9,40 @@
import cn.iocoder.yudao.module.mes.controller.admin.wm.productsales.vo.MesWmProductSalesPageReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.productsales.vo.MesWmProductSalesSaveReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.productsales.vo.MesWmProductSalesShippingReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.wm.productsales.vo.MesWmProductSalesScanReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.wm.productsales.vo.MesWmProductSalesScanRespVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pro.bagcode.MesProBagCodeDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pro.pallet.MesProPalletDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pro.batch.MesProBatchDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.wm.materialstock.MesWmMaterialStockDO;
+import cn.iocoder.yudao.module.mes.dal.mysql.wm.productsales.MesWmProductSalesDetailMapper;
+import cn.iocoder.yudao.module.mes.service.pro.bagcode.MesProBagCodeService;
+import cn.iocoder.yudao.module.mes.service.pro.pallet.MesProPalletService;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.salesnotice.MesWmSalesNoticeDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productsales.MesWmProductSalesDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productsales.MesWmProductSalesDetailDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productsales.MesWmProductSalesLineDO;
import cn.iocoder.yudao.module.mes.dal.mysql.wm.productsales.MesWmProductSalesMapper;
+import cn.iocoder.yudao.module.mes.dal.mysql.wm.productsales.MesWmProductSalesLineMapper;
import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants;
import cn.iocoder.yudao.module.mes.enums.wm.MesWmProductSalesStatusEnum;
import cn.iocoder.yudao.module.mes.enums.wm.MesWmQualityStatusEnum;
import cn.iocoder.yudao.module.mes.enums.wm.MesWmSalesNoticeStatusEnum;
import cn.iocoder.yudao.module.mes.enums.wm.MesWmStockReserveBizTypeEnum;
import cn.iocoder.yudao.module.mes.enums.wm.MesWmTransactionTypeEnum;
-import cn.iocoder.yudao.module.mes.service.md.client.MesMdClientService;
+import cn.iocoder.yudao.module.crm.api.customer.CrmCustomerApi;
import cn.iocoder.yudao.module.mes.service.wm.materialstock.MesWmMaterialStockService;
import cn.iocoder.yudao.module.mes.service.wm.salesnotice.MesWmSalesNoticeService;
+import cn.iocoder.yudao.module.mes.service.pro.batch.MesProBatchService;
import cn.iocoder.yudao.module.mes.service.wm.stockreserve.MesWmStockReserveService;
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.qc.oqc.MesQcOqcService;
import cn.iocoder.yudao.module.erp.api.sale.ErpSaleOrderApi;
+import cn.iocoder.yudao.module.erp.api.sale.enums.ErpSaleOrderOutStatusEnum;
import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
@@ -42,21 +57,32 @@
/**
* MES 閿�鍞嚭搴撳崟 Service 瀹炵幇绫�
*
- * @author 鑺嬮亾婧愮爜
+ * @author 瓒呯骇绠$悊鍛�
*/
@Service
@Validated
+@Slf4j
public class MesWmProductSalesServiceImpl implements MesWmProductSalesService {
@Resource
private MesWmProductSalesMapper productSalesMapper;
+ @Resource
+ private MesWmProductSalesLineMapper productSalesLineMapper;
+ @Resource
+ private MesWmProductSalesDetailMapper productSalesDetailMapper;
+ @Resource
+ private MesProBagCodeService bagCodeService;
+ @Resource
+ private MesProPalletService palletService;
+ @Resource
+ private MesProBatchService wmBatchService;
@Resource
private MesWmProductSalesLineService productSalesLineService;
@Resource
private MesWmProductSalesDetailService productSalesDetailService;
@Resource
- private MesMdClientService clientService;
+ private CrmCustomerApi customerApi;
@Resource
private MesWmSalesNoticeService salesNoticeService;
@Resource
@@ -65,6 +91,9 @@
private MesWmMaterialStockService materialStockService;
@Resource
private MesWmStockReserveService stockReserveService;
+ @Resource
+ @Lazy
+ private MesQcOqcService oqcService;
@Resource
private ErpSaleOrderApi saleOrderApi;
@@ -113,7 +142,8 @@
@Override
public PageResult<MesWmProductSalesDO> getProductSalesPage(MesWmProductSalesPageReqVO pageReqVO) {
- return productSalesMapper.selectPage(pageReqVO);
+ List<Long> permittedClientIds = customerApi.getPermittedCustomerIds();
+ return productSalesMapper.selectPage(pageReqVO, permittedClientIds);
}
@Override
@@ -133,10 +163,26 @@
}
}
+ // 鑾峰彇鍑哄簱鍗曚俊鎭�
+ MesWmProductSalesDO sales = productSalesMapper.selectById(id);
+
// 妫�鏌ユ墍鏈夎鐨� oqcCheckFlag锛氬鏋滄湁闇�瑕� OQC 妫�楠岀殑琛岋紝杩涘叆寰呮娴嬬姸鎬�
boolean needOqc = CollectionUtils.anyMatch(lines,
line -> Boolean.TRUE.equals(line.getOqcCheckFlag()));
if (needOqc) {
+ // 涓洪渶瑕� OQC 妫�楠屼笖灏氭湭鍒涘缓妫�楠屽崟鐨勬槑缁嗗垱寤� OQC 妫�楠屽崟
+ List<MesWmProductSalesLineDO> oqcLines = CollectionUtils.filterList(lines,
+ line -> Boolean.TRUE.equals(line.getOqcCheckFlag()) && line.getOqcId() == null);
+ for (MesWmProductSalesLineDO line : oqcLines) {
+ // 鐩存帴璋冪敤锛岃寮傚父鎶涘嚭锛屼繚璇佹暟鎹竴鑷存��
+ Long oqcId = oqcService.createOqcFromProductSales(
+ sales.getId(), line, sales.getCode(), sales.getClientId());
+ // 鏇存柊鍑哄簱鏄庣粏鐨� oqcId
+ productSalesLineMapper.updateById(new MesWmProductSalesLineDO()
+ .setId(line.getId())
+ .setOqcId(oqcId));
+ }
+
// 鎵归噺鍒濆鍖� OQC 琛岀殑璐ㄩ噺鐘舵�佷负"寰呮楠�"
List<Long> oqcLineIds = convertList(
CollectionUtils.filterList(lines, line -> Boolean.TRUE.equals(line.getOqcCheckFlag())),
@@ -168,6 +214,66 @@
}
}
return true;
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public MesWmProductSalesScanRespVO scanProductSales(MesWmProductSalesScanReqVO reqVO) {
+ MesWmProductSalesDO sales = validateProductSalesExists(reqVO.getSalesId());
+ if (ObjUtil.notEqual(MesWmProductSalesStatusEnum.APPROVING.getStatus(), sales.getStatus())) {
+ throw exception(WM_PRODUCT_SALES_CANNOT_PICK);
+ }
+ MesProPalletDO pallet = palletService.getPalletByCode(reqVO.getScanContent());
+ MesProBagCodeDO bag = pallet == null ? bagCodeService.getBagCodeByCode(reqVO.getScanContent()) : null;
+ if (pallet == null && bag == null) {
+ throw exception(WM_MATERIAL_STOCK_NOT_EXISTS);
+ }
+ Long codeId = pallet != null ? pallet.getId() : bag.getId();
+ MesWmProductSalesDetailDO existed = pallet != null
+ ? productSalesDetailMapper.selectBySalesIdAndPalletCodeId(reqVO.getSalesId(), codeId)
+ : productSalesDetailMapper.selectBySalesIdAndBagCodeId(reqVO.getSalesId(), codeId);
+ if (existed != null) {
+ return new MesWmProductSalesScanRespVO().setScanType(pallet != null ? "PALLET" : "BAG")
+ .setCodeId(codeId).setScanContent(reqVO.getScanContent()).setAlreadyScanned(true)
+ .setDetailId(existed.getId()).setOutboundQuantity(existed.getQuantity()).setMessage("璇ョ爜宸叉壂鎻�");
+ }
+ Long batchId = pallet != null ? pallet.getBatchId() : bag.getBatchId();
+ MesProBatchDO batch = wmBatchService.getBatch(batchId);
+ if (batch == null) {
+ throw exception(WM_TRANSACTION_BATCH_NOT_EXISTS);
+ }
+ List<MesWmProductSalesLineDO> lines = productSalesLineService.getProductSalesLineListBySalesId(reqVO.getSalesId());
+ MesWmProductSalesLineDO line = lines.stream().filter(item -> ObjUtil.equal(item.getItemId(), batch.getItemId())).findFirst().orElse(null);
+ if (line == null) {
+ throw exception(WM_PRODUCT_SALES_DETAIL_ITEM_MISMATCH);
+ }
+ BigDecimal quantity = reqVO.getQuantity();
+ if (quantity == null) {
+ quantity = line.getQuantity();
+ }
+ List<MesWmMaterialStockDO> stocks = materialStockService.getMaterialStockListByItemAndBatch(batch.getItemId(), batchId);
+ BigDecimal outboundQuantity = quantity;
+ MesWmMaterialStockDO stock = stocks.stream().filter(item -> item.getQuantity().compareTo(outboundQuantity) >= 0).findFirst().orElse(null);
+ if (stock == null) {
+ throw exception(WM_MATERIAL_STOCK_INSUFFICIENT);
+ }
+ BigDecimal scanned = productSalesDetailService.getProductSalesDetailListByLineId(line.getId()).stream()
+ .map(MesWmProductSalesDetailDO::getQuantity).reduce(BigDecimal.ZERO, BigDecimal::add);
+ if (scanned.add(quantity).compareTo(line.getQuantity()) > 0) {
+ throw exception(WM_PRODUCT_SALES_LINE_QUANTITY_INVALID);
+ }
+ MesWmProductSalesDetailDO detail = new MesWmProductSalesDetailDO().setLineId(line.getId())
+ .setSalesId(reqVO.getSalesId()).setItemId(batch.getItemId()).setQuantity(quantity)
+ .setMaterialStockId(stock.getId()).setBatchId(batchId).setBatchCode(batch.getCode())
+ .setWarehouseId(stock.getWarehouseId()).setLocationId(stock.getLocationId()).setAreaId(stock.getAreaId())
+ .setBagCodeId(pallet == null ? codeId : null).setPalletCodeId(pallet != null ? codeId : null);
+ productSalesDetailMapper.insert(detail);
+ return new MesWmProductSalesScanRespVO().setScanType(pallet != null ? "PALLET" : "BAG")
+ .setCodeId(codeId).setScanContent(reqVO.getScanContent()).setItemId(batch.getItemId())
+ .setBatchId(batchId).setBatchCode(batch.getCode()).setMaterialStockId(stock.getId())
+ .setAvailableQuantity(stock.getQuantity()).setScanQuantity(quantity).setOutboundQuantity(quantity)
+ .setAlreadyScanned(false).setDetailId(detail.getId()).setWarehouseId(stock.getWarehouseId())
+ .setLocationId(stock.getLocationId()).setAreaId(stock.getAreaId()).setMessage("鎵爜鎴愬姛");
}
@Override
@@ -213,6 +319,12 @@
// 鎵ц鎷h揣锛堝緟鎷h揣 鈫� 寰呭~鍐欒繍鍗曪級
productSalesMapper.updateById(new MesWmProductSalesDO()
.setId(id).setStatus(MesWmProductSalesStatusEnum.SHIPPING.getStatus()));
+
+ // 鍥炲啓 ERP 閿�鍞鍗曞嚭搴撶姸鎬佷负"閮ㄥ垎鍑哄簱"锛堣〃绀哄凡鎷h揣/鍗犵敤锛�
+ if (sales.getSaleOrderId() != null) {
+ saleOrderApi.updateOrderOutStatusTo(sales.getSaleOrderId(),
+ ErpSaleOrderOutStatusEnum.PART_OUT.getStatus());
+ }
}
@Override
@@ -251,13 +363,30 @@
productSalesMapper.updateById(new MesWmProductSalesDO()
.setId(id).setStatus(MesWmProductSalesStatusEnum.FINISHED.getStatus()));
- // 5. 鏇存柊鍏宠仈鐨勫彂璐ч�氱煡鍗曠姸鎬�
+ // 5. 鏇存柊鍏宠仈鐨勫彂璐ч�氱煡鍗曞嚭搴撴暟閲忓拰鐘舵��
if (sales.getNoticeId() != null) {
- salesNoticeService.finishSalesNotice(sales.getNoticeId());
+ updateSalesNoticeOutCount(sales);
}
// 6. 鍥炲啓 ERP 閿�鍞鍗曠殑鍑哄簱鏁伴噺鍜岀姸鎬�
updateErpSaleOrderOutCount(sales);
+ }
+
+ /**
+ * 鏇存柊鍙戣揣閫氱煡鍗曠殑鍑哄簱鏁伴噺鍜岀姸鎬�
+ */
+ private void updateSalesNoticeOutCount(MesWmProductSalesDO sales) {
+ // 鑾峰彇鍑哄簱鍗曡
+ List<MesWmProductSalesLineDO> lines = productSalesLineService.getProductSalesLineListBySalesId(sales.getId());
+ if (CollUtil.isEmpty(lines)) {
+ return;
+ }
+ // 閬嶅巻琛岋紝鎸� noticeLineId 鏇存柊鍑哄簱鏁伴噺
+ for (MesWmProductSalesLineDO line : lines) {
+ if (line.getNoticeLineId() != null && line.getQuantity() != null) {
+ salesNoticeService.updateSalesNoticeLineOutCount(line.getNoticeLineId(), line.getQuantity());
+ }
+ }
}
/**
@@ -299,7 +428,8 @@
.setBatchId(detail.getBatchId()).setBatchCode(detail.getBatchCode())
.setWarehouseId(detail.getWarehouseId()).setLocationId(detail.getLocationId()).setAreaId(detail.getAreaId())
.setBizType(MesBizTypeConstants.WM_PRODUCT_SALES).setBizId(sales.getId())
- .setBizCode(sales.getCode()).setBizLineId(detail.getLineId())));
+ .setBizCode(sales.getCode()).setBizLineId(detail.getLineId())
+ .setMaterialStockId(detail.getMaterialStockId()))); // 鎸囧畾搴撳瓨璁板綍ID锛岄伩鍏嶉噸鏂板尮閰�
}
@Override
@@ -341,7 +471,7 @@
// 鏍¢獙缂栫爜鍞竴
validateCodeUnique(id, reqVO.getCode());
// 鏍¢獙瀹㈡埛瀛樺湪
- clientService.validateClientExistsAndEnable(reqVO.getClientId());
+ customerApi.validateCustomer(reqVO.getClientId());
// 鏍¢獙鍙戣揣閫氱煡鍗�
if (reqVO.getNoticeId() != null) {
MesWmSalesNoticeDO notice = salesNoticeService.validateSalesNoticeExists(reqVO.getNoticeId());
--
Gitblit v1.9.3