chen.lin
昨天 98d88ac8caf7f0991d741079474c262f1e252927
rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/LocItemServiceImpl.java
@@ -1,6 +1,7 @@
package com.vincent.rsf.server.manager.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.vincent.rsf.framework.exception.CoolException;
import com.vincent.rsf.server.api.controller.erp.params.TaskInParam;
import com.vincent.rsf.server.api.entity.dto.InTaskMsgDto;
@@ -66,13 +67,11 @@
    @Synchronized
    @Transactional(rollbackFor = Exception.class)
    public synchronized void generateTask(Short resouce, LocToTaskParams map, Long loginUserId) throws Exception {
        if (Objects.isNull(map.getSiteNo())) {
            throw new CoolException("站点不能为空!");
        }
        // 出库口未传时默认 1001
        String siteNo = StringUtils.isNotBlank(map.getSiteNo()) ? map.getSiteNo() : "1001";
        if (Objects.isNull(map.getItems()) || map.getItems().isEmpty()) {
            throw new CoolException("明细不能为空!");
        }
        String siteNo = map.getSiteNo();
        List<LocItem> items = map.getItems();
        Map<Long, List<LocItem>> listMap = items.stream().collect(Collectors.groupingBy(LocItem::getLocId));
        WkOrder order;
@@ -97,14 +96,53 @@
            if (Objects.isNull(loc)) {
                throw new CoolException("数据错误:所选库存信息不存在!!");
            }
            if (!loc.getUseStatus().equals(LocStsType.LOC_STS_TYPE_F.type)) {
                throw new CoolException("库位:" + loc.getCode() + ",不处于F.在库状态,不可执行R.出库预约操作!!");
            // 支持 F.在库 或 R.出库预约/拣货中 状态下分配(拣货中可追加同库位订单,分配量不超过已分配剩余)
            if (!loc.getUseStatus().equals(LocStsType.LOC_STS_TYPE_F.type)
                    && !loc.getUseStatus().equals(LocStsType.LOC_STS_TYPE_R.type)) {
                throw new CoolException("库位:" + loc.getCode() + ",不处于F.在库或R.出库预约状态,不可执行出库分配!!");
            }
            loc.setUseStatus(LocStsType.LOC_STS_TYPE_R.type);
            if (!locService.updateById(loc)) {
                throw new CoolException("库位状态更新失败!!");
            if (loc.getUseStatus().equals(LocStsType.LOC_STS_TYPE_F.type)) {
                loc.setUseStatus(LocStsType.LOC_STS_TYPE_R.type);
                if (!locService.updateById(loc)) {
                    throw new CoolException("库位状态更新失败!!");
                }
            }
            // 库位已为 R 时:计算该库位当前任务已分配量,新分配不能超过 (库位数量 - 已分配)
            Map<String, Double> allocatedByKey = new HashMap<>();
            List<Task> existTasks = new ArrayList<>();
            if (loc.getUseStatus().equals(LocStsType.LOC_STS_TYPE_R.type)) {
                existTasks = taskService.list(new LambdaQueryWrapper<Task>()
                        .eq(Task::getOrgLoc, loc.getCode())
                        .in(Task::getTaskStatus, Arrays.asList(TaskStsType.GENERATE_OUT.id, TaskStsType.WAVE_SEED.id)));
                for (Task t : existTasks) {
                    List<TaskItem> existItems = taskItemService.list(new LambdaQueryWrapper<TaskItem>().eq(TaskItem::getTaskId, t.getId()));
                    for (TaskItem ti : existItems) {
                        String k = buildAllocKey(ti.getMatnrId(), ti.getBatch(), ti.getFieldsIndex());
                        allocatedByKey.put(k, allocatedByKey.getOrDefault(k, 0.0) + (ti.getAnfme() != null ? ti.getAnfme() : 0.0));
                    }
                }
            }
            // 料箱码:优先用库位;预约出库(R) 时若库位未绑定则从同库位已有任务带出并回写库位;再否则从本次分配明细带出
            String barcodeToUse = StringUtils.isNotBlank(loc.getBarcode()) ? loc.getBarcode() : null;
            if (barcodeToUse == null && !existTasks.isEmpty()) {
                barcodeToUse = existTasks.get(0).getBarcode();
                if (StringUtils.isNotBlank(barcodeToUse)) {
                    locService.update(new LambdaUpdateWrapper<Loc>().eq(Loc::getId, loc.getId())
                            .set(Loc::getBarcode, barcodeToUse).set(Loc::getUpdateBy, loginUserId).set(Loc::getUpdateTime, new Date()));
                }
            }
            if (barcodeToUse == null) {
                List<LocItem> allocItems = listMap.get(key);
                if (allocItems != null) {
                    barcodeToUse = allocItems.stream()
                            .map(LocItem::getBarcode)
                            .filter(StringUtils::isNotBlank)
                            .findFirst()
                            .orElse(null);
                }
            }
            if (barcodeToUse == null) {
                barcodeToUse = loc.getBarcode();
            }
            Task moveTask = new Task();
            String ruleCode = SerialRuleUtils.generateRuleCode(SerialRuleCode.SYS_TASK_CODE, null);
@@ -118,7 +156,7 @@
                    .setCreateTime(new Date())
                    .setUpdateTime(new Date())
                    .setTaskStatus(TaskStsType.GENERATE_OUT.id)
                    .setBarcode(loc.getBarcode())
                    .setBarcode(barcodeToUse)
                    .setMemo(map.getMemo());
            List<LocItem> locItems = this.list(new LambdaQueryWrapper<LocItem>().eq(LocItem::getLocId, key));
@@ -205,10 +243,33 @@
            List<TaskItem> taskItems = new ArrayList<>();
            listMap.get(key).forEach(item -> {
                LocItem locItem = locItemService.getById(item.getId());
                if (Objects.isNull(locItem)) {
                    throw new CoolException("库存信息不存在!");
                }
                if (item.getOutQty().compareTo(0.0) < 0) {
                    throw new CoolException("出库数里不能小于0!!");
                }
                // 预约出库/拣货中追加:新分配量不能超过 (库位数量 - 该物料已分配量)
                Double allocQty = item.getOutQty();
                if (!allocatedByKey.isEmpty()) {
                    String allocKey = buildAllocKey(locItem.getMatnrId(), locItem.getBatch(), locItem.getFieldsIndex());
                    Double already = allocatedByKey.getOrDefault(allocKey, 0.0);
                    Double available = Math.round((locItem.getAnfme() - already) * 1000000) / 1000000.0;
                    if (available.compareTo(0.0) <= 0) {
                        throw new CoolException("库位:" + loc.getCode() + " 该物料已无剩余可分配数量,不可再追加订单!");
                    }
                    if (allocQty.compareTo(available) > 0) {
                        allocQty = available;
                        item.setOutQty(allocQty);
                    }
                    allocatedByKey.put(allocKey, already + allocQty);
                }
                TaskItem taskItem = new TaskItem();
                BeanUtils.copyProperties(item, taskItem);
                taskItem.setTaskId(task.getId())
                        .setAnfme(item.getOutQty())
                        .setAnfme(allocQty)
                        .setBatch(item.getBatch())
                        .setUpdateBy(loginUserId)
                        .setCreateBy(loginUserId)
@@ -218,7 +279,8 @@
                if (map.getType().equals(Constants.TASK_TYPE_ORDER_OUT_STOCK)) {
                    taskItem.setWkType(Short.parseShort(order.getWkType()))
                            .setSourceCode(order.getCode())
                            .setSourceId(order.getId());
                            .setSourceId(order.getId())
                            .setOrderItemId(item.getOrderItemId());
                } else if (map.getType().equals(Constants.TASK_TYPE_WAVE_OUT_STOCK)) {
                    taskItem.setSourceId(wave.getId())
                            .setWkType(Short.parseShort(OrderWorkType.ORDER_WORK_TYPE_OTHER.type))
@@ -232,18 +294,9 @@
                }
                taskItems.add(taskItem);
                Double qty = Math.round((item.getWorkQty() + item.getOutQty()) * 1000000) / 1000000.0;
                LocItem locItem = locItemService.getById(item.getId());
                if (Objects.isNull(locItem)) {
                    throw new CoolException("库存信息不存在!");
                }
                if (item.getOutQty().compareTo(0.0) < 0) {
                    throw new CoolException("出库数里不能小于0!!");
                }
                Double qty = Math.round((item.getWorkQty() != null ? item.getWorkQty() : 0.0) + allocQty) * 1000000.0 / 1000000.0;
                if (locItem.getAnfme().compareTo(qty) < 0) {
                    Double minusQty = Math.round((locItem.getAnfme() - locItem.getWorkQty()) * 1000000) / 1000000.0;
                    Double minusQty = Math.round((locItem.getAnfme() - (locItem.getWorkQty() != null ? locItem.getWorkQty() : 0.0)) * 1000000) / 1000000.0;
                    item.setWorkQty(minusQty);
                } else {
                    item.setWorkQty(qty);
@@ -369,6 +422,66 @@
    }
    /**
     * 空板出库:从指定空板库位(useStatus=D)生成 TASK_TYPE_EMPITY_OUT 任务至目标站点。
     * 需在设备站点中配置 type=110(空板出库)的站点路径。
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Task generateTaskEmpty(LocToTaskParams map, Long loginUserId) {
        if (StringUtils.isBlank(map.getSiteNo())) {
            throw new CoolException("目标站点不能为空!");
        }
        if (StringUtils.isBlank(map.getOrgLoc())) {
            throw new CoolException("源库位不能为空!");
        }
        if (!Constants.TASK_TYPE_OUT_STOCK_EMPTY.equals(map.getType())) {
            throw new CoolException("类型必须为 empty(空板出库)!");
        }
        Loc loc = locService.getOne(new LambdaQueryWrapper<Loc>().eq(Loc::getCode, map.getOrgLoc()));
        if (loc == null) {
            throw new CoolException("源库位不存在!");
        }
        if (!LocStsType.LOC_STS_TYPE_D.type.equals(loc.getUseStatus())) {
            throw new CoolException("库位 " + loc.getCode() + " 不处于空板状态(D),不可执行空板出库!");
        }
        DeviceSite deviceSite = deviceSiteService.getOne(new LambdaQueryWrapper<DeviceSite>()
                .eq(DeviceSite::getSite, map.getSiteNo())
                .eq(DeviceSite::getType, TaskType.TASK_TYPE_EMPITY_OUT.type)
                .last("limit 1"));
        if (deviceSite == null) {
            throw new CoolException("站点不支持空板出库或未配置空板出库路径!");
        }
        if (!locService.update(new LambdaUpdateWrapper<Loc>()
                .eq(Loc::getId, loc.getId())
                .set(Loc::getUseStatus, LocStsType.LOC_STS_TYPE_R.type))) {
            throw new CoolException("库位出库预约失败!");
        }
        String ruleCode = SerialRuleUtils.generateRuleCode(SerialRuleCode.SYS_TASK_CODE, null);
        if (StringUtils.isBlank(ruleCode)) {
            throw new CoolException("编码错误:请确认是否已生成!");
        }
        Task task = new Task();
        task.setTaskCode(ruleCode)
                .setTaskStatus(TaskStsType.GENERATE_OUT.id)
                .setTaskType(TaskType.TASK_TYPE_EMPITY_OUT.type)
                .setWarehType(WarehType.WAREHOUSE_TYPE_CRN.val)
                .setOrgLoc(loc.getCode())
                .setTargSite(map.getSiteNo())
                .setBarcode(loc.getBarcode())
                .setSort(Constants.TASK_SORT_DEFAULT_VALUE)
                .setCreateBy(loginUserId)
                .setUpdateBy(loginUserId)
                .setCreateTime(new Date())
                .setUpdateTime(new Date())
                .setMemo(map.getMemo());
        if (!taskService.save(task)) {
            throw new CoolException("空板出库任务创建失败!");
        }
        logger.info("[空板出库] 已创建任务: {}, 源库位: {}, 目标站点: {}", ruleCode, loc.getCode(), map.getSiteNo());
        return task;
    }
    /**
     * @author Ryan
     * @date 2025/7/16
     * @description: 获取当前物料所有库存信息
@@ -382,4 +495,9 @@
                .in(!matnr.getMatnrCode().isEmpty(), LocItem::getMatnrCode, matnr.getMatnrCode());
        return  this.baseMapper.listByMatnr(LocStsType.LOC_STS_TYPE_F.type, matnr.getChannel(), wrapper);
    }
    /** 库位已分配量按物料+批次+票号聚合的 key */
    private static String buildAllocKey(Long matnrId, String batch, String fieldsIndex) {
        return (matnrId != null ? matnrId : "") + "|" + (batch != null ? batch : "") + "|" + (fieldsIndex != null ? fieldsIndex : "");
    }
}