chen.lin
8 小时以前 c81fc5e2a4f4153be2bb8602ed14a0743e6ecd29
RCS对接优化
12个文件已修改
376 ■■■■■ 已修改文件
rsf-admin/src/page/basicInfo/matnr/MatnrCreate.jsx 24 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/page/basicInfo/matnr/MatnrEdit.jsx 24 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/page/basicInfo/matnr/MatnrList.jsx 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/PdaCheckOrderServiceImpl.java 17 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/PdaOutStockServiceImpl.java 72 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/ReportMsgServiceImpl.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/WcsServiceImpl.java 106 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/schedules/MaterialAutoSchedules.java 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/schedules/ScheduleJobs.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/schedules/TaskSchedules.java 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/TaskServiceImpl.java 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/WaitPakinServiceImpl.java 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/page/basicInfo/matnr/MatnrCreate.jsx
@@ -289,17 +289,19 @@
                                            />
                                        </Grid>
                                        {dynamicFields.map((item) => {
                                            return (
                                                <Grid key={item.id} item xs={6} display="flex" gap={1}>
                                                    <TextInput
                                                        label={item.fieldsAlise}
                                                        source={item.fields}
                                                        validate={item.unique === 1 ? required() : false}
                                                    />
                                                </Grid>
                                            )
                                        })}
                                        {dynamicFields
                                            .filter((item) => item.fields !== 'crushNo' && item.fieldsAlise !== '现品票号')
                                            .map((item) => {
                                                return (
                                                    <Grid key={item.id} item xs={6} display="flex" gap={1}>
                                                        <TextInput
                                                            label={item.fieldsAlise}
                                                            source={item.fields}
                                                            validate={item.unique === 1 ? required() : false}
                                                        />
                                                    </Grid>
                                                )
                                            })}
                                        <Grid item xs={6} display="flex" gap={1}>
                                            <StatusSelectInput />
                                        </Grid>
rsf-admin/src/page/basicInfo/matnr/MatnrEdit.jsx
@@ -264,17 +264,19 @@
                                    />
                                </Grid>
                                {dynamicFields.map((item) => {
                                    return (
                                        <Grid key={item.id} item xs={6} display="flex" gap={1}>
                                            <DynamicFields
                                                label={item.fieldsAlise}
                                                source={item.fields}
                                                validate={item.unique === 1 ? required() : false}
                                            />
                                        </Grid>
                                    )
                                })}
                                {dynamicFields
                                    .filter((item) => item.fields !== 'crushNo' && item.fieldsAlise !== '现品票号')
                                    .map((item) => {
                                        return (
                                            <Grid key={item.id} item xs={6} display="flex" gap={1}>
                                                <DynamicFields
                                                    label={item.fieldsAlise}
                                                    source={item.fields}
                                                    validate={item.unique === 1 ? required() : false}
                                                />
                                            </Grid>
                                        )
                                    })}
                            </Grid>
