From 704265308b1227dbada5a36b2027b588c062ded2 Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期一, 12 十月 2026 09:14:37 +0800
Subject: [PATCH] feat(mes): 新增业务码支持并优化追溯查询功能
---
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productsales/MesWmProductSalesServiceImpl.java | 295 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
1 files changed, 284 insertions(+), 11 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 a1cedf7..b72e8f0 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
@@ -6,31 +6,59 @@
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
+import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
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.batch.MesWmBatchDO;
+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.pro.MesProBagCodeQcStatusEnum;
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.crm.api.customer.dto.CrmCustomerRespDTO;
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.trace.integrity.MesTraceIntegrityService;
+import cn.iocoder.yudao.module.mes.service.wm.batch.MesWmBatchService;
+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 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;
import java.math.BigDecimal;
-import java.util.List;
+import java.util.*;
+import java.util.stream.Collectors;
+import cn.iocoder.yudao.module.mes.dal.mysql.pro.bagcode.MesProBagCodeMapper;
+import cn.iocoder.yudao.module.mes.dal.mysql.pro.pallet.MesProPalletMapper;
+import cn.iocoder.yudao.module.mes.dal.mysql.pro.batch.MesProBatchMapper;
+import cn.iocoder.yudao.module.mes.dal.mysql.wm.materialstockbag.MesWmMaterialStockBagMapper;
+import cn.iocoder.yudao.module.mes.dal.dataobject.wm.materialstockbag.MesWmMaterialStockBagDO;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
@@ -39,27 +67,52 @@
/**
* 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 proBatchService;
+ @Resource
+ private MesWmBatchService mesWmBatchService;
@Resource
private MesWmProductSalesLineService productSalesLineService;
@Resource
private MesWmProductSalesDetailService productSalesDetailService;
@Resource
- private MesMdClientService clientService;
+ private CrmCustomerApi customerApi;
@Resource
private MesWmSalesNoticeService salesNoticeService;
@Resource
private MesWmTransactionService wmTransactionService;
@Resource
private MesWmMaterialStockService materialStockService;
+ @Resource
+ private MesWmStockReserveService stockReserveService;
+ @Resource
+ @Lazy
+ private MesQcOqcService oqcService;
+
+ @Resource
+ private MesTraceIntegrityService integrityService;
+ @Resource private MesProBagCodeMapper pickingBagMapper;
+ @Resource private MesProPalletMapper pickingPalletMapper;
+ @Resource private MesProBatchMapper pickingBatchMapper;
+ @Resource private MesWmMaterialStockBagMapper pickingStockBagMapper;
@Override
public Long createProductSales(MesWmProductSalesSaveReqVO createReqVO) {
@@ -105,7 +158,8 @@
@Override
public PageResult<MesWmProductSalesDO> getProductSalesPage(MesWmProductSalesPageReqVO pageReqVO) {
- return productSalesMapper.selectPage(pageReqVO);
+ List<Long> permittedClientIds = customerApi.getPermittedCustomerIds();
+ return productSalesMapper.selectPage(pageReqVO, permittedClientIds);
}
@Override
@@ -125,10 +179,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())),
@@ -162,6 +232,152 @@
return true;
}
+ private record PickingScope(String type, Long id, MesProBatchDO batch, List<MesProBagCodeDO> bags) { }
+
+ private PickingScope pickingScope(String code, String type, boolean lock) {
+ if (type == null || type.isBlank()) {
+ type = palletService.getPalletByScanCode(code) != null ? "PALLET"
+ : bagCodeService.getBagCodeByScanCode(code) != null ? "BAG" : "BATCH";
+ }
+ MesProBatchDO batch;
+ List<MesProBagCodeDO> bags;
+ Long id;
+ switch (type) {
+ case "BAG" -> {
+ MesProBagCodeDO bag = lock ? pickingBagMapper.selectByScanCodeForUpdate(code) : bagCodeService.getBagCodeByScanCode(code);
+ if (bag == null || Integer.valueOf(30).equals(bag.getStatus())) throw new IllegalArgumentException("琚嬬爜涓嶅瓨鍦ㄦ垨宸蹭綔搴�");
+ id = bag.getId(); batch = proBatchService.getBatch(bag.getBatchId()); bags = List.of(bag);
+ }
+ case "PALLET" -> {
+ MesProPalletDO pallet = lock ? pickingPalletMapper.selectByScanCodeForUpdate(code) : palletService.getPalletByScanCode(code);
+ if (pallet == null || Integer.valueOf(20).equals(pallet.getStatus())) throw new IllegalArgumentException("鎵樼洏涓嶅瓨鍦ㄦ垨宸蹭綔搴�");
+ if (pallet.getBatchId() == null) throw exception(PRO_PALLET_NOT_BIND_BATCH);
+ id = pallet.getId(); batch = proBatchService.getBatch(pallet.getBatchId());
+ bags = lock ? pickingBagMapper.selectListByPalletIdForUpdate(id) : bagCodeService.getBagCodeListByPalletId(id);
+ }
+ case "BATCH" -> {
+ batch = lock ? pickingBatchMapper.selectByScanCodeForUpdate(code) : proBatchService.getBatchByScanCode(code);
+ if (batch == null) throw new IllegalArgumentException("鐢熶骇鎵规鐮佷笉瀛樺湪");
+ id = batch.getId(); bags = lock ? pickingBagMapper.selectListByBatchIdForUpdate(id) : bagCodeService.getBagCodeListByBatchId(id);
+ }
+ default -> throw new IllegalArgumentException("涓嶆敮鎸佺殑鎷h揣鎵爜绫诲瀷");
+ }
+ if (batch == null || batch.getItemId() == null || Integer.valueOf(40).equals(batch.getStatus()))
+ throw new IllegalArgumentException("鏈粦瀹氭湁鏁堝搧绉嶆垨鐢熶骇鎵规宸蹭綔搴�");
+ return new PickingScope(type, id, batch, bags.stream().filter(b -> !Integer.valueOf(30).equals(b.getStatus()))
+ .sorted(Comparator.comparing(MesProBagCodeDO::getId)).toList());
+ }
+
+ private String unavailable(MesProBagCodeDO bag, MesWmMaterialStockBagDO stockBag,
+ Map<Long, MesWmMaterialStockDO> stocks, List<MesWmProductSalesDetailDO> occupied, MesProBatchDO batch) {
+ if (!Objects.equals(bag.getBatchId(), batch.getId())) return "琚嬬爜鎵规褰掑睘寮傚父";
+ if (bag.getItemId() != null && !Objects.equals(bag.getItemId(), batch.getItemId())) return "琚嬬爜鍝佺褰掑睘寮傚父";
+ if (!MesProBagCodeQcStatusEnum.QUALIFIED.getStatus().equals(bag.getQcStatus())) return "灏氭湭璐ㄦ鍚堟牸";
+ if (stockBag == null || !Integer.valueOf(1).equals(stockBag.getStatus())) return "涓嶅湪搴撴垨宸插嚭搴�";
+ if (Boolean.TRUE.equals(stockBag.getFrozen())) return "琚嬬爜宸插喕缁�";
+ if (!Integer.valueOf(20).equals(stockBag.getStockState()) || !Objects.equals(stockBag.getItemId(), batch.getItemId())
+ || !stocks.containsKey(stockBag.getMaterialStockId()) || stockBag.getQuantity() == null || BigDecimal.ONE.compareTo(stockBag.getQuantity()) != 0)
+ return "琚嬬爜涓嶅湪鏈夋晥鍚堟牸搴撳瓨涓�";
+ if (occupied.stream().anyMatch(d -> Objects.equals(d.getBagCodeId(), bag.getId())
+ || d.getBagCodeId() == null && bag.getPalletId() != null && Objects.equals(d.getPalletCodeId(), bag.getPalletId())))
+ return "宸插姞鍏ュ嚭搴撳崟鎷h揣";
+ return null;
+ }
+
+ @Override
+ public MesWmProductSalesScanRespVO previewScan(String code) { return previewScan(code, null, null); }
+
+ /** 鍙璇嗗埆锛屽苟杩斿洖姣忚鐨勫彲閫夌姸鎬侊紝涓嶅垱寤哄簱瀛樻壒娆℃垨鎷h揣璁板綍銆� */
+ @Override
+ public MesWmProductSalesScanRespVO previewScan(String code, String scanType, Long salesId) {
+ PickingScope scope = pickingScope(code, scanType, false);
+ if (salesId != null) {
+ MesWmProductSalesDO sales = validateProductSalesExists(salesId);
+ if (!Integer.valueOf(2).equals(sales.getStatus())) throw exception(WM_PRODUCT_SALES_CANNOT_PICK);
+ if (productSalesLineService.getProductSalesLineListBySalesId(salesId).stream()
+ .noneMatch(l -> Objects.equals(l.getItemId(), scope.batch().getItemId()))) throw exception(WM_PRODUCT_SALES_DETAIL_ITEM_MISMATCH);
+ }
+ MesWmBatchDO wmBatch = mesWmBatchService.getBatchByCode(scope.batch().getCode());
+ Map<Long, MesWmMaterialStockDO> stocks = wmBatch == null ? Map.of() : materialStockService
+ .getQualifiedMaterialStockListByItemAndBatch(scope.batch().getItemId(), wmBatch.getId()).stream()
+ .collect(Collectors.toMap(MesWmMaterialStockDO::getId, x -> x));
+ Map<Long, MesWmMaterialStockBagDO> stockBags = pickingStockBagMapper.selectByBagIds(scope.bags().stream().map(MesProBagCodeDO::getId).toList(), false)
+ .stream().collect(Collectors.toMap(MesWmMaterialStockBagDO::getBagCodeId, x -> x));
+ List<MesWmProductSalesDetailDO> occupied = productSalesDetailMapper.selectPickingByBatchCode(scope.batch().getCode());
+ List<MesWmProductSalesScanRespVO.BagOption> options = scope.bags().stream().map(b -> {
+ String reason = unavailable(b, stockBags.get(b.getId()), stocks, occupied, scope.batch());
+ return new MesWmProductSalesScanRespVO.BagOption().setId(b.getId()).setCode(b.getCode()).setBusinessCode(b.getBusinessCode())
+ .setPalletId(b.getPalletId()).setSelectable(reason == null).setUnavailableReason(reason);
+ }).toList();
+ return new MesWmProductSalesScanRespVO().setScanType(scope.type()).setCodeId(scope.id()).setScanContent(code)
+ .setBatchCode(scope.batch().getCode()).setItemId(scope.batch().getItemId()).setBagCount(options.size()).setBags(options)
+ .setScanQuantity(BigDecimal.valueOf(options.stream().filter(o -> Boolean.TRUE.equals(o.getSelectable())).count()));
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public MesWmProductSalesScanRespVO scanProductSales(MesWmProductSalesScanReqVO reqVO) {
+ MesWmProductSalesDO sales = productSalesMapper.selectByIdForUpdate(reqVO.getSalesId());
+ if (sales == null) throw exception(WM_PRODUCT_SALES_NOT_EXISTS);
+ if (!Integer.valueOf(2).equals(sales.getStatus())) throw exception(WM_PRODUCT_SALES_CANNOT_PICK);
+ PickingScope scope = pickingScope(reqVO.getScanContent(), reqVO.getScanType(), true);
+ List<Long> ids = reqVO.getBagIds();
+ if (ids == null && "BAG".equals(scope.type())) ids = List.of(scope.id());
+ if (ids == null || ids.isEmpty() || new HashSet<>(ids).size() != ids.size())
+ throw new IllegalArgumentException("璇烽�夋嫨鍏蜂綋琚嬬爜锛屼笉鍏佽閲嶅琚嬬爜");
+ BigDecimal quantity = BigDecimal.valueOf(ids.size());
+ if (reqVO.getQuantity() != null && quantity.compareTo(reqVO.getQuantity()) != 0)
+ throw new IllegalArgumentException("鎷h揣鏁伴噺蹇呴』涓庢墍閫夎鐮佹暟閲忎竴鑷�");
+ Set<Long> selectedIds = new HashSet<>(ids);
+ List<MesProBagCodeDO> selected = scope.bags().stream().filter(b -> selectedIds.contains(b.getId())).toList();
+ if (selected.size() != ids.size()) throw new IllegalArgumentException("鎵�閫夎鐮佷笉灞炰簬褰撳墠鎵爜瀵硅薄鎴栧凡浣滃簾锛岃閲嶆柊鏍稿");
+ // 閿佸畾瀹為檯琚嬬爜鍜屽簱瀛樿鏄庣粏锛岃法鍑哄簱鍗曠殑鎵爜涔熶笉鑳介噸澶嶆嫞閫夊悓涓�琚嬨��
+ List<MesWmMaterialStockBagDO> stockBagRows = pickingStockBagMapper.selectByBagIds(ids, true);
+ Map<Long, MesWmMaterialStockBagDO> stockBags = stockBagRows.stream().collect(Collectors.toMap(MesWmMaterialStockBagDO::getBagCodeId, x -> x));
+ List<MesWmProductSalesDetailDO> occupied = productSalesDetailMapper.selectPickingByBatchCodeForUpdate(scope.batch().getCode());
+ List<MesWmProductSalesDetailDO> own = occupied.stream().filter(d -> Objects.equals(d.getSalesId(), sales.getId())).toList();
+ long already = selected.stream().filter(b -> own.stream().anyMatch(d -> Objects.equals(d.getBagCodeId(), b.getId()))).count();
+ if (already == selected.size()) return new MesWmProductSalesScanRespVO().setScanType(scope.type()).setCodeId(scope.id())
+ .setScanContent(reqVO.getScanContent()).setSelectedBagIds(ids).setAlreadyScanned(true).setOutboundQuantity(quantity)
+ .setDetailId(own.stream().filter(d -> selectedIds.contains(d.getBagCodeId())).findFirst().orElseThrow().getId()).setMessage("鎵�閫夎鐮佸凡鍦ㄦ湰鍗曟嫞璐ф槑缁嗕腑");
+ if (already > 0) throw new IllegalArgumentException("鎵�閫夎鐮侀儴鍒嗗凡鍔犲叆鏈崟锛岃閲嶆柊閫夋嫨鏈嫞琚嬬爜");
+ MesWmBatchDO wmBatch = mesWmBatchService.getBatchByCode(scope.batch().getCode());
+ if (wmBatch == null) throw exception(WM_TRANSACTION_BATCH_NOT_EXISTS);
+ Map<Long, MesWmMaterialStockDO> stocks = materialStockService.getQualifiedMaterialStockListByItemAndBatch(scope.batch().getItemId(), wmBatch.getId())
+ .stream().collect(Collectors.toMap(MesWmMaterialStockDO::getId, x -> x));
+ for (MesProBagCodeDO bag : selected) {
+ String reason = unavailable(bag, stockBags.get(bag.getId()), stocks, occupied, scope.batch());
+ if (reason != null) throw new IllegalArgumentException("琚嬬爜 " + bag.getCode() + "锛�" + reason);
+ }
+ MesWmProductSalesLineDO line = productSalesLineService.getProductSalesLineListBySalesId(sales.getId()).stream()
+ .filter(l -> Objects.equals(l.getItemId(), scope.batch().getItemId())).findFirst().orElse(null);
+ if (line == null) throw exception(WM_PRODUCT_SALES_DETAIL_ITEM_MISMATCH);
+ BigDecimal scanned = productSalesDetailService.getProductSalesDetailListByLineId(line.getId()).stream()
+ .map(MesWmProductSalesDetailDO::getQuantity).reduce(BigDecimal.ZERO, BigDecimal::add);
+ if (line.getQuantity() == null || scanned.add(quantity).compareTo(line.getQuantity()) > 0) throw exception(WM_PRODUCT_SALES_LINE_QUANTITY_INVALID);
+ Map<Long, Long> byStock = selected.stream().collect(Collectors.groupingBy(b -> stockBags.get(b.getId()).getMaterialStockId(), Collectors.counting()));
+ for (var allocation : byStock.entrySet()) {
+ BigDecimal ownStockQty = own.stream().filter(d -> Objects.equals(d.getMaterialStockId(), allocation.getKey()))
+ .map(MesWmProductSalesDetailDO::getQuantity).reduce(BigDecimal.ZERO, BigDecimal::add);
+ if (stocks.get(allocation.getKey()).getQuantity().compareTo(ownStockQty.add(BigDecimal.valueOf(allocation.getValue()))) < 0)
+ throw exception(WM_MATERIAL_STOCK_INSUFFICIENT);
+ }
+ Long firstDetail = null;
+ for (MesProBagCodeDO bag : selected) {
+ MesWmMaterialStockDO stock = stocks.get(stockBags.get(bag.getId()).getMaterialStockId());
+ MesWmProductSalesDetailDO detail = new MesWmProductSalesDetailDO().setSalesId(sales.getId()).setLineId(line.getId())
+ .setItemId(scope.batch().getItemId()).setQuantity(BigDecimal.ONE).setBagCodeId(bag.getId())
+ .setPalletCodeId(bag.getPalletId()).setBatchId(wmBatch.getId()).setBatchCode(wmBatch.getCode()).setMaterialStockId(stock.getId())
+ .setWarehouseId(stock.getWarehouseId()).setLocationId(stock.getLocationId()).setAreaId(stock.getAreaId())
+ .setRemark("鎵爜绫诲瀷 " + scope.type() + "锛涙潵婧愮爜 " + reqVO.getScanContent() + "锛涜鐮� " + bag.getCode());
+ productSalesDetailMapper.insert(detail);
+ if (firstDetail == null) firstDetail = detail.getId();
+ }
+ return new MesWmProductSalesScanRespVO().setScanType(scope.type()).setCodeId(scope.id()).setScanContent(reqVO.getScanContent())
+ .setItemId(scope.batch().getItemId()).setBatchCode(wmBatch.getCode()).setSelectedBagIds(ids)
+ .setScanQuantity(quantity).setOutboundQuantity(quantity).setAlreadyScanned(false).setDetailId(firstDetail).setMessage("宸叉寜鎵�閫夎鐮佸姞鍏ユ嫞璐�");
+ }
+
@Override
@Transactional(rollbackFor = Exception.class)
public void stockProductSales(Long id) {
@@ -177,7 +393,7 @@
if (CollUtil.isEmpty(details)) {
throw exception(WM_PRODUCT_SALES_DETAILS_EMPTY);
}
- // 鏍¢獙搴撳瓨鍏呰冻
+ // 鏍¢獙搴撳瓨鍏呰冻骞跺垱寤哄崰鐢�
for (MesWmProductSalesDetailDO detail : details) {
materialStockService.validateSelectedStock(
detail.getMaterialStockId(),
@@ -187,6 +403,16 @@
detail.getWarehouseId(),
detail.getLocationId(),
detail.getAreaId(),
+ detail.getQuantity()
+ );
+ // 鍒涘缓搴撳瓨鍗犵敤
+ stockReserveService.createReserve(
+ detail.getMaterialStockId(),
+ MesWmStockReserveBizTypeEnum.PRODUCT_SALES.getType(),
+ id,
+ sales.getCode(),
+ detail.getId(),
+ detail.getItemId(),
detail.getQuantity()
);
}
@@ -223,12 +449,55 @@
throw exception(WM_PRODUCT_SALES_CANNOT_FINISH);
}
- // 2. 閬嶅巻鎵�鏈夋槑缁嗭紝鍒涘缓搴撳瓨浜嬪姟锛堟墸鍑忓簱瀛� + 璁板綍娴佹按锛�
+ // 2. 纭鍗犵敤杞嚭搴擄紙閲婃斁鍗犵敤閲忥級
+ stockReserveService.confirmReserveToOutbound(MesWmStockReserveBizTypeEnum.PRODUCT_SALES.getType(), id);
+
+ // 3. 閬嶅巻鎵�鏈夋槑缁嗭紝鍒涘缓搴撳瓨浜嬪姟锛堟墸鍑忓簱瀛� + 璁板綍娴佹按锛�
createTransactionList(sales);
- // 3. 鏇存柊鍑哄簱鍗曠姸鎬�
+ // 4. 鍥哄寲缁忛攢鍟嗕笌瀹為檯鍑哄簱鎿嶄綔淇℃伅
+ CrmCustomerRespDTO customer = customerApi.getCustomer(sales.getClientId());
productSalesMapper.updateById(new MesWmProductSalesDO()
- .setId(id).setStatus(MesWmProductSalesStatusEnum.FINISHED.getStatus()));
+ .setId(id)
+ .setClientName(customer != null ? customer.getName() : null)
+ .setOutboundTime(java.time.LocalDateTime.now())
+ .setOutboundUserId(SecurityFrameworkUtils.getLoginUserId())
+ .setStatus(MesWmProductSalesStatusEnum.FINISHED.getStatus()));
+
+ // 5. 鏇存柊鍏宠仈鐨勫彂璐ч�氱煡鍗曞嚭搴撴暟閲忓拰鐘舵��
+ if (sales.getNoticeId() != null) {
+ updateSalesNoticeOutCount(sales);
+ }
+
+ // 6. 鍥炲啓琚嬬爜/鎵樼洏鍑哄簱鍘诲悜锛堜緵绐滆揣绋芥煡瀹氫綅缁忛攢鍟嗗湴鍖猴級锛屽苟鐢熸垚鍑哄簱瀹屾暣鎬ф憳瑕�
+ List<MesWmProductSalesDetailDO> outDetails = productSalesDetailService.getProductSalesDetailListBySalesId(id);
+ for (MesWmProductSalesDetailDO detail : outDetails) {
+ if (detail.getBagCodeId() != null) {
+ bagCodeService.markOutbound(detail.getBagCodeId(), id);
+ }
+ // 閮ㄥ垎鎵樼洏鎷h揣鍙兘鍒嗙粰澶氫釜缁忛攢鍟嗭紝涓嶈兘鎶婃暣涓墭鐩樻爣璁颁负鏈崟鍑哄簱銆�
+ if (detail.getPalletCodeId() != null && detail.getBagCodeId() == null) {
+ palletService.markOutbound(detail.getPalletCodeId(), id);
+ }
+ }
+ integrityService.recordSalesIntegrity(id);
+ }
+
+ /**
+ * 鏇存柊鍙戣揣閫氱煡鍗曠殑鍑哄簱鏁伴噺鍜岀姸鎬�
+ */
+ 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());
+ }
+ }
}
private void createTransactionList(MesWmProductSalesDO sales) {
@@ -237,9 +506,11 @@
.setType(MesWmTransactionTypeEnum.OUT.getType()).setItemId(detail.getItemId())
.setQuantity(detail.getQuantity().negate()) // 鍑哄簱鏁伴噺涓鸿礋鏁�
.setBatchId(detail.getBatchId()).setBatchCode(detail.getBatchCode())
+ .setBagCodeId(detail.getBagCodeId()) // 鏂版壂鐮佹槑缁嗛�愯鍏宠仈锛屾墽琛屽嚭搴撻�愯鏇存柊搴撳瓨鐘舵��
.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
@@ -253,6 +524,8 @@
MesWmProductSalesStatusEnum.CANCELED.getStatus())) {
throw exception(WM_PRODUCT_SALES_CANNOT_CANCEL);
}
+ // 閲婃斁搴撳瓨鍗犵敤
+ stockReserveService.releaseReserve(MesWmStockReserveBizTypeEnum.PRODUCT_SALES.getType(), id);
// 鍙栨秷
productSalesMapper.updateById(new MesWmProductSalesDO()
.setId(id).setStatus(MesWmProductSalesStatusEnum.CANCELED.getStatus()));
@@ -279,7 +552,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