chen.lin
17 小时以前 79ce234f181a8fa5e3e02938520395243c82a5da
拣货过程中的出库库存匹配+空板
1个文件已修改
281 ■■■■■ 已修改文件
rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/OutStockServiceImpl.java 281 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/OutStockServiceImpl.java
@@ -54,6 +54,16 @@
    /** 出库剩余量容差:小于等于此值视为已分配完,避免浮点误差产生多余“库存不足”行 */
    private static final BigDecimal ISSUED_TOLERANCE = new BigDecimal("0.000001");
    /** 数量精度(小数位数),统一用 BigDecimal 计算后按此精度舍入再写回 Double 字段 */
    private static final int QTY_SCALE = 6;
    /** 将 Map/实体中的数量转为 BigDecimal,避免 double 精度问题 */
    private static BigDecimal toBigDecimal(Object o) {
        if (o == null) return BigDecimal.ZERO;
        if (o instanceof BigDecimal) return (BigDecimal) o;
        if (o instanceof Number) return new BigDecimal(o.toString());
        try { return new BigDecimal(o.toString()); } catch (NumberFormatException e) { return BigDecimal.ZERO; }
    }
    @Autowired
    private AsnOrderItemService asnOrderItemService;
@@ -88,6 +98,8 @@
    @Autowired
    private TaskItemService taskItemService;
    @Autowired
    private TaskService taskService;
    @Autowired
    private LocItemMapper locItemMapper;
    @Override