rsf-admin/src/page/basicInfo/matnr/MatnrList.jsx
@@ -167,6 +167,7 @@
                data: { code, data, msg },
            } = await request.get("/fields/enable/list");
            if (code === 200) {
                const dataFiltered = (data || []).filter(el => el.fields !== 'crushNo' && el.fieldsAlise !== '现品票号');
                const arr = [
                    <NumberField key="id" source="id" />,
                    <TextField key="code" source="code" label="table.field.matnr.code" />,
@@ -204,14 +205,14 @@
                    <BooleanField key="statusBool" source="statusBool" label="common.field.status" sortable={false} />,
                    <TextField key="memo" source="memo" label="common.field.memo" sortable={false} />,
                ]
                const fields = data.map(el => <TextField key={el.fields} source={`extendFields.[${el.fields}]`} label={el.fieldsAlise} />)
                const fields = dataFiltered.map(el => <TextField key={el.fields} source={`extendFields.[${el.fields}]`} label={el.fieldsAlise} />)
                const opt = <WrapperField key="opt" cellClassName="fixed" className="fixed" label="common.field.opt">
                    <EditButton sx={{ padding: '1px', fontSize: '.75rem' }} />
                    <PrintButton />
                </WrapperField>
                setColumns([...arr, ...fields, opt]);
                //filters添加过滤字段
                data.map(el => {
                //filters添加过滤字段(排除现品票号)
                dataFiltered.map(el => {
                    var i =0;
                    filters.map((item) =>{
                        if(item.key === el.fields){
rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/PdaCheckOrderServiceImpl.java
@@ -28,6 +28,7 @@
import com.vincent.rsf.server.system.service.impl.FieldsItemServiceImpl;
import com.vincent.rsf.server.system.service.impl.UserServiceImpl;
import com.vincent.rsf.server.system.utils.SerialRuleUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@@ -350,11 +351,17 @@
                    .setMaktx(matnr.getName())
                    .setAnfme(item.getCheckQty());
            FieldsItem fieldsItem = fieldsItemService.getOne(new LambdaQueryWrapper<FieldsItem>().eq(FieldsItem::getValue, item.getFieldsIndex()).last("limit 1"));
            if (Objects.isNull(fieldsItem)) {
                throw new CoolException("当前票号不存在库存中,请拿出后重新入库!!");
            // 票号暂不使用,不校验票号是否存在
            FieldsItem fieldsItem = null;
            if (StringUtils.isNotBlank(item.getFieldsIndex())) {
                fieldsItem = fieldsItemService.getOne(new LambdaQueryWrapper<FieldsItem>().eq(FieldsItem::getValue, item.getFieldsIndex()).last("limit 1"));
                // if (Objects.isNull(fieldsItem)) {
                //     throw new CoolException("当前票号不存在库存中,请拿出后重新入库!!");
                // }
            }
            taskItem.setFieldsIndex(fieldsItem.getUuid());
            if (fieldsItem != null) {
                taskItem.setFieldsIndex(fieldsItem.getUuid());
            }
            taskItems.add(taskItem);
        });
@@ -463,7 +470,7 @@
                    .setPDQty(taskItem.getAnfme())
                    .setEditUser(nickName)
                    .setEditDate(taskItem.getUpdateTime())
                    .setGoodsNO(fields.get("crushNo"))
                    // .setGoodsNO(fields.get("crushNo"))  // 票号暂不使用
                    .setIsBad(0 + "")
                    .setMemoDtl(taskItem.getMemo());
rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/PdaOutStockServiceImpl.java
@@ -231,9 +231,10 @@
        if (Objects.isNull(param)) {
            return R.error("参数不能为空!!");
        }
        if (Objects.isNull(param.get("fieldsIndex"))) {
            return R.error("票号不能为空!!");
        }
        // 票号暂不使用,注释校验
        // if (Objects.isNull(param.get("fieldsIndex"))) {
        //     return R.error("票号不能为空!!");
        // }
        if (Objects.isNull(param.get("barcode"))) {
            return R.error("料箱码不能为空!!");
        }
@@ -246,28 +247,42 @@
        if (Objects.isNull(task)) {
            throw new CoolException("数据错误,任务档已不存在!!");
        }
        FieldsItem fieldsItem = fieldsItemService.getOne(new LambdaQueryWrapper<FieldsItem>()
                .eq(FieldsItem::getValue, param.get("fieldsIndex").toString())
                .last("limit 1"));
        if (Objects.isNull(fieldsItem)) {
            return R.error("数据错误,票号不存在!!");
        }
        TaskItem taskItem = taskItemService.getOne(new LambdaQueryWrapper<TaskItem>()
        // 票号暂不使用,按任务取第一条明细
        // FieldsItem fieldsItem = fieldsItemService.getOne(new LambdaQueryWrapper<FieldsItem>()
        //         .eq(FieldsItem::getValue, param.get("fieldsIndex").toString())
        //         .last("limit 1"));
        // if (Objects.isNull(fieldsItem)) {
        //     return R.error("数据错误,票号不存在!!");
        // }
        TaskItem taskItem = null;
        FieldsItem fieldsItem = null;
        if (param.get("fieldsIndex") != null && StringUtils.isNotBlank(param.get("fieldsIndex").toString())) {
            fieldsItem = fieldsItemService.getOne(new LambdaQueryWrapper<FieldsItem>()
                    .eq(FieldsItem::getValue, param.get("fieldsIndex").toString())
                    .last("limit 1"));
            if (fieldsItem != null) {
                taskItem = taskItemService.getOne(new LambdaQueryWrapper<TaskItem>()
                        .eq(TaskItem::getFieldsIndex, fieldsItem.getUuid())
                .eq(TaskItem::getTaskId, task.getId()));
                        .eq(TaskItem::getTaskId, task.getId()));
            }
        }
        if (taskItem == null) {
            taskItem = taskItemService.getOne(new LambdaQueryWrapper<TaskItem>()
                    .eq(TaskItem::getTaskId, task.getId())
                    .last("limit 1"));
        }
        if (Objects.isNull(taskItem)) {
            return R.error("数据错误,任务档明细不存在!!");
        }
//        Long orderId = Long.valueOf(param.get("orderId").toString());
//        List<WkOrderItem> orderItems = asnOrderItemService.list(new LambdaQueryWrapper<WkOrderItem>().eq(WkOrderItem::getOrderId, orderId));
//        if (orderItems.isEmpty()) {
//            return R.error("数据错误,订单数据不存在!!");
//        }
        //根据索引获取动态字段Value值
        Map<String, String> fields = new HashMap<>();
        Fields fields1 = fieldsService.getById(fieldsItem.getFieldsId());
        fields.put(fields1.getFields(), fieldsItem.getValue());
        taskItem.setExtendFields(fields);
        // 票号暂不使用:仅当有 fieldsItem 时设置 extendFields
        if (fieldsItem != null) {
            Fields fields1 = fieldsService.getById(fieldsItem.getFieldsId());
            if (fields1 != null) {
                Map<String, String> fields = new HashMap<>();
                fields.put(fields1.getFields(), fieldsItem.getValue());
                taskItem.setExtendFields(fields);
            }
        }
        return R.ok().add(taskItem);
    }
@@ -373,10 +388,10 @@
                        throw new CoolException("缓存数据丢失!!");
                    }
                    Double v1 = Math.round((workItem.getAnfme() - serviceOne.getQty()) * 1000000) / 1000000.0;
                    //不管是否允许超收,都需判断是否超出库存范围
                    if (taskItem.getAnfme().compareTo(v1) > 0) {
                        throw new CoolException("拣货数量超出当前票号库存数量!!");
                    }
                    //不管是否允许超收,都需判断是否超出库存范围(票号暂不使用,该判断注释)
                    // if (taskItem.getAnfme().compareTo(v1) > 0) {
                    //     throw new CoolException("拣货数量超出当前票号库存数量!!");
                    // }
                    if (!Boolean.parseBoolean(config.getVal())) {
                        Double v = Math.round((item.getQty() + taskItem.getAnfme()) * 1000000) / 1000000.0;
                        if (item.getAnfme().compareTo(v) < 0.0) {
@@ -488,6 +503,9 @@
            return R.error("参数不能为空!!");
        }
        for (TaskItem item : items) {
            // 票号暂不使用,跳过修改出库任务档明细票号逻辑
            continue;
            /*
            if (Objects.isNull(item.getCrushNo())) {
                continue;
            }
@@ -496,7 +514,7 @@
                Map<String, String> fields = FieldsUtils.getFields(byId.getFieldsIndex());
                byId.setExtendFields(fields);
            }
            if (byId.getExtendFields().get("crushNo").equals(item.getCrushNo())) {
            if (byId.getExtendFields() != null && byId.getExtendFields().get("crushNo") != null && byId.getExtendFields().get("crushNo").equals(item.getCrushNo())) {
                continue;
            }
            FieldsItem fieldsItem = fieldsItemService.getOne(new LambdaQueryWrapper<FieldsItem>()
@@ -530,7 +548,7 @@
            //更新库位信息
            locItemWorkingService.updateById(oldOne);
            locItemWorkingService.updateById(one);
            */
        }
        return R.ok();
    }
rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/ReportMsgServiceImpl.java
@@ -320,7 +320,7 @@
                        .setEditUser(nickName)
                        .setEditDate(order.getUpdateTime())
                        .setZone(stocks.getLocCode())
                        .setGoodsNO(fields.get("crushNo"))
                        // .setGoodsNO(fields.get("crushNo"))  // 票号暂不使用
                        .setMemoDtl(order.getMemo());
                if (order.getWkType().equals(OrderWorkType.ORDER_WORK_TYPE_DONE_IN.type)) {
@@ -407,7 +407,7 @@
                            .setItemCode(stockItem.getMatnrCode())
                            .setEditUser(finalNickName)
                            .setEditDate(order.getUpdateTime())
                            .setGoodsNO(fields.get("crushNo"))
                            // .setGoodsNO(fields.get("crushNo"))  // 票号暂不使用
                            .setMemoDtl(order.getMemo());
                    if (order.getWkType().equals(OrderWorkType.ORDER_WORK_TYPE_DONE_IN.type)) {
                        //采购入库单
@@ -520,7 +520,7 @@
                .setItemCode(orderItem.getMatnrCode())
                .setEditUser(nickName)
                .setEditDate(order.getUpdateTime())
                .setGoodsNO(fields.get("crushNo"))
                // .setGoodsNO(fields.get("crushNo"))  // 票号暂不使用
                .setMemoDtl(order.getMemo());
        if (order.getWkType().equals(OrderWorkType.ORDER_WORK_TYPE_DONE_IN.type)) {
rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/WcsServiceImpl.java
@@ -322,22 +322,47 @@
        // 2. 若未命中拣料/盘点入库,再校验组托并继续其他入库逻辑
        if (pickInTask == null && checkInTask == null) {
            waitPakin = waitPakinService.getOne(new LambdaQueryWrapper<WaitPakin>()
                    .eq(WaitPakin::getBarcode, param.getBarcode())
                    .in(WaitPakin::getIoStatus, PakinIOStatus.PAKIN_IO_STATUS_DONE.val, PakinIOStatus.PAKIN_IO_STATUS_TASK_EXCE.val));
            // 空托盘无组托时:若配置启用则按 AUTO_FULL_OUT_MATNR_CODE 自动组托并生成入库单,再继续入库任务逻辑
            if (waitPakin == null) {
                tryAutoPakinForBarcode(param.getBarcode());
            String barcode = param.getBarcode();
            // 该托盘已在库存中,不可重复申请入库
            List<Loc> inStock = locService.list(new LambdaQueryWrapper<Loc>().eq(Loc::getBarcode, barcode));
            if (!inStock.isEmpty()) {
                throw new CoolException("barcode=" + barcode + ": 该托盘已在库,不可重复申请入库");
            }
            // 该托盘出库中未完成,不可申请入库
            List<Integer> outboundTaskTypes = Arrays.asList(
                    TaskType.TASK_TYPE_OUT.type,
                    TaskType.TASK_TYPE_PICK_AGAIN_OUT.type,
                    TaskType.TASK_TYPE_MERGE_OUT.type,
                    TaskType.TASK_TYPE_CHECK_OUT.type,
                    TaskType.TASK_TYPE_EMPITY_OUT.type
            );
            Task outboundTask = taskService.getOne(new LambdaQueryWrapper<Task>()
                    .eq(Task::getBarcode, barcode)
                    .in(Task::getTaskType, outboundTaskTypes)
                    .lt(Task::getTaskStatus, TaskStsType.COMPLETE_OUT.id));
            if (outboundTask != null) {
                throw new CoolException("barcode=" + barcode + ": 该托盘出库中未完成,不可申请入库");
            }
            // 按 barcode 加锁,避免同一 barcode 并发请求重复自动组托、重复生成入库单
            String barcodeForLock = param.getBarcode();
            synchronized ((barcodeForLock != null ? barcodeForLock : "").intern()) {
                waitPakin = waitPakinService.getOne(new LambdaQueryWrapper<WaitPakin>()
                        .eq(WaitPakin::getBarcode, param.getBarcode())
                        .in(WaitPakin::getIoStatus, PakinIOStatus.PAKIN_IO_STATUS_DONE.val, PakinIOStatus.PAKIN_IO_STATUS_TASK_EXCE.val));
                // 空托盘无组托时:若配置启用则按 AUTO_FULL_OUT_MATNR_CODE 自动组托并生成入库单,再继续入库任务逻辑
                if (waitPakin == null) {
                    tryAutoPakinForBarcode(param.getBarcode());
                    waitPakin = waitPakinService.getOne(new LambdaQueryWrapper<WaitPakin>()
                            .eq(WaitPakin::getBarcode, param.getBarcode())
                            .in(WaitPakin::getIoStatus, PakinIOStatus.PAKIN_IO_STATUS_DONE.val, PakinIOStatus.PAKIN_IO_STATUS_TASK_EXCE.val));
                }
            }
            waitPakin = validateWaitPakin(param.getBarcode());
            waitPakinItems = waitPakinItemService.list(
                    new LambdaQueryWrapper<WaitPakinItem>().eq(WaitPakinItem::getPakinId, waitPakin.getId()));
        }
        // 检查其他入库任务类型(用箱号查询,状态为1或2)
        // 注意:盘点入库已单独处理,不再包含在此列表中
        // 检查该托盘号是否已有入库任务(含进行中、已完成),有则直接复用返回,拦截重复提交
        List<Integer> otherInboundTaskTypes = Arrays.asList(
                TaskType.TASK_TYPE_IN.type,
                TaskType.TASK_TYPE_MERGE_IN.type,
@@ -347,48 +372,35 @@
        Task existingInTask = taskService.getOne(new LambdaQueryWrapper<Task>()
                .eq(Task::getBarcode, param.getBarcode())
                .in(Task::getTaskType, otherInboundTaskTypes)
                .in(Task::getTaskStatus, TaskStsType.GENERATE_IN.id, TaskStsType.WCS_EXECUTE_IN.id)
                .orderByDesc(Task::getCreateTime)
                .last("LIMIT 1"));
        if (Objects.nonNull(existingInTask)) {
            log.info("找到匹配的其他入库任务 - 任务编码:{},任务类型:{},箱号:{}",
                    existingInTask.getTaskCode(), existingInTask.getTaskType(), param.getBarcode());
            log.info("找到该托盘号已有入库任务,复用并拦截重复提交 - 任务编码:{},箱号:{},状态:{}",
                    existingInTask.getTaskCode(), param.getBarcode(), existingInTask.getTaskStatus());
            // 检查组托明细是否有订单编码(任务编号)
            List<WaitPakinItem> itemsWithAsnCode = waitPakinItems.stream()
                    .filter(item -> StringUtils.isNotBlank(item.getAsnCode()))
                    .collect(Collectors.toList());
            if (!itemsWithAsnCode.isEmpty()) {
                log.info("组托档有任务编号,使用现有入库任务单号 - 任务编码:{},箱号:{},任务编号数量:{}",
                        existingInTask.getTaskCode(), param.getBarcode(), itemsWithAsnCode.size());
                // 更新入库站点信息(如果与当前申请的站点不同)
                if (StringUtils.isNotBlank(param.getSourceStaNo()) &&
                        !param.getSourceStaNo().equals(existingInTask.getOrgSite())) {
                    log.info("更新入库任务的入库站点 - 任务编码:{},原站点:{},新站点:{}",
                            existingInTask.getTaskCode(), existingInTask.getOrgSite(), param.getSourceStaNo());
                    existingInTask.setOrgSite(param.getSourceStaNo());
                    if (!taskService.updateById(existingInTask)) {
                        log.warn("更新入库任务的入库站点失败 - 任务编码:{}", existingInTask.getTaskCode());
                    }
            // 更新入库站点信息(如果与当前申请的站点不同)
            if (StringUtils.isNotBlank(param.getSourceStaNo()) &&
                    !param.getSourceStaNo().equals(existingInTask.getOrgSite())) {
                log.info("更新入库任务的入库站点 - 任务编码:{},原站点:{},新站点:{}",
                        existingInTask.getTaskCode(), existingInTask.getOrgSite(), param.getSourceStaNo());
                existingInTask.setOrgSite(param.getSourceStaNo());
                if (!taskService.updateById(existingInTask)) {
                    log.warn("更新入库任务的入库站点失败 - 任务编码:{}", existingInTask.getTaskCode());
                }
                // 返回现有入库任务的信息
                InTaskMsgDto msgDto = new InTaskMsgDto();
                msgDto.setWorkNo(existingInTask.getTaskCode());
                msgDto.setTaskId(existingInTask.getId());
                msgDto.setLocNo(existingInTask.getTargLoc());
                msgDto.setSourceStaNo(existingInTask.getOrgSite());
                msgDto.setStaNo(existingInTask.getTargSite());
                return msgDto;
            } else {
                log.info("组托档没有任务编号,继续创建新任务 - 箱号:{}", param.getBarcode());
            }
        } else {
            log.info("未找到匹配的其他入库任务,继续创建新任务 - 箱号:{}", param.getBarcode());
            // 直接返回已有任务信息,不再新建任务
            InTaskMsgDto msgDto = new InTaskMsgDto();
            msgDto.setWorkNo(existingInTask.getTaskCode());
            msgDto.setTaskId(existingInTask.getId());
            msgDto.setLocNo(existingInTask.getTargLoc());
            msgDto.setSourceStaNo(existingInTask.getOrgSite());
            msgDto.setStaNo(existingInTask.getTargSite());
            return msgDto;
        }
        log.info("未找到该托盘号已有入库任务,继续创建新任务 - 箱号:{}", param.getBarcode());
        // 生成任务编码
        String ruleCode = generateTaskCode();
@@ -439,6 +451,14 @@
        if (matnrConfig == null || StringUtils.isBlank(matnrConfig.getVal())) {
            return;
        }
        // 二次确认:已有该 barcode 的组托则直接返回,由外层复用,避免重复请求生成多条入库单
        WaitPakin existing = waitPakinService.getOne(new LambdaQueryWrapper<WaitPakin>()
                .eq(WaitPakin::getBarcode, barcode)
                .in(WaitPakin::getIoStatus, PakinIOStatus.PAKIN_IO_STATUS_DONE.val, PakinIOStatus.PAKIN_IO_STATUS_TASK_EXCE.val));
        if (existing != null) {
            log.info("[RCS入库申请-自动组托] barcode={} 已有组托,跳过自动组托", barcode);
            return;
        }
        Config qtyConfig = configService.getOne(new LambdaQueryWrapper<Config>().eq(Config::getFlag, GlobalConfigCode.AUTO_PAKIN_QTY));
        double autoQty = 1.0;
        if (qtyConfig != null && StringUtils.isNotBlank(qtyConfig.getVal())) {
@@ -470,7 +490,7 @@
            waitPakin = mobileService.mergeItems(param, 1L);
        } catch (Exception e) {
            log.warn("[RCS入库申请-自动组托] 组托失败, barcode={}: {}", barcode, e.getMessage());
            return;
            throw new CoolException("barcode=" + barcode + ": " + e.getMessage());
        }
        String ruleCode = SerialRuleUtils.generateRuleCode(SerialRuleCode.SYS_ASN_ORDER, null);
        if (StringUtils.isBlank(ruleCode)) {
rsf-server/src/main/java/com/vincent/rsf/server/manager/schedules/MaterialAutoSchedules.java
@@ -65,10 +65,10 @@
    private WaitPakinItemService waitPakinItemService;
    /**
     * 定时任务1:指定物料有库存时自动生成全版出库单(每 2 分钟)
     * 定时任务1:指定物料有库存时自动生成全版出库单
     * 配置:AUTO_FULL_OUT_MATNR_CODE(物料编码)、AUTO_FULL_OUT_ENABLED(true 启用)
     */
    @Scheduled(cron = "0 0/2 * * * ?")
    @Scheduled(cron = "0/35 * * * * ?")
    @Transactional(rollbackFor = Exception.class)
    public void autoCreateFullOutOrder() {
        Config enabledConfig = configService.getOne(new LambdaQueryWrapper<Config>().eq(Config::getFlag, GlobalConfigCode.AUTO_FULL_OUT_ENABLED));
@@ -227,11 +227,11 @@
    }
    /**
     * 定时任务3:无订单组托 + 自动生成入库单(仅针对配置物料,每 2 分钟)
     * 定时任务3:无订单组托 + 自动生成入库单(仅针对配置物料,)
     * 先按配置物料与数量做无订单组托,再生成入库单并关联组托明细,便于 RCS 入库闭环。
     * 配置:AUTO_PAKIN_ON_ASN_ENABLED(true)、AUTO_FULL_OUT_MATNR_CODE、AUTO_PAKIN_QTY(数量)
     */
    @Scheduled(cron = "0 0/2 * * * ?")
    @Scheduled(cron = "0/35 * * * * ?")
    @Transactional(rollbackFor = Exception.class)
    public void autoPakinOnInbound() {
        Config enabledConfig = configService.getOne(new LambdaQueryWrapper<Config>().eq(Config::getFlag, GlobalConfigCode.AUTO_PAKIN_ON_ASN_ENABLED));
@@ -288,7 +288,7 @@
            waitPakin = mobileService.mergeItems(param, SYSTEM_USER_ID);
        } catch (Exception e) {
            log.warn("[无订单自动组托] 组托失败: {}", e.getMessage());
            return;
            throw e; // 重新抛出,避免事务被标记 rollback-only 后仍尝试提交导致 UnexpectedRollbackException
        }
        // 2)自动生成入库单(一条明细,配置物料 + 数量)
        String ruleCode = SerialRuleUtils.generateRuleCode(SerialRuleCode.SYS_ASN_ORDER, null);
rsf-server/src/main/java/com/vincent/rsf/server/manager/schedules/ScheduleJobs.java
@@ -18,6 +18,7 @@
import com.vincent.rsf.server.system.service.FieldsItemService;
import com.vincent.rsf.server.system.service.impl.FieldsItemServiceImpl;
import com.vincent.rsf.server.system.utils.SerialRuleUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
@@ -39,6 +40,7 @@
 * @description
 * @create 2025/3/3 15:38
 */
@Slf4j
@Component
public class ScheduleJobs {
@@ -172,7 +174,9 @@
                List<FieldsItem> fieldsList = fieldsItemService.list(new LambdaQueryWrapper<FieldsItem>().eq(FieldsItem::getUuid, orderItem.getFieldsIndex()).last("LIMIT 1"));
                FieldsItem fieldsItem = fieldsList.isEmpty() ? null : fieldsList.get(0);
                if (Objects.nonNull(fieldsItem)) {
                    throw new CoolException("票号:" + fieldsItem.getValue() + "已在收货区,不可推送相当票号数据。请联系管理员!!");
                    // 票号已在收货区时跳过推送,避免定时任务反复抛错;视为已收货,由后续逻辑更新单据状态
                    log.warn("票号:{} 已在收货区,跳过推送", fieldsItem.getValue());
                    return;
                }
            }
        }
rsf-server/src/main/java/com/vincent/rsf/server/manager/schedules/TaskSchedules.java
@@ -598,25 +598,40 @@
            }
            List<TaskItem> taskItems = taskItemService.list(new LambdaQueryWrapper<TaskItem>().eq(TaskItem::getTaskId, task.getId()));
            // 上报ERP暂时注释(/rsf-open-api/erp/report/order)
            // if (task.getTaskType().equals(TaskType.TASK_TYPE_IN.type)) {
            //     for (TaskItem taskItem : taskItems) {
            //         if (Objects.isNull(taskItem.getOrderId())) {
            //             continue;
            //         }
            //         WkOrder order = asnOrderService.getById(taskItem.getOrderId());
            //         if (Objects.isNull(order)) {
            //             continue;
            //         }
            //         // 入库单任务明细上报:优先按 orderItemId(单据明细ID)查,否则按 orderId+matnrId(+fieldsIndex) 查,确保能找到单据明细
            //         WkOrderItem wkOrderItem = null;
            //         if (taskItem.getOrderItemId() != null) {
            //             wkOrderItem = asnOrderItemService.getById(taskItem.getOrderItemId());
            //         }
            //         if (wkOrderItem == null) {
            //             LambdaQueryWrapper<WkOrderItem> qw = new LambdaQueryWrapper<WkOrderItem>()
            //                     .eq(WkOrderItem::getOrderId, order.getId())
            //                     .eq(WkOrderItem::getMatnrId, taskItem.getMatnrId());
            //             if (StringUtils.isNotBlank(taskItem.getFieldsIndex())) {
            //                 qw.eq(WkOrderItem::getFieldsIndex, taskItem.getFieldsIndex());
            //             }
            //             wkOrderItem = asnOrderItemService.getOne(qw);
            //         }
            //         if (Objects.isNull(wkOrderItem)) {
            //             logger.warn("任务历史档处理:单据明细不存在或已完成,跳过上报 - taskId={}, orderId={}, orderItemId={}, matnrId={}, fieldsIndex={}", task.getId(), order.getId(), taskItem.getOrderItemId(), taskItem.getMatnrId(), taskItem.getFieldsIndex());
            //             continue;
            //         }
            //         /**入库单明细上报*/
            //         reportMsgService.reportOrderItem(wkOrderItem);
            //     }
            // } else
            if (task.getTaskType().equals(TaskType.TASK_TYPE_IN.type)) {
                for (TaskItem taskItem : taskItems) {
                    if (Objects.isNull(taskItem.getOrderId())) {
                        continue;
                    }
                    WkOrder order = asnOrderService.getById(taskItem.getOrderId());
                    if (Objects.isNull(order)) {
                        continue;
                    }
                    //入库单任务明细上报
                    WkOrderItem wkOrderItem = asnOrderItemService.getOne(new LambdaQueryWrapper<WkOrderItem>()
                            .eq(WkOrderItem::getOrderId, order.getId())
                            .eq(WkOrderItem::getFieldsIndex, taskItem.getFieldsIndex()));
                    if (Objects.isNull(wkOrderItem)) {
                        throw new CoolException("数据错误,单据明细不存在或已完成!!");
                    }
                    /**入库单明细上报*/
                    reportMsgService.reportOrderItem(wkOrderItem);
                }
                // 入库类型仅转历史,不上报ERP(已注释)
            } else if ((task.getTaskType() >= TaskType.TASK_TYPE_OUT.type && task.getTaskType() <= TaskType.TASK_TYPE_EMPITY_OUT.type)
                    || task.getTaskType().equals(TaskType.TASK_TYPE_PICK_IN.type)) {
                /**判断单据是否完成:波次下发、按单下发(点击下发任务)完成后均将出库单置为完结*/
rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/TaskServiceImpl.java
@@ -2356,12 +2356,12 @@
                locItemWrapper.and(wrapper -> wrapper.isNull(LocItem::getBatch).or().eq(LocItem::getBatch, ""));
            }
            
            // 票号匹配:如果taskItem有票号,则必须匹配;如果taskItem没有票号,则查询票号为null或空字符串的记录
            if (StringUtils.isNotBlank(taskItem.getFieldsIndex())) {
                locItemWrapper.eq(LocItem::getFieldsIndex, taskItem.getFieldsIndex());
            } else {
                locItemWrapper.and(wrapper -> wrapper.isNull(LocItem::getFieldsIndex).or().eq(LocItem::getFieldsIndex, ""));
            }
            // 票号暂不使用,不按票号匹配,只查票号为 null 或空的库位明细
            // if (StringUtils.isNotBlank(taskItem.getFieldsIndex())) {
            //     locItemWrapper.eq(LocItem::getFieldsIndex, taskItem.getFieldsIndex());
            // } else {
            locItemWrapper.and(wrapper -> wrapper.isNull(LocItem::getFieldsIndex).or().eq(LocItem::getFieldsIndex, ""));
            // }
            
            LocItem locItem = locItemService.getOne(locItemWrapper);
            if (Objects.isNull(locItem)) {
rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/WaitPakinServiceImpl.java
@@ -115,8 +115,44 @@
        List<WaitPakinItem> items = new ArrayList<>();
        if (directWaitPakin) {
            // DirectWaitPakin 启用:组托来自收货区,param 中 id 为 WarehouseAreasItem.id
            // DirectWaitPakin 启用:组托可来自收货区(有 id)或无订单组托(id 为 null,仅物料+数量)
            for (PakinItem pakinItem1 : waitPakin.getItems()) {
                if (pakinItem1.getId() == null) {
                    // 无订单组托:无收货区来源,按 matnrId + receiptQty 建组托明细
                    if (pakinItem1.getMatnrId() == null) {
                        throw new CoolException("无订单组托时物料ID不能为空!!");
                    }
                    Matnr matnr = matnrMapper.selectById(pakinItem1.getMatnrId());
                    if (matnr == null) {
                        throw new CoolException("物料信息不存在,物料ID:" + pakinItem1.getMatnrId());
                    }
                    if (pakinItem1.getReceiptQty() == null || pakinItem1.getReceiptQty().compareTo(0.0) <= 0) {
                        throw new CoolException("组托数量不能小于等于零!!");
                    }
                    WaitPakinItem pakinItem = new WaitPakinItem();
                    pakinItem.setPakinId(waitPakin1.getId())
                            .setSource(null)
                            .setAsnId(null)
                            .setAsnCode(null)
                            .setAsnItemId(null)
                            .setIsptResult(null)
                            .setPlatItemId(null)
                            .setPlatOrderCode(null)
                            .setPlatWorkCode(null)
                            .setProjectCode(null)
                            .setBatch(null)
                            .setUnit(matnr.getStockUnit() != null ? matnr.getStockUnit() : "个")
                            .setFieldsIndex(matnr.getFieldsIndex())
                            .setMatnrId(matnr.getId())
                            .setMaktx(matnr.getName())
                            .setUpdateBy(userId)
                            .setCreateBy(userId)
                            .setMatnrCode(matnr.getCode())
                            .setAnfme(QuantityUtils.roundToScale(pakinItem1.getReceiptQty()))
                            .setTrackCode(pakinItem1.getTrackCode());
                    items.add(pakinItem);
                    continue;
                }
                WarehouseAreasItem areaItem = warehouseAreasItemService.getById(pakinItem1.getId());
                if (areaItem == null) {
                    throw new CoolException("物料未送至收货区!!");
@@ -157,6 +193,9 @@
                throw new CoolException("组托明细保存失败!!");
            }
            for (WaitPakinItem pakinItem : items) {
                if (pakinItem.getSource() == null) {
                    continue; // 无订单组托无收货区来源,不更新收货区
                }
                WarehouseAreasItem one = warehouseAreasItemService.getOne(new LambdaQueryWrapper<WarehouseAreasItem>().eq(WarehouseAreasItem::getId, pakinItem.getSource()));
                if (one == null) {
                    throw new CoolException("收货区数据错误!!");