云
2 天以前 704265308b1227dbada5a36b2027b588c062ded2
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productsales/MesWmProductSalesServiceImpl.java
@@ -27,6 +27,7 @@
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;
@@ -43,8 +44,6 @@
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;
@@ -53,7 +52,13 @@
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;
@@ -104,9 +109,10 @@
    @Resource
    private MesTraceIntegrityService integrityService;
    @Resource
    private ErpSaleOrderApi saleOrderApi;
    @Resource private MesProBagCodeMapper pickingBagMapper;
    @Resource private MesProPalletMapper pickingPalletMapper;
    @Resource private MesProBatchMapper pickingBatchMapper;
    @Resource private MesWmMaterialStockBagMapper pickingStockBagMapper;
    @Override
    public Long createProductSales(MesWmProductSalesSaveReqVO createReqVO) {
@@ -226,73 +232,150 @@
        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("不支持的拣货扫码类型");
        }
        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 "已加入出库单拣货";
        return null;
    }
    @Override
    public MesWmProductSalesScanRespVO previewScan(String code) { return previewScan(code, null, null); }
    /** 只读识别,并返回每袋的可选状态,不创建库存批次或拣货记录。 */
    @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 = validateProductSalesExists(reqVO.getSalesId());
        if (ObjUtil.notEqual(MesWmProductSalesStatusEnum.APPROVING.getStatus(), sales.getStatus())) {
            throw exception(WM_PRODUCT_SALES_CANNOT_PICK);
        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("拣货数量必须与所选袋码数量一致");
        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);
        }
        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);
        }
        // 未绑定生产批次的临时托盘不允许参与销售出库
        if (pallet != null && pallet.getBatchId() == null) {
            throw exception(PRO_PALLET_NOT_BIND_BATCH);
        }
        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 proBatchId = pallet != null ? pallet.getBatchId() : bag.getBatchId();
        MesProBatchDO batch = proBatchService.getBatch(proBatchId);
        if (batch == null) {
            throw exception(WM_TRANSACTION_BATCH_NOT_EXISTS);
        }
        // 生产批次桥接仓库批次:扫码入库写入的是仓库批次(mes_wm_batch),此处必须用仓库批次 id 查库存
        MesWmBatchDO wmBatch = mesWmBatchService.getOrCreateByProBatch(batch);
        if (wmBatch == 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(), wmBatch.getId());
        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);
        }
        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 (scanned.add(quantity).compareTo(line.getQuantity()) > 0) {
            throw exception(WM_PRODUCT_SALES_LINE_QUANTITY_INVALID);
        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);
        }
        MesWmProductSalesDetailDO detail = new MesWmProductSalesDetailDO().setLineId(line.getId())
                .setSalesId(reqVO.getSalesId()).setItemId(batch.getItemId()).setQuantity(quantity)
                .setMaterialStockId(stock.getId()).setBatchId(wmBatch.getId()).setBatchCode(wmBatch.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(wmBatch.getId()).setBatchCode(wmBatch.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("扫码成功");
        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
@@ -338,12 +421,6 @@
        // 执行拣货(待拣货 → 待填写运单)
        productSalesMapper.updateById(new MesWmProductSalesDO()
                .setId(id).setStatus(MesWmProductSalesStatusEnum.SHIPPING.getStatus()));
        // 回写 ERP 销售订单出库状态为"部分出库"(表示已拣货/占用)
        if (sales.getSaleOrderId() != null) {
            saleOrderApi.updateOrderOutStatusTo(sales.getSaleOrderId(),
                    ErpSaleOrderOutStatusEnum.PART_OUT.getStatus());
        }
    }
    @Override
@@ -392,16 +469,14 @@
            updateSalesNoticeOutCount(sales);
        }
        // 6. 回写 ERP 销售订单的出库数量和状态
        updateErpSaleOrderOutCount(sales);
        // 7. 回写袋码/托盘出库去向(供窜货稽查定位经销商地区),并生成出库完整性摘要
        // 6. 回写袋码/托盘出库去向(供窜货稽查定位经销商地区),并生成出库完整性摘要
        List<MesWmProductSalesDetailDO> outDetails = productSalesDetailService.getProductSalesDetailListBySalesId(id);
        for (MesWmProductSalesDetailDO detail : outDetails) {
            if (detail.getBagCodeId() != null) {
                bagCodeService.markOutbound(detail.getBagCodeId(), id);
            }
            if (detail.getPalletCodeId() != null) {
            // 部分托盘拣货可能分给多个经销商,不能把整个托盘标记为本单出库。
            if (detail.getPalletCodeId() != null && detail.getBagCodeId() == null) {
                palletService.markOutbound(detail.getPalletCodeId(), id);
            }
        }
@@ -425,43 +500,13 @@
        }
    }
    /**
     * 回写 ERP 销售订单的出库数量和状态
     */
    private void updateErpSaleOrderOutCount(MesWmProductSalesDO sales) {
        // 获取出库单行
        List<MesWmProductSalesLineDO> lines = productSalesLineService.getProductSalesLineListBySalesId(sales.getId());
        if (CollUtil.isEmpty(lines)) {
            return;
        }
        // 用于存储销售订单 ID,避免重复更新
        Long saleOrderId = null;
        // 遍历行,按 saleOrderItemId 更新出库数量
        for (MesWmProductSalesLineDO line : lines) {
            if (line.getSaleOrderItemId() != null && line.getQuantity() != null) {
                saleOrderApi.updateOrderItemOutCount(line.getSaleOrderItemId(), line.getQuantity());
                // 获取销售订单 ID(从第一个有效的明细项获取)
                if (saleOrderId == null) {
                    cn.iocoder.yudao.module.erp.api.sale.dto.ErpSaleOrderItemRespDTO itemDTO =
                            saleOrderApi.getSaleOrderItem(line.getSaleOrderItemId()).getCheckedData();
                    if (itemDTO != null && itemDTO.getOrderId() != null) {
                        saleOrderId = itemDTO.getOrderId();
                    }
                }
            }
        }
        // 更新销售订单的出库状态
        if (saleOrderId != null) {
            saleOrderApi.updateOrderOutStatus(saleOrderId);
        }
    }
    private void createTransactionList(MesWmProductSalesDO sales) {
        List<MesWmProductSalesDetailDO> details = productSalesDetailService.getProductSalesDetailListBySalesId(sales.getId());
        wmTransactionService.createTransactionList(convertList(details, detail -> new MesWmTransactionSaveReqDTO()
                .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())