gaoluyang
9 天以前 7bd10d34e8a8dc2437ec8551eb511e37634d7306
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productreceipt/MesWmProductReceiptServiceImpl.java
@@ -2,6 +2,7 @@
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
@@ -19,6 +20,8 @@
import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants;
import cn.iocoder.yudao.module.mes.enums.wm.MesWmProductReceiptStatusEnum;
import cn.iocoder.yudao.module.mes.enums.wm.MesWmTransactionTypeEnum;
import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO;
import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService;
import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderService;
import cn.iocoder.yudao.module.mes.service.wm.transaction.MesWmTransactionService;
import cn.iocoder.yudao.module.mes.service.wm.transaction.dto.MesWmTransactionSaveReqDTO;
@@ -32,6 +35,9 @@
import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*;
@@ -54,6 +60,8 @@
    private MesWmTransactionService wmTransactionService;
    @Resource
    private MesProWorkOrderService workOrderService;
    @Resource
    private MesMdItemService itemService;
    @Resource
    private MesWmWarehouseService warehouseService;
    @Resource
@@ -139,11 +147,81 @@
            throw exception(WM_PRODUCT_RECPT_STATUS_ERROR);
        }
        // 校验上架明细已覆盖每一行收货数量:状态一旦推进到「待执行入库」,明细就不能再编辑,
        // 若此时明细为空或数量不足,单据将永远无法执行入库(会被卡死在待执行入库)
        validateProductReceiptStockDetail(receipt);
        // 执行上架(待上架 → 待入库)
        productReceiptMapper.updateById(new MesWmProductReceiptDO()
                .setId(id).setStatus(MesWmProductReceiptStatusEnum.APPROVED.getStatus()));
    }
    /**
     * 校验执行上架前的上架明细:每一行的收货数量都必须被该行的上架明细合计覆盖
     *
     * 提示按「具体行 + 双方数量 + 下一步动作」组织,避免只报一句笼统的"数量不匹配"
     */
    private void validateProductReceiptStockDetail(MesWmProductReceiptDO receipt) {
        List<MesWmProductReceiptLineDO> lines = productReceiptLineService.getProductReceiptLineListByRecptId(receipt.getId());
        if (CollUtil.isEmpty(lines)) {
            throw new ServiceException(WM_PRODUCT_RECPT_NO_LINE.getCode(),
                    String.format("收货单[%s]没有收货行,无法执行上架:请先在单据中添加收货行(物料与收货数量)后再执行上架。",
                            receipt.getCode()));
        }
        Map<Long, MesMdItemDO> itemMap = itemService.getItemMap(lines.stream()
                .map(MesWmProductReceiptLineDO::getItemId).filter(Objects::nonNull).collect(Collectors.toSet()));
        for (int i = 0; i < lines.size(); i++) {
            MesWmProductReceiptLineDO line = lines.get(i);
            String lineLabel = String.format("第 %d 行[%s]", i + 1, formatItemLabel(line.getItemId(), itemMap));
            if (line.getBatchCode() != null) {
                lineLabel += String.format("(批次[%s])", line.getBatchCode());
            }
            BigDecimal lineQuantity = line.getQuantity() == null ? BigDecimal.ZERO : line.getQuantity();
            List<MesWmProductReceiptDetailDO> details =
                    productReceiptDetailService.getProductReceiptDetailListByLineId(line.getId());
            // 明细为空时直接提示"去点上架",比提示"合计 0 与 20 不一致"更明确
            if (CollUtil.isEmpty(details)) {
                throw new ServiceException(WM_PRODUCT_RECPT_NO_DETAIL.getCode(),
                        String.format("收货单[%s]%s还没有上架明细(收货数量[%s]):请先在该行点「上架」,填写入库仓库与上架数量,"
                                + "合计填满 %s 后再执行上架。", receipt.getCode(), lineLabel,
                                formatQuantity(lineQuantity), formatQuantity(lineQuantity)));
            }
            BigDecimal detailQuantity = CollectionUtils.getSumValue(details,
                    MesWmProductReceiptDetailDO::getQuantity, BigDecimal::add, BigDecimal.ZERO);
            if (detailQuantity.compareTo(lineQuantity) != 0) {
                BigDecimal diff = lineQuantity.subtract(detailQuantity);
                String action = diff.signum() > 0
                        ? String.format("还差[%s]:请在该行点「上架」补登缺少的上架明细", formatQuantity(diff))
                        : String.format("多出[%s]:请删除或调整该行多余的上架明细", formatQuantity(diff.negate()));
                throw new ServiceException(WM_PRODUCT_RECPT_DETAIL_QUANTITY_MISMATCH.getCode(),
                        String.format("收货单[%s]%s上架明细合计[%s]与收货数量[%s]不一致,%s,调整一致后再执行上架。",
                                receipt.getCode(), lineLabel, formatQuantity(detailQuantity),
                                formatQuantity(lineQuantity), action));
            }
        }
    }
    /**
     * 数量显示:去掉数据库 scale 带出的多余 0(20.000000 → 20)
     */
    private String formatQuantity(BigDecimal quantity) {
        if (quantity == null) {
            return "0";
        }
        return quantity.stripTrailingZeros().toPlainString();
    }
    /**
     * 物料显示名:优先「名称/编码」,查不到时回退为 ID,避免把裸 ID 直接抛给用户
     */
    private String formatItemLabel(Long itemId, Map<Long, MesMdItemDO> itemMap) {
        MesMdItemDO item = itemId == null ? null : itemMap.get(itemId);
        if (item == null) {
            return itemId == null ? "物料[未选择]" : String.format("物料[ID=%s]", itemId);
        }
        return String.format("%s/%s", item.getName(), item.getCode());
    }
    @Override
    public Boolean checkProductReceiptQuantity(Long id) {
        List<MesWmProductReceiptLineDO> lines = productReceiptLineService.getProductReceiptLineListByRecptId(id);