@@ -115,12 +127,12 @@
                continue;
            }
            for (Map<String, Object> row : statusRows) {
                double v = row.get("stockQty") instanceof Number ? ((Number) row.get("stockQty")).doubleValue() : 0d;
                BigDecimal stockQtyVal = toBigDecimal(row.get("stockQty"));
                String useStatus = getStr(row, "useStatus", "usestatus");
                String statusDesc = useStatus != null ? LocStsType.getDescByType(useStatus) : null;
                String locCodesWithQty = getStr(row, "locCodes$", "loccodes$");
                Matnr copy = cloneMatnrForRow(record);
                copy.setStockQty(v);
                copy.setStockQty(stockQtyVal.setScale(QTY_SCALE, RoundingMode.HALF_UP).doubleValue());
                copy.setLocUseStatus$(statusDesc);
                copy.setLocCodes$(locCodesWithQty);
                expanded.add(copy);
@@ -158,12 +170,12 @@
            try { matnrId = Long.parseLong(parts[0]); } catch (NumberFormatException ex) { continue; }
            String useStatus = parts[1];
            List<Map<String, Object>> locs = e.getValue();
            double stockQty = 0d;
            BigDecimal stockQty = BigDecimal.ZERO;
            StringBuilder sb = new StringBuilder();
            for (Map<String, Object> locRow : locs) {
                Object q = getAny(locRow, "locQty", "locqty");
                double qty = q instanceof Number ? ((Number) q).doubleValue() : 0d;
                stockQty += qty;
                BigDecimal qty = toBigDecimal(q);
                stockQty = stockQty.add(qty);
                String code = getStr(locRow, "locCode", "loccode");
                if (sb.length() > 0) sb.append(",");
                sb.append(code != null ? code : "").append("(").append(formatQtyForLoc(qty)).append(")");
@@ -178,10 +190,11 @@
        return rowsByMatnr;
    }
    private static String formatQtyForLoc(double qty) {
        if (qty == (long) qty) return String.valueOf((long) qty);
        String s = BigDecimal.valueOf(qty).setScale(6, RoundingMode.HALF_UP).stripTrailingZeros().toPlainString();
        return s;
    private static String formatQtyForLoc(BigDecimal qty) {
        if (qty == null) return "0";
        BigDecimal scaled = qty.setScale(QTY_SCALE, RoundingMode.HALF_UP).stripTrailingZeros();
        if (scaled.scale() <= 0) return scaled.toPlainString();
        return scaled.toPlainString();
    }
    private static Long getLong(Map<String, Object> map, String... keys) {
@@ -227,8 +240,10 @@
        });
        List<WkOrderItem> orderItems = asnOrderItemService.list(new LambdaQueryWrapper<WkOrderItem>()
                .eq(WkOrderItem::getOrderId, params.getOrders().getId()));
        Double sum = orderItems.stream().mapToDouble(WkOrderItem::getAnfme).sum();
        orders.setAnfme(sum);
        BigDecimal sum = orderItems.stream()
                .map(o -> toBigDecimal(o.getAnfme()))
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        orders.setAnfme(sum.setScale(QTY_SCALE, RoundingMode.HALF_UP).doubleValue());
        if (!this.updateById(orders)) {
            throw new CoolException("计划收货数量修改失败!!");
        }
@@ -259,18 +274,18 @@
        List<WkOrderItem> orderItems = outStockItemService.list(new LambdaQueryWrapper<WkOrderItem>().eq(WkOrderItem::getOrderId, id));
        if (!orderItems.isEmpty()) {
            for (WkOrderItem orderItem : orderItems) {
                if (!Objects.isNull(orderItem.getPoDetlId())) {
                    if (!Objects.isNull(orderItem.getPoDetlId())) {
                    DeliveryItem deliveryItem = deliveryItemService.getById(orderItem.getPoDetlId());
                    Double workQty = Math.round((deliveryItem.getWorkQty() - orderItem.getAnfme()) * 1000000) / 1000000.0;
                    deliveryItem.setWorkQty(workQty.compareTo(0.0) >= 0 ? workQty : 0);
                    BigDecimal workQty = toBigDecimal(deliveryItem.getWorkQty()).subtract(toBigDecimal(orderItem.getAnfme())).setScale(QTY_SCALE, RoundingMode.HALF_UP);
                    deliveryItem.setWorkQty(workQty.compareTo(BigDecimal.ZERO) >= 0 ? workQty.doubleValue() : 0);
                    if (!deliveryItemService.updateById(deliveryItem)) {
                        throw new CoolException("DO单明细更新失败!!");
                    }
                    Delivery delivery = deliveryService.getOne(new LambdaQueryWrapper<Delivery>().eq(Delivery::getCode, orderItem.getPoCode()));
                    if (!Objects.isNull(delivery)) {
                        Double wkQty = Math.round((delivery.getWorkQty() - delivery.getAnfme()) * 1000000) / 1000000.0;
                        delivery.setWorkQty(wkQty.compareTo(0.0) >= 0 ? wkQty : 0).setExceStatus(POExceStatus.PO_EXCE_STATUS_UN_EXCE.val);
                        BigDecimal wkQty = toBigDecimal(delivery.getWorkQty()).subtract(toBigDecimal(delivery.getAnfme())).setScale(QTY_SCALE, RoundingMode.HALF_UP);
                        delivery.setWorkQty(wkQty.compareTo(BigDecimal.ZERO) >= 0 ? wkQty.doubleValue() : 0).setExceStatus(POExceStatus.PO_EXCE_STATUS_UN_EXCE.val);
                        if (!deliveryService.updateById(delivery)) {
                            throw new CoolException("DO单据修改失败!!");
                        }
@@ -333,8 +348,8 @@
                if (item.getAnfme().compareTo(0.0) <= 0) {
                    throw new CoolException("出库数量不能小于或等于零!!");
                }
                Double anfme = Math.round((deliveryItem.getAnfme() - item.getAnfme()) * 1000000) / 1000000.0;
                if (anfme.compareTo(0.0) < 0) {
                BigDecimal anfme = toBigDecimal(deliveryItem.getAnfme()).subtract(toBigDecimal(item.getAnfme())).setScale(QTY_SCALE, RoundingMode.HALF_UP);
                if (anfme.compareTo(BigDecimal.ZERO) < 0) {
                    throw new CoolException("出库数量不足!!");
                }
@@ -366,9 +381,9 @@
                }
            });
            Double sum = orderItems.stream().mapToDouble(WkOrderItem::getAnfme).sum();
            BigDecimal sum = orderItems.stream().map(o -> toBigDecimal(o.getAnfme())).reduce(BigDecimal.ZERO, BigDecimal::add);
            //修改计划数量
            order.setAnfme(sum).setWorkQty(0.0);
            order.setAnfme(sum.setScale(QTY_SCALE, RoundingMode.HALF_UP).doubleValue()).setWorkQty(0.0);
            if (!this.saveOrUpdate(order)) {
                throw new CoolException("主单数量修改失败!!");
            }
@@ -381,10 +396,10 @@
                exceStatus = AsnExceStatus.ASN_EXCE_STATUS_TASK_DONE.val;
            }
            Double wkQty = Math.round((delivery.getWorkQty() + sum) * 1000000) / 1000000.0;
            BigDecimal wkQty = toBigDecimal(delivery.getWorkQty()).add(sum).setScale(QTY_SCALE, RoundingMode.HALF_UP);
            if (!deliveryService.update(new LambdaUpdateWrapper<Delivery>()
                    .set(Delivery::getExceStatus, exceStatus)
                    .set(Delivery::getWorkQty, wkQty)
                    .set(Delivery::getWorkQty, wkQty.doubleValue())
                    .eq(Delivery::getId, key))) {
                throw new CoolException("主单修改失败!!");
            }
@@ -411,9 +426,9 @@
        if (orders.isEmpty()) {
            throw new CoolException("当前单据状态不能执行波次生成操作!!");
        }
        Double sum = orders.stream().mapToDouble(WkOrder::getAnfme).sum();
        Double workQty = orders.stream().mapToDouble(WkOrder::getWorkQty).sum();
        Double anfme = Math.round((sum - workQty) * 1000000) / 1000000.0;
        BigDecimal sum = orders.stream().map(o -> toBigDecimal(o.getAnfme())).reduce(BigDecimal.ZERO, BigDecimal::add);
        BigDecimal workQty = orders.stream().map(o -> toBigDecimal(o.getWorkQty())).reduce(BigDecimal.ZERO, BigDecimal::add);
        BigDecimal anfme = sum.subtract(workQty).setScale(QTY_SCALE, RoundingMode.HALF_UP);
        Wave wave = new Wave();
        String ruleCode = SerialRuleUtils.generateRuleCode(SerialRuleCode.SYS_WAVE_TYPE, null);
        if (StringUtils.isBlank(ruleCode)) {
@@ -423,7 +438,7 @@
                .setType(Short.parseShort("1"))
                .setCode(ruleCode)
                .setExceStatus(WaveExceStatus.WAVE_EXCE_STATUS_INIT.val)
                .setAnfme(anfme);
                .setAnfme(anfme.doubleValue());
        if (!waveService.save(wave)) {
            throw new CoolException("波次保存失败!!");
        }
@@ -442,8 +457,8 @@
        if (!waveItemService.saveBatch(waveItems)) {
            throw new CoolException("波次明细保存失败!!");
        }
        double sum1 = waveItems.stream().mapToDouble(WaveItem::getAnfme).sum();
        wave.setAnfme(sum1).setGroupQty(waveItems.size());
        BigDecimal sum1 = waveItems.stream().map(o -> toBigDecimal(o.getAnfme())).reduce(BigDecimal.ZERO, BigDecimal::add);
        wave.setAnfme(sum1.setScale(QTY_SCALE, RoundingMode.HALF_UP).doubleValue()).setGroupQty(waveItems.size());
        if (!waveService.saveOrUpdate(wave)) {
            throw new CoolException("主单修改失败!!");
        }
@@ -478,10 +493,10 @@
            throw new CoolException("出库单执行数量修改失败!!");
        }
        for (WkOrder order : orders) {
            Double wkQty = Math.round((order.getWorkQty() + order.getAnfme()) * 1000000) / 1000000.0;
            BigDecimal wkQty = toBigDecimal(order.getWorkQty()).add(toBigDecimal(order.getAnfme())).setScale(QTY_SCALE, RoundingMode.HALF_UP);
            if (!this.update(new LambdaUpdateWrapper<WkOrder>()
                    .set(WkOrder::getWaveId, wave.getId())
                    .set(WkOrder::getWorkQty, wkQty)
                    .set(WkOrder::getWorkQty, wkQty.doubleValue())
                    .set(WkOrder::getExceStatus, AsnExceStatus.OUT_STOCK_STATUS_TASK_WAVE.val)
                    .eq(WkOrder::getId, order.getId()))) {
                throw new CoolException("执行状态修改修改失败!!");
@@ -593,8 +608,8 @@
        // 重新汇总主单数量(删除明细后)
        List<WkOrderItem> afterItems = asnOrderItemService.list(new LambdaQueryWrapper<WkOrderItem>()
                .eq(WkOrderItem::getOrderId, orders.getId()));
        Double sum = afterItems.stream().mapToDouble(WkOrderItem::getAnfme).sum();
        orders.setAnfme(sum);
        BigDecimal sum = afterItems.stream().map(o -> toBigDecimal(o.getAnfme())).reduce(BigDecimal.ZERO, BigDecimal::add);
        orders.setAnfme(sum.setScale(QTY_SCALE, RoundingMode.HALF_UP).doubleValue());
        this.updateById(orders);
        return R.ok();
    }
@@ -628,9 +643,9 @@
            if (locItem == null) {
                continue;
            }
            Double anfme = taskItem.getAnfme() != null ? taskItem.getAnfme() : 0.0;
            Double newWorkQty = Math.round((locItem.getWorkQty() - anfme) * 1000000) / 1000000.0;
            locItem.setWorkQty(newWorkQty >= 0 ? newWorkQty : 0)
            BigDecimal anfme = toBigDecimal(taskItem.getAnfme());
            BigDecimal newWorkQty = toBigDecimal(locItem.getWorkQty()).subtract(anfme).setScale(QTY_SCALE, RoundingMode.HALF_UP);
            locItem.setWorkQty(newWorkQty.compareTo(BigDecimal.ZERO) >= 0 ? newWorkQty.doubleValue() : 0)
                    .setOrderId(null)
                    .setOrderItemId(null)
                    .setUpdateBy(loginUserId)
@@ -733,61 +748,74 @@
                if (StringUtils.isNotBlank(locItem.getFieldsIndex())) {
                    orderItemWrapper.eq(WkOrderItem::getFieldsIndex, locItem.getFieldsIndex());
                }
                // 同一出库单下同一物料可能有多条明细(如多行合并),用 list 取仍有剩余数量的第一条,避免 getOne 返回多条抛 TooManyResultsException
                // 同一出库单下同一物料可能有多条明细(如多行合并),用 list 取仍有剩余数量的第一条
                List<WkOrderItem> orderItemCandidates = outStockItemService.list(orderItemWrapper);
                WkOrderItem orderItem = orderItemCandidates.stream()
                        .filter(o -> o.getAnfme() != null && o.getWorkQty() != null && o.getAnfme().compareTo(o.getWorkQty()) > 0)
                        .findFirst()
                        .orElse(null);
                // 如果找不到单据明细,且LocItem来自库存调整,则自动创建WkOrderItem
                // 严格匹配未找到时:再按「同单号+同物料」宽松匹配,避免原明细 fields_index 为空而分配带票号导致重复创建一条
                if (Objects.isNull(orderItem)) {
                    // 检查是否是库存调整产生的库存
                    if (locItem.getWkType() != null &&
                        locItem.getWkType().equals(Short.parseShort(OrderWorkType.ORDER_WORK_TYPE_STOCK_REVISE.type))) {
                        // 获取出库单信息
                        WkOrder outOrder = outStockService.getById(outId);
                        if (Objects.isNull(outOrder)) {
                            throw new CoolException("出库单据不存在!!");
                        }
                        log.info("库存调整产生的库存,自动创建WkOrderItem - 出库单ID:{},物料ID:{},批次:{}",
                                outId, locItem.getMatnrId(), locItem.getBatch());
                        // 创建WkOrderItem
                        orderItem = new WkOrderItem();
                        orderItem.setOrderId(outId)
                                .setOrderCode(outOrder.getCode())
                                .setMatnrId(locItem.getMatnrId())
                                .setMatnrCode(locItem.getMatnrCode())
                                .setMaktx(locItem.getMaktx())
                                .setBatch(StringUtils.isNotBlank(param.getBatch()) ? param.getBatch() : locItem.getBatch())
                                .setSplrBatch(StringUtils.isNotBlank(locItem.getSplrBatch()) ? locItem.getSplrBatch() :
                                             (StringUtils.isNotBlank(locItem.getBatch()) ? locItem.getBatch() : null))
                                .setFieldsIndex(locItem.getFieldsIndex())
                                .setAnfme(param.getOutQty())
                                .setWorkQty(0.0)
                                .setStockUnit(locItem.getUnit())
                                .setPurUnit(locItem.getUnit())
                                .setSpec(locItem.getSpec())
                                .setModel(locItem.getModel())
                                .setCreateBy(loginUserId)
                                .setUpdateBy(loginUserId)
                                .setCreateTime(new Date())
                                .setUpdateTime(new Date());
                        if (!outStockItemService.save(orderItem)) {
                            throw new CoolException("库存调整单据明细创建失败!!");
                        }
                        log.info("WkOrderItem创建成功 - ID:{},出库单ID:{},物料ID:{}",
                                orderItem.getId(), outId, locItem.getMatnrId());
                    } else {
                        throw new CoolException("单据明细不存在!!出库单ID:" + outId + ",物料ID:" + locItem.getMatnrId() +
                                (StringUtils.isNotBlank(locItem.getSplrBatch()) ? ",供应商批次:" + locItem.getSplrBatch() :
                                 StringUtils.isNotBlank(locItem.getBatch()) ? ",库存批次:" + locItem.getBatch() : "") +
                                (StringUtils.isNotBlank(locItem.getFieldsIndex()) ? ",字段索引:" + locItem.getFieldsIndex() : ""));
                    LambdaQueryWrapper<WkOrderItem> relaxedWrapper = new LambdaQueryWrapper<WkOrderItem>()
                            .eq(WkOrderItem::getOrderId, outId)
                            .eq(WkOrderItem::getMatnrId, locItem.getMatnrId());
                    if (StringUtils.isNotBlank(locItem.getSplrBatch())) {
                        relaxedWrapper.eq(WkOrderItem::getSplrBatch, locItem.getSplrBatch());
                    } else if (StringUtils.isNotBlank(locItem.getBatch())) {
                        relaxedWrapper.and(w -> w.eq(WkOrderItem::getBatch, locItem.getBatch()).or().eq(WkOrderItem::getSplrBatch, locItem.getBatch()));
                    }
                    orderItem = outStockItemService.list(relaxedWrapper).stream()
                            .filter(o -> o.getAnfme() != null && o.getWorkQty() != null && o.getAnfme().compareTo(o.getWorkQty()) > 0)
                            .findFirst()
                            .orElse(null);
                    if (orderItem != null && StringUtils.isNotBlank(locItem.getFieldsIndex()) && !locItem.getFieldsIndex().equals(orderItem.getFieldsIndex())) {
                        orderItem.setFieldsIndex(locItem.getFieldsIndex());
                        outStockItemService.updateById(orderItem);
                    }
                }
                // 找不到单据明细时:出库单存在则自动创建WkOrderItem(库存调整或分配时出库单暂无该物料行)
                if (Objects.isNull(orderItem)) {
                    WkOrder outOrder = outStockService.getById(outId);
                    if (Objects.isNull(outOrder)) {
                        throw new CoolException("出库单据不存在!!");
                    }
                    boolean isStockRevise = locItem.getWkType() != null
                            && locItem.getWkType().equals(Short.parseShort(OrderWorkType.ORDER_WORK_TYPE_STOCK_REVISE.type));
                    if (isStockRevise) {
                        log.info("库存调整产生的库存,自动创建WkOrderItem - 出库单ID:{},物料ID:{},批次:{}",
                                outId, locItem.getMatnrId(), locItem.getBatch());
                    } else {
                        log.info("出库单下无匹配明细,自动创建WkOrderItem - 出库单ID:{},物料ID:{},批次:{}",
                                outId, locItem.getMatnrId(), locItem.getBatch());
                    }
                    orderItem = new WkOrderItem();
                    orderItem.setOrderId(outId)
                            .setOrderCode(outOrder.getCode())
                            .setMatnrId(locItem.getMatnrId())
                            .setMatnrCode(locItem.getMatnrCode())
                            .setMaktx(locItem.getMaktx())
                            .setBatch(StringUtils.isNotBlank(param.getBatch()) ? param.getBatch() : locItem.getBatch())
                            .setSplrBatch(StringUtils.isNotBlank(locItem.getSplrBatch()) ? locItem.getSplrBatch() :
                                    (StringUtils.isNotBlank(locItem.getBatch()) ? locItem.getBatch() : null))
                            .setFieldsIndex(locItem.getFieldsIndex())
                            .setAnfme(param.getOutQty())
                            .setWorkQty(0.0)
                            .setStockUnit(locItem.getUnit())
                            .setPurUnit(locItem.getUnit())
                            .setSpec(locItem.getSpec())
                            .setModel(locItem.getModel())
                            .setCreateBy(loginUserId)
                            .setUpdateBy(loginUserId)
                            .setCreateTime(new Date())
                            .setUpdateTime(new Date());
                    if (!outStockItemService.save(orderItem)) {
                        throw new CoolException("出库单明细创建失败!!");
                    }
                    log.info("WkOrderItem创建成功 - ID:{},出库单ID:{},物料ID:{}",
                            orderItem.getId(), outId, locItem.getMatnrId());
                }
                locItem.setOutQty(param.getOutQty())
@@ -815,9 +843,9 @@
                    throw new CoolException(e.getMessage());
                }
                Double workQty = Math.round((orderItem.getWorkQty() + locItem.getOutQty()) * 1000000) / 1000000.0;
                BigDecimal workQty = toBigDecimal(orderItem.getWorkQty()).add(toBigDecimal(locItem.getOutQty())).setScale(QTY_SCALE, RoundingMode.HALF_UP);
                orderItem.setUpdateBy(loginUserId).setUpdateTime(new Date()).setWorkQty(workQty);
                orderItem.setUpdateBy(loginUserId).setUpdateTime(new Date()).setWorkQty(workQty.doubleValue());
                if (!outStockItemService.updateById(orderItem)) {
                    throw new CoolException("单据明细修改失败!!");
@@ -825,15 +853,15 @@
            }
        }
        Double sum = Items.stream().mapToDouble(OutStockToTaskParams::getOutQty).sum();
        BigDecimal sum = Items.stream().map(OutStockToTaskParams::getOutQty).map(OutStockServiceImpl::toBigDecimal).reduce(BigDecimal.ZERO, BigDecimal::add);
        //更新出库单明细及主单
        WkOrder outOrder = outStockService.getById(outId);
        if (Objects.isNull(outOrder)) {
            throw new CoolException("出库单据不存在!!");
        }
        Double workQty = Math.round((outOrder.getWorkQty() + sum) * 1000000) / 1000000.0;
        BigDecimal workQty = toBigDecimal(outOrder.getWorkQty()).add(sum).setScale(QTY_SCALE, RoundingMode.HALF_UP);
        outOrder.setWorkQty(workQty).setExceStatus(AsnExceStatus.OUT_STOCK_STATUS_TASK_CREATE.val);
        outOrder.setWorkQty(workQty.doubleValue()).setExceStatus(AsnExceStatus.OUT_STOCK_STATUS_TASK_CREATE.val);
        if (!outStockService.updateById(outOrder)) {
            throw new CoolException("出库单状态修改失败!!");
@@ -875,16 +903,16 @@
            if (!items.isEmpty()) {
                for (WkOrderItem orderItem : items) {
                    DeliveryItem deliveryItem = deliveryItemService.getById(orderItem.getPoDetlId());
                    Double workQty = Math.round((deliveryItem.getWorkQty() - orderItem.getAnfme()) * 1000000) / 1000000.0;
                    deliveryItem.setWorkQty(workQty.compareTo(0.0) >= 0 ? workQty : 0);
                    BigDecimal workQty = toBigDecimal(deliveryItem.getWorkQty()).subtract(toBigDecimal(orderItem.getAnfme())).setScale(QTY_SCALE, RoundingMode.HALF_UP);
                    deliveryItem.setWorkQty(workQty.compareTo(BigDecimal.ZERO) >= 0 ? workQty.doubleValue() : 0);
                    if (!deliveryItemService.updateById(deliveryItem)) {
                        throw new CoolException("DO单明细更新失败!!");
                    }
                    Delivery delivery = deliveryService.getOne(new LambdaQueryWrapper<Delivery>().eq(Delivery::getCode, orderItem.getPoCode()));
                    if (!Objects.isNull(delivery)) {
                        Double wkQty = Math.round((delivery.getWorkQty() - delivery.getAnfme()) * 1000000) / 1000000.0;
                        delivery.setWorkQty(wkQty.compareTo(0.0) >= 0 ? wkQty : 0).setExceStatus(POExceStatus.PO_EXCE_STATUS_UN_EXCE.val);
                        BigDecimal wkQty = toBigDecimal(delivery.getWorkQty()).subtract(toBigDecimal(delivery.getAnfme())).setScale(QTY_SCALE, RoundingMode.HALF_UP);
                        delivery.setWorkQty(wkQty.compareTo(BigDecimal.ZERO) >= 0 ? wkQty.doubleValue() : 0).setExceStatus(POExceStatus.PO_EXCE_STATUS_UN_EXCE.val);
                        if (!deliveryService.updateById(delivery)) {
                            throw new CoolException("DO单据修改失败!!");
                        }
@@ -929,22 +957,44 @@
                if (issued.compareTo(ISSUED_TOLERANCE) <= 0) {
                    break;
                }
                // 该库位可分配数量:取本行待分配与库位库存的较小值
                double allocatable = Math.min(issued.doubleValue(), locItem.getAnfme() != null ? locItem.getAnfme() : 0);
                // 预约出库中且未生成拣料入库单的库位:扣减「拣料中占用」得到剩余可用,并标记「正在拣料中,剩余 X 可用」
                BigDecimal pickingAllocated = getPickingAllocatedOnLoc(loc.getCode(), locItem.getMatnrId(), locItem.getBatch(), locItem.getFieldsIndex());
                BigDecimal rawAnfme = toBigDecimal(locItem.getAnfme());
                BigDecimal available = rawAnfme.subtract(pickingAllocated).setScale(QTY_SCALE, RoundingMode.HALF_UP);
                if (available.compareTo(BigDecimal.ZERO) < 0) available = BigDecimal.ZERO;
                locItem.setAnfme(available.doubleValue());
                String pickingStatus = null;
                if (pickingAllocated.compareTo(ISSUED_TOLERANCE) > 0) {
                    pickingStatus = "正在拣料中,剩余 " + available.setScale(2, RoundingMode.HALF_UP).stripTrailingZeros().toPlainString() + " 可用";
                }
                // 该库位可分配数量:取本行待分配与库位剩余可用的较小值
                BigDecimal allocatable = issued.min(available);
                // 当分配量等于库位库存时,使用库位库存精度作为出库数量,避免截断导致界面显示/库存校验不一致(如库存15.123457被截成15.123)
                double outQtyToSet = (locItem.getAnfme() != null && Math.abs(allocatable - locItem.getAnfme()) < 1e-6)
                        ? locItem.getAnfme() : allocatable;
                BigDecimal outQtyToSet = (allocatable.subtract(available).abs().compareTo(ISSUED_TOLERANCE) <= 0) ? available : allocatable;
                ExistDto existDto = new ExistDto().setBatch(locItem.getBatch()).setMatnr(locItem.getMatnrCode()).setLocNo(locItem.getLocCode());
                if (existDtos.add(existDto)) {
                    // 首次使用该库位:加入列表并扣减 issued
                    locItem.setOutQty(outQtyToSet);
                    locItem.setBarcode(loc.getBarcode());
                    locItem.setOutQty(outQtyToSet.doubleValue());
                    String barcode = StringUtils.isNotBlank(loc.getBarcode()) ? loc.getBarcode() : null;
                    if (barcode == null && LocStsType.LOC_STS_TYPE_R.type.equals(loc.getUseStatus())) {
                        List<Task> tasksOnLoc = taskService.list(new LambdaQueryWrapper<Task>()
                                .eq(Task::getOrgLoc, loc.getCode())
                                .in(Task::getTaskStatus, Arrays.asList(TaskStsType.GENERATE_OUT.id, TaskStsType.WAVE_SEED.id))
                                .last("LIMIT 1"));
                        if (!tasksOnLoc.isEmpty() && StringUtils.isNotBlank(tasksOnLoc.get(0).getBarcode())) {
                            barcode = tasksOnLoc.get(0).getBarcode();
                        }
                    }
                    locItem.setBarcode(barcode != null ? barcode : loc.getBarcode());
                    OrderOutItemDto orderOutItemDto = new OrderOutItemDto();
                    orderOutItemDto.setLocItem(locItem);
                    if (pickingStatus != null) {
                        orderOutItemDto.setPickingStatus(pickingStatus);
                    }
                    List<DeviceSite> deviceSites = deviceSiteService.list(new LambdaQueryWrapper<DeviceSite>()
                            .eq(DeviceSite::getChannel, loc.getChannel())
                            .eq(DeviceSite::getType, issued.doubleValue() >= locItem.getAnfme() && itemList.size() == 1 ? TaskType.TASK_TYPE_OUT.type : TaskType.TASK_TYPE_PICK_AGAIN_OUT.type)
                            .eq(DeviceSite::getType, issued.compareTo(toBigDecimal(locItem.getAnfme())) >= 0 && itemList.size() == 1 ? TaskType.TASK_TYPE_OUT.type : TaskType.TASK_TYPE_PICK_AGAIN_OUT.type)
                    );
                    // 出库口列表排序:1001 排第一,作为默认
                    deviceSites.sort((a, b) -> {
@@ -970,21 +1020,21 @@
                    }
                    list.add(orderOutItemDto);
                    issued = issued.subtract(new BigDecimal(locItem.getAnfme().toString()));
                    issued = issued.subtract(outQtyToSet);
                } else {
                    // 该库位已被前序订单行占用:只扣减 issued,不重复加入列表,避免产生“库存不足”脏数据
                    issued = issued.subtract(new BigDecimal(String.valueOf(allocatable)));
                    issued = issued.subtract(allocatable);
                }
            }
            if (issued.compareTo(ISSUED_TOLERANCE) > 0) {
                double remaining = issued.setScale(6, RoundingMode.HALF_UP).doubleValue();
                BigDecimal remaining = issued.setScale(QTY_SCALE, RoundingMode.HALF_UP);
                LocItem locItem = new LocItem()
                        .setId(new Random().nextLong())
                        .setMatnrCode(wkOrderItem.getMatnrCode())
                        .setMaktx(wkOrderItem.getMaktx())
                        .setAnfme(0.00)
                        .setWorkQty(remaining)
                        .setOutQty(remaining)
                        .setWorkQty(remaining.doubleValue())
                        .setOutQty(remaining.doubleValue())
                        .setUnit(wkOrderItem.getStockUnit())
                        .setBatch(wkOrderItem.getSplrBatch());
                OrderOutItemDto orderOutItemDto = new OrderOutItemDto();
@@ -998,6 +1048,25 @@
    }
    /**
     * 该库位下「拣料出库」且未生成拣料入库单的任务占用的数量(同一物料+批次+票号)
     */
    private BigDecimal getPickingAllocatedOnLoc(String locCode, Long matnrId, String batch, String fieldsIndex) {
        List<Task> tasks = taskService.list(new LambdaQueryWrapper<Task>()
                .eq(Task::getOrgLoc, locCode)
                .eq(Task::getTaskType, TaskType.TASK_TYPE_PICK_AGAIN_OUT.type)
                .in(Task::getTaskStatus, Arrays.asList(TaskStsType.GENERATE_OUT.id, TaskStsType.WAVE_SEED.id)));
        if (tasks.isEmpty()) return BigDecimal.ZERO;
        Set<Long> taskIds = tasks.stream().map(Task::getId).collect(Collectors.toSet());
        LambdaQueryWrapper<TaskItem> q = new LambdaQueryWrapper<TaskItem>()
                .in(TaskItem::getTaskId, taskIds)
                .eq(TaskItem::getMatnrId, matnrId);
        if (StringUtils.isNotBlank(batch)) q.eq(TaskItem::getBatch, batch);
        if (StringUtils.isNotBlank(fieldsIndex)) q.eq(TaskItem::getFieldsIndex, fieldsIndex);
        List<TaskItem> items = taskItemService.list(q);
        return items.stream().map(ti -> toBigDecimal(ti.getAnfme())).reduce(BigDecimal.ZERO, BigDecimal::add);
    }
    /**
     * @param
     * @param wave
     * @return
@@ -1008,11 +1077,11 @@
    private List<WaveItem> mergeWave(List<WkOrderItem> orderItems, Wave wave) {
        List<WaveItem> items = new ArrayList<>();
        orderItems.forEach(order -> {
            Double anfme = Math.round((order.getAnfme() - order.getWorkQty()) * 1000000) / 1000000.0;
            BigDecimal anfme = toBigDecimal(order.getAnfme()).subtract(toBigDecimal(order.getWorkQty())).setScale(QTY_SCALE, RoundingMode.HALF_UP);
            WaveItem item = new WaveItem();
            BeanUtils.copyProperties(order, item);
                item.setId(null)
                    .setAnfme(anfme)
                    .setAnfme(anfme.doubleValue())
                    .setMatnrId(order.getMatnrId())
                    .setMaktx(order.getMaktx())
                    .setWaveId(wave.getId())
@@ -1034,7 +1103,7 @@
                        p1.getUnit(),
                        p1.getTrackCode(),
                        p1.getFieldsIndex(),
                        Math.round((p1.getAnfme() + p2.getAnfme())* 1000000) / 1000000.0,
                        toBigDecimal(p1.getAnfme()).add(toBigDecimal(p2.getAnfme())).setScale(QTY_SCALE, RoundingMode.HALF_UP).doubleValue(),
                        p1.getWorkQty(),
                        p1.getTenantId(),
                        p1.getStatus(),