云
2 天以前 704265308b1227dbada5a36b2027b588c062ded2
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productsales/MesWmProductSalesServiceImpl.java
@@ -6,9 +6,20 @@
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;
@@ -16,20 +27,23 @@
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.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 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;
@@ -38,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;
@@ -47,7 +67,7 @@
/**
 * MES 销售出库单 Service 实现类
 *
 * @author 芋道源码
 * @author 超级管理员
 */
@Service
@Validated
@@ -58,6 +78,16 @@
    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;
@@ -78,7 +108,11 @@
    private MesQcOqcService oqcService;
    @Resource
    private ErpSaleOrderApi saleOrderApi;
    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) {
@@ -198,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("不支持的拣货扫码类型");
        }
        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 = 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);
        }
        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) {
@@ -241,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
@@ -281,17 +455,32 @@
        // 3. 遍历所有明细,创建库存事务(扣减库存 + 记录流水)
        createTransactionList(sales);
        // 4. 更新出库单状态
        // 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. 回写 ERP 销售订单的出库数量和状态
        updateErpSaleOrderOutCount(sales);
        // 6. 回写袋码/托盘出库去向(供窜货稽查定位经销商地区),并生成出库完整性摘要
        List<MesWmProductSalesDetailDO> outDetails = productSalesDetailService.getProductSalesDetailListBySalesId(id);
        for (MesWmProductSalesDetailDO detail : outDetails) {
            if (detail.getBagCodeId() != null) {
                bagCodeService.markOutbound(detail.getBagCodeId(), id);
            }
            // 部分托盘拣货可能分给多个经销商,不能把整个托盘标记为本单出库。
            if (detail.getPalletCodeId() != null && detail.getBagCodeId() == null) {
                palletService.markOutbound(detail.getPalletCodeId(), id);
            }
        }
        integrityService.recordSalesIntegrity(id);
    }
    /**
@@ -311,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())