From 11323345b516186011148aa424eb40d336c6a2d6 Mon Sep 17 00:00:00 2001 From: my_local_kimi_dev2 Date: Sat, 19 Sep 2026 22:26:32 +0800 Subject: [PATCH] =?UTF-8?q?[fix]=20=E5=87=BA=E5=85=A5=E5=BA=93=E5=B7=A5?= =?UTF-8?q?=E5=85=B7=E5=A4=8D=E6=9F=A5=E4=BF=AE=E5=A4=8D=EF=BC=9A=E8=B0=83?= =?UTF-8?q?=E6=8B=A8=E5=BE=85=E5=8A=9E=E6=BC=8F=E6=9C=AC=E4=BA=BA=E4=BF=AE?= =?UTF-8?q?=E6=94=B9/=E5=BE=85=E5=8A=9E=E8=A1=8C=E4=B8=B2=E4=BD=8D/?= =?UTF-8?q?=E8=B0=83=E6=8B=A8=E6=B4=BE=E7=94=9F=E5=85=A5=E5=BA=93=E5=8D=95?= =?UTF-8?q?=E5=87=AD=E8=AF=81=E5=9B=9E=E6=9F=A5=20(REQ-20260919-0003)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../doc/agent-mcp-tools-reference.md | 10 ++-- .../doc/warehouse-in-mcp-tools-design.md | 3 ++ .../doc/warehouse-todo-mcp-tool-design.md | 12 +++-- .../agent/entity/m2/WarehouseAllocation.java | 26 ++++++++++ .../mapper/m2/WarehouseAllocationMapper.java | 7 +++ .../iiv/agent/mcp/tool/WarehouseInTools.java | 43 ++++++++++++++-- .../agent/mcp/tool/WarehouseTodoTools.java | 16 +++--- .../agent/mcp/tool/WarehouseInToolsTest.java | 48 +++++++++++++++++- .../mcp/tool/WarehouseTodoToolsTest.java | 49 +++++++++++++++++++ 9 files changed, 195 insertions(+), 19 deletions(-) create mode 100644 xiaoqu-agent-service/src/main/java/me/iiv/agent/entity/m2/WarehouseAllocation.java create mode 100644 xiaoqu-agent-service/src/main/java/me/iiv/agent/mapper/m2/WarehouseAllocationMapper.java diff --git a/xiaoqu-agent-service/doc/agent-mcp-tools-reference.md b/xiaoqu-agent-service/doc/agent-mcp-tools-reference.md index a0c0460..b0a9cb9 100644 --- a/xiaoqu-agent-service/doc/agent-mcp-tools-reference.md +++ b/xiaoqu-agent-service/doc/agent-mcp-tools-reference.md @@ -540,7 +540,9 @@ - 返回 `record/sceneType/warehouses/items/processes/files/relatedOutOrder`。 - `items` 按物料定义、物料索引和仓库汇总;`includeSerials` 默认 false,`serialLimit` 默认 100、上限 500, 同时返回 `serialTotal/serialTruncated`。 -- `files.application/files.confirmation` 仅返回 `id/fileName/bucket/objectKey`。 +- `files.application/files.confirmation/files.allocationAudit` 仅返回 `id/fileName/bucket/objectKey`。 + 调拨派生的入库单其申请附件与审批流图片挂在调拨单维度(`aggregate_id=调拨单id, in_out_allocation_type=2`), + 已按 `allocationOrderNo` 自动回查合并:申请附件并入 `application`,审批流图片(fileType=3)在 `allocationAudit`。 - `relatedOutOrder` 仅给出关联出库单号和查询工具名,不内嵌出库单内容。 详细设计见 `warehouse-in-mcp-tools-design.md`。 @@ -555,8 +557,10 @@ - 分类:warehouse | 类型:读 - 数据来源:m2 流程视图 `warehouse_out_process` / `warehouse_in_process` / `warehouse_allocation_process` - 权限:待办是"人"的语义,只按流程表 `user_id = 当前登录人` 过滤,不叠加代理商行级权限。 -- 待办判定:`user_id = 当前登录人 AND soft_delete = 0 AND operation_type IN (0,1,2)` - (3=纯日志排除)。`todoOnly` 默认 true 再限定 `schedule_state=1` 真待办。 +- 待办判定:`user_id = 当前登录人 AND soft_delete = 0`;出入库视图排除 + `operation_type=3`(纯日志),调拨视图不过滤 `operation_type`(其 3=本人修改是真实待办)。 + `todoOnly` 默认 true 再限定 `schedule_state=1` 真待办,且该条件下推到第二阶段组装查询, + 代表行不串到已办审批行。 - 同一笔调拨最多以调拨审批/确认、出库执行、入库执行 3 个视角出现,不去重; 派生自调拨的真实出入库单带 `derivedFrom="allocation"`,可按 `allocationOrderNo` 归并。 - 分页实现:先全量查符合条件流程行按单据 id 内存去重 → 内存分页 → 按页 id 批量查视图行组装,防 N+1。 diff --git a/xiaoqu-agent-service/doc/warehouse-in-mcp-tools-design.md b/xiaoqu-agent-service/doc/warehouse-in-mcp-tools-design.md index b834408..7aa1763 100644 --- a/xiaoqu-agent-service/doc/warehouse-in-mcp-tools-design.md +++ b/xiaoqu-agent-service/doc/warehouse-in-mcp-tools-design.md @@ -36,6 +36,9 @@ - 可选 `serials` 以及完整 `serialTotal`、`serialTruncated`; - `schedule_state=2` 的已处理流程; - `file_type=0/1` 的申请、确认入库附件,仅返回 OSS 对象定位,不生成签名 URL; + 调拨派生入库单的申请附件(fileType=0)与审批流图片(fileType=3)挂在调拨单维度 + (`aggregate_id=调拨单id, in_out_allocation_type=2`),详情按 `allocationOrderNo` 回查合并: + 前者并入 `files.application`,后者在 `files.allocationAudit`; - `relatedOutOrder`:仅出库单号和建议调用的 `warehouse.out.detail.query` 工具名。 ## 4. 权限与安全 diff --git a/xiaoqu-agent-service/doc/warehouse-todo-mcp-tool-design.md b/xiaoqu-agent-service/doc/warehouse-todo-mcp-tool-design.md index b6c0ce6..06fa2e4 100644 --- a/xiaoqu-agent-service/doc/warehouse-todo-mcp-tool-design.md +++ b/xiaoqu-agent-service/doc/warehouse-todo-mcp-tool-design.md @@ -17,11 +17,13 @@ 流程视图一行 = 单据 × 处理人 × 待办环节。待办判定: ``` -user_id = 当前登录人 AND soft_delete = 0 AND operation_type IN (0, 1, 2) +user_id = 当前登录人 AND soft_delete = 0 ``` -- `operation_type=3` 为"驳回或修改记录",纯日志不需要待办,排除(出入库视图); - 调拨视图 `operation_type=4`(撤销)同为纯日志,也被 IN (0,1,2) 排除。 +- 出入库视图再排除 `operation_type=3`("驳回或修改记录",纯日志不需要待办); + 调拨视图**不过滤** `operation_type`——其 `3=本人修改` 是真实待办环节(调拨被驳回后 + 申请人需修改重新提交),老系统调拨待办接口同样不过滤(`warehouseAllocationProcessListByPage` + 仅按 user_id + soft_delete 过滤)。`4=撤销` 在调拨视图也视为可操作环节一并返回。 - 不按代理商过滤:待办是"人"的语义,不是代理商数据权限语义。 老系统出入库待办同样只按 `user_id` 过滤(`input.setUserId(user.getId())`), 只有调拨待办显式 `eq("user_id", user.getId())` 且代理行级过滤被注释掉, @@ -34,8 +36,8 @@ user_id = 当前登录人 AND soft_delete = 0 AND operation_type IN (0, 1, 2) | 0 | 审批流操作 | 审批流操作 | | 1 | 确认出库 / 确认入库 | 确认入库操作 | | 2 | 本人修改(需申请人待办) | 确认出库操作 | -| 3 | 驳回或修改操作(纯日志,排除) | 本人修改 | -| 4 | — | 撤销(纯日志,排除) | +| 3 | 驳回或修改操作(纯日志,排除) | 本人修改(真实待办,保留) | +| 4 | — | 撤销(保留) | `schedule_state`:0=无需待办,1=待办,2=已办。 diff --git a/xiaoqu-agent-service/src/main/java/me/iiv/agent/entity/m2/WarehouseAllocation.java b/xiaoqu-agent-service/src/main/java/me/iiv/agent/entity/m2/WarehouseAllocation.java new file mode 100644 index 0000000..707dd86 --- /dev/null +++ b/xiaoqu-agent-service/src/main/java/me/iiv/agent/entity/m2/WarehouseAllocation.java @@ -0,0 +1,26 @@ +package me.iiv.agent.entity.m2; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +import java.io.Serializable; + +/** 调拨/采购/划转单 warehouse_allocation(m2,只读):op_type 0调拨 1采购 2划转。 */ +@Getter +@Setter +@ToString +@TableName("warehouse_allocation") +public class WarehouseAllocation implements Serializable { + + @TableId(value = "id", type = IdType.AUTO) + private Long id; + private Integer softDelete; + @TableField("allocation_order_no") + private String allocationOrderNo; + private Integer opType; +} diff --git a/xiaoqu-agent-service/src/main/java/me/iiv/agent/mapper/m2/WarehouseAllocationMapper.java b/xiaoqu-agent-service/src/main/java/me/iiv/agent/mapper/m2/WarehouseAllocationMapper.java new file mode 100644 index 0000000..3e51478 --- /dev/null +++ b/xiaoqu-agent-service/src/main/java/me/iiv/agent/mapper/m2/WarehouseAllocationMapper.java @@ -0,0 +1,7 @@ +package me.iiv.agent.mapper.m2; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import me.iiv.agent.entity.m2.WarehouseAllocation; + +public interface WarehouseAllocationMapper extends BaseMapper { +} diff --git a/xiaoqu-agent-service/src/main/java/me/iiv/agent/mcp/tool/WarehouseInTools.java b/xiaoqu-agent-service/src/main/java/me/iiv/agent/mcp/tool/WarehouseInTools.java index 1500350..7e7b804 100644 --- a/xiaoqu-agent-service/src/main/java/me/iiv/agent/mcp/tool/WarehouseInTools.java +++ b/xiaoqu-agent-service/src/main/java/me/iiv/agent/mcp/tool/WarehouseInTools.java @@ -3,11 +3,13 @@ package me.iiv.agent.mcp.tool; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.toolkit.support.SFunction; import me.iiv.agent.entity.m0.User; +import me.iiv.agent.entity.m2.WarehouseAllocation; import me.iiv.agent.entity.m2.WarehouseIn; import me.iiv.agent.entity.m2.WarehouseInAuditProcess; import me.iiv.agent.entity.m2.WarehouseInDetail; import me.iiv.agent.entity.m2.WarehouseInOutPic; import me.iiv.agent.entity.m2.WarehouseType; +import me.iiv.agent.mapper.m2.WarehouseAllocationMapper; import me.iiv.agent.mapper.m2.WarehouseInAuditProcessMapper; import me.iiv.agent.mapper.m2.WarehouseInDetailMapper; import me.iiv.agent.mapper.m2.WarehouseInMapper; @@ -49,6 +51,7 @@ public class WarehouseInTools implements ToolObject { private static final int DEFAULT_SERIAL_LIMIT = 100; private static final int MAX_SERIAL_LIMIT = 500; private static final int IN_TYPE = 0; + private static final int ALLOCATION_TYPE = 2; private static final ZoneId BUSINESS_ZONE = ZoneId.of("Asia/Shanghai"); private static final Pattern DATE_PATTERN = Pattern.compile("^\\d{4}-\\d{2}-\\d{2}$"); private static final DateTimeFormatter DATE_FORMATTER = @@ -71,6 +74,7 @@ public class WarehouseInTools implements ToolObject { final WarehouseInDetailMapper detailMapper; final WarehouseInAuditProcessMapper processMapper; final WarehouseInOutPicMapper picMapper; + final WarehouseAllocationMapper allocationMapper; final WarehouseTypeMapper warehouseTypeMapper; final PermissionService permissionService; @@ -78,12 +82,14 @@ public class WarehouseInTools implements ToolObject { WarehouseInDetailMapper detailMapper, WarehouseInAuditProcessMapper processMapper, WarehouseInOutPicMapper picMapper, + WarehouseAllocationMapper allocationMapper, WarehouseTypeMapper warehouseTypeMapper, PermissionService permissionService) { this.warehouseInMapper = warehouseInMapper; this.detailMapper = detailMapper; this.processMapper = processMapper; this.picMapper = picMapper; + this.allocationMapper = allocationMapper; this.warehouseTypeMapper = warehouseTypeMapper; this.permissionService = permissionService; } @@ -180,6 +186,8 @@ public class WarehouseInTools implements ToolObject { @Tool(name = "warehouse.in.detail.query", description = "按 id 或入库单号查询入库单详情。" + "行级权限与 warehouse.in.list.query 完全一致,不存在与无权访问统一返回 NOT_FOUND_OR_FORBIDDEN。" + "返回主单、场景类型、仓库、按物料和仓库汇总的明细、可选 SN、已处理流程和申请/确认附件;调拨入库单可直接查询。" + + "files 含 application(申请附件)/confirmation(入库确认)/allocationAudit(调拨审批流图片);" + + "调拨派生的入库单其申请附件与审批图片挂在调拨单维度,已按调拨单号自动回查合并。" + "关联出库仅返回索引,不内嵌内容。includeSerials 默认 false;serialLimit 默认100、上限500。") public Map warehouseInDetailQuery( @ToolParam(required = false, description = "入库单主键 id,与 inOrderNo 至少传一个") Long id, @@ -222,11 +230,19 @@ public class WarehouseInTools implements ToolObject { .eq(WarehouseInAuditProcess::getScheduleState, 2) .orderByAsc(WarehouseInAuditProcess::getOperationTime) .orderByAsc(WarehouseInAuditProcess::getId)); - List pics = picMapper.selectList(new LambdaQueryWrapper() + List pics = new ArrayList<>(picMapper.selectList(new LambdaQueryWrapper() .eq(WarehouseInOutPic::getSoftDelete, 0) .eq(WarehouseInOutPic::getAggregateId, order.getId()) .eq(WarehouseInOutPic::getInOutAllocationType, IN_TYPE) - .orderByAsc(WarehouseInOutPic::getId)); + .orderByAsc(WarehouseInOutPic::getId))); + // 调拨派生的入库单:申请附件(fileType=0)和审批流图片(fileType=3)挂在调拨单维度 + // (aggregate_id=调拨单id, in_out_allocation_type=2),按调拨单号回查补齐,否则这两类凭证恒为空。 + List allocationPics = allocationPics(order); + if (!allocationPics.isEmpty()) { + Set seen = pics.stream().map(WarehouseInOutPic::getId).collect(Collectors.toSet()); + allocationPics.stream().filter(pic -> pic.getId() == null || seen.add(pic.getId())) + .forEach(pics::add); + } WarehouseType type = order.getWarehouseTypeId() == null ? null : warehouseTypeMapper.selectById(order.getWarehouseTypeId()); @@ -242,6 +258,7 @@ public class WarehouseInTools implements ToolObject { Map files = new LinkedHashMap<>(); files.put("application", fileRecords(pics, 0)); files.put("confirmation", fileRecords(pics, 1)); + files.put("allocationAudit", fileRecords(pics, 3)); result.put("files", files); result.put("relatedOutOrder", relatedOutOrder(order)); return result; @@ -413,8 +430,26 @@ public class WarehouseInTools implements ToolObject { }).collect(Collectors.toList()); } - private Map relatedOutOrder(WarehouseIn order) { - if (!StringUtils.hasText(order.getOutOrderNo())) return null; + /** 调拨派生入库单的申请/审批凭证挂在调拨单维度,按调拨单号回查;调拨单不存在或无调拨单号时返回空。 */ + private List allocationPics(WarehouseIn order) { + if (!StringUtils.hasText(order.getAllocationOrderNo())) { + return List.of(); + } + WarehouseAllocation allocation = allocationMapper.selectOne(new LambdaQueryWrapper() + .eq(WarehouseAllocation::getSoftDelete, 0) + .eq(WarehouseAllocation::getAllocationOrderNo, order.getAllocationOrderNo()) + .last("LIMIT 1")); + if (allocation == null) { + return List.of(); + } + return picMapper.selectList(new LambdaQueryWrapper() + .eq(WarehouseInOutPic::getSoftDelete, 0) + .eq(WarehouseInOutPic::getAggregateId, allocation.getId()) + .eq(WarehouseInOutPic::getInOutAllocationType, ALLOCATION_TYPE) + .orderByAsc(WarehouseInOutPic::getId)); + } + + private Map relatedOutOrder(WarehouseIn order) { if (!StringUtils.hasText(order.getOutOrderNo())) return null; Map related = new LinkedHashMap<>(); related.put("outOrderNo", order.getOutOrderNo()); related.put("queryTool", "warehouse.out.detail.query"); diff --git a/xiaoqu-agent-service/src/main/java/me/iiv/agent/mcp/tool/WarehouseTodoTools.java b/xiaoqu-agent-service/src/main/java/me/iiv/agent/mcp/tool/WarehouseTodoTools.java index 41bf66d..e6cb4b2 100644 --- a/xiaoqu-agent-service/src/main/java/me/iiv/agent/mcp/tool/WarehouseTodoTools.java +++ b/xiaoqu-agent-service/src/main/java/me/iiv/agent/mcp/tool/WarehouseTodoTools.java @@ -40,7 +40,7 @@ public class WarehouseTodoTools implements ToolObject { private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").withZone(BUSINESS_ZONE); - /** 待办环节只保留可办理的操作类型,3(驳回或修改记录)为纯日志,排除。 */ + /** 待办环节只保留可办理的操作类型,3(驳回或修改记录)为纯日志,排除。仅适用于出入库视图,调拨视图不过滤。 */ private static final List TODO_OPERATION_TYPES = List.of(0, 1, 2); private static final Map SCHEDULE_STATE_TEXT = Map.of( @@ -76,8 +76,9 @@ public class WarehouseTodoTools implements ToolObject { @Tool(name = "warehouse.todo.list.query", description = "分页查询当前用户的仓库待办,统一老系统 /myWarehouseOut/needToBeDealtWith、" + "/myWarehouseIn/needToBeDealtWith、/warehouseAllocation/warehouseAllocationProcessListByPage 三个待办接口。" + "数据来自 m2 流程视图 warehouse_out_process / warehouse_in_process / warehouse_allocation_process:" - + "待办语义 = 视图里存在当前用户的任务行(user_id = 当前登录人 AND soft_delete = 0 AND operation_type IN (0,1,2)," - + "3=纯日志排除),不按代理商过滤。同一笔调拨最多以调拨审批/确认、出库执行、入库执行 3 个视角出现,不去重;" + + "待办语义 = 视图里存在当前用户的任务行(user_id = 当前登录人 AND soft_delete = 0," + + "出库/入库视图排除 operation_type=3 纯日志;调拨视图不过滤 operation_type,其 3=本人修改 是真实待办),不按代理商过滤。" + + "同一笔调拨最多以调拨审批/确认、出库执行、入库执行 3 个视角出现,不去重;" + "派生自调拨的真实出入库单带 derivedFrom=\"allocation\" 标记。" + "docType:out=出库单 in=入库单 allocation=调拨/采购/划转单(按 op_type 区分) all=全部,默认 all。" + "todoOnly 默认 true(只返回 schedule_state=1 真待办),false 返回当前用户参与过的全部单据。" @@ -174,11 +175,11 @@ public class WarehouseTodoTools implements ToolObject { } private List allocationStubs(Long userId, boolean onlyTodo, Integer orderState, String orderNo) { + // 老系统调拨待办接口不过滤 operation_type(3=本人修改 是真实待办环节),与出入库视图口径不同 LambdaQueryWrapper wrapper = new LambdaQueryWrapper() .select(WarehouseAllocationProcess::getWarehouseAllocationId, WarehouseAllocationProcess::getApplyDate) .eq(WarehouseAllocationProcess::getSoftDelete, 0) .eq(WarehouseAllocationProcess::getUserId, userId) - .in(WarehouseAllocationProcess::getOperationType, TODO_OPERATION_TYPES) .eq(onlyTodo, WarehouseAllocationProcess::getScheduleState, 1) .eq(orderState != null, WarehouseAllocationProcess::getOrderState, orderState) .like(StringUtils.hasText(orderNo), WarehouseAllocationProcess::getAllocationOrderNo, @@ -206,6 +207,7 @@ public class WarehouseTodoTools implements ToolObject { .eq(WarehouseOutProcess::getSoftDelete, 0) .eq(WarehouseOutProcess::getUserId, userId) .in(WarehouseOutProcess::getOperationType, TODO_OPERATION_TYPES) + .eq(onlyTodo, WarehouseOutProcess::getScheduleState, 1) .in(WarehouseOutProcess::getWarehouseOutId, ids) .orderByAsc(WarehouseOutProcess::getId)).stream() .collect(Collectors.groupingBy(WarehouseOutProcess::getWarehouseOutId, @@ -221,6 +223,7 @@ public class WarehouseTodoTools implements ToolObject { .eq(WarehouseInProcess::getSoftDelete, 0) .eq(WarehouseInProcess::getUserId, userId) .in(WarehouseInProcess::getOperationType, TODO_OPERATION_TYPES) + .eq(onlyTodo, WarehouseInProcess::getScheduleState, 1) .in(WarehouseInProcess::getWarehouseInId, ids) .orderByAsc(WarehouseInProcess::getId)).stream() .collect(Collectors.groupingBy(WarehouseInProcess::getWarehouseInId, @@ -235,7 +238,7 @@ public class WarehouseTodoTools implements ToolObject { new LambdaQueryWrapper() .eq(WarehouseAllocationProcess::getSoftDelete, 0) .eq(WarehouseAllocationProcess::getUserId, userId) - .in(WarehouseAllocationProcess::getOperationType, TODO_OPERATION_TYPES) + .eq(onlyTodo, WarehouseAllocationProcess::getScheduleState, 1) .in(WarehouseAllocationProcess::getWarehouseAllocationId, ids) .orderByAsc(WarehouseAllocationProcess::getId)).stream() .collect(Collectors.groupingBy(WarehouseAllocationProcess::getWarehouseAllocationId, @@ -263,7 +266,8 @@ public class WarehouseTodoTools implements ToolObject { } } - /** 取代表当前待办环节的行:优先 schedule_state=1 的真待办行,否则取首行(已办/无需待办)。 */ + /** 取代表当前待办环节的行:onlyTodo 时 assemble 已下推 schedule_state=1,取首行即可; + * 否则优先 schedule_state=1 的真待办行,再退回首行(已办/无需待办)。 */ private static T pickPending(List rows, boolean onlyTodo) { if (onlyTodo) { return rows.get(0); diff --git a/xiaoqu-agent-service/src/test/java/me/iiv/agent/mcp/tool/WarehouseInToolsTest.java b/xiaoqu-agent-service/src/test/java/me/iiv/agent/mcp/tool/WarehouseInToolsTest.java index 0b5725d..0ba747a 100644 --- a/xiaoqu-agent-service/src/test/java/me/iiv/agent/mcp/tool/WarehouseInToolsTest.java +++ b/xiaoqu-agent-service/src/test/java/me/iiv/agent/mcp/tool/WarehouseInToolsTest.java @@ -3,11 +3,13 @@ package me.iiv.agent.mcp.tool; import com.baomidou.mybatisplus.core.MybatisConfiguration; import com.baomidou.mybatisplus.core.metadata.TableInfoHelper; import me.iiv.agent.entity.m0.User; +import me.iiv.agent.entity.m2.WarehouseAllocation; import me.iiv.agent.entity.m2.WarehouseIn; import me.iiv.agent.entity.m2.WarehouseInAuditProcess; import me.iiv.agent.entity.m2.WarehouseInDetail; import me.iiv.agent.entity.m2.WarehouseInOutPic; import me.iiv.agent.entity.m2.WarehouseType; +import me.iiv.agent.mapper.m2.WarehouseAllocationMapper; import me.iiv.agent.mapper.m2.WarehouseInAuditProcessMapper; import me.iiv.agent.mapper.m2.WarehouseInDetailMapper; import me.iiv.agent.mapper.m2.WarehouseInMapper; @@ -49,6 +51,7 @@ class WarehouseInToolsTest { @Mock WarehouseInDetailMapper detailMapper; @Mock WarehouseInAuditProcessMapper processMapper; @Mock WarehouseInOutPicMapper picMapper; + @Mock WarehouseAllocationMapper allocationMapper; @Mock WarehouseTypeMapper warehouseTypeMapper; @Mock PermissionService permissionService; @@ -61,9 +64,10 @@ class WarehouseInToolsTest { TableInfoHelper.initTableInfo(assistant, WarehouseInDetail.class); TableInfoHelper.initTableInfo(assistant, WarehouseInAuditProcess.class); TableInfoHelper.initTableInfo(assistant, WarehouseInOutPic.class); + TableInfoHelper.initTableInfo(assistant, WarehouseAllocation.class); TableInfoHelper.initTableInfo(assistant, WarehouseType.class); tools = new WarehouseInTools(warehouseInMapper, detailMapper, processMapper, picMapper, - warehouseTypeMapper, permissionService); + allocationMapper, warehouseTypeMapper, permissionService); User user = new User(); user.setId(7L); AuthContextHolder.set(new AuthContext(null, user)); @@ -153,12 +157,54 @@ class WarehouseInToolsTest { Map files = (Map) result.get("files"); assertEquals(1, ((List) files.get("application")).size()); assertEquals(1, ((List) files.get("confirmation")).size()); + assertEquals(0, ((List) files.get("allocationAudit")).size()); @SuppressWarnings("unchecked") Map related = (Map) result.get("relatedOutOrder"); assertEquals("CK-001", related.get("outOrderNo")); assertEquals("warehouse.out.detail.query", related.get("queryTool")); } + @Test + void detailMergesAllocationDimensionPicsForDerivedInOrder() { + WarehouseIn order = order(); + when(warehouseInMapper.selectList(any())).thenReturn(List.of(order)); + when(detailMapper.selectList(any())).thenReturn(List.of(detail("SN-1", 1))); + when(processMapper.selectList(any())).thenReturn(List.of()); + WarehouseAllocation allocation = new WarehouseAllocation(); + allocation.setId(40L); + allocation.setAllocationOrderNo("DB-001"); + when(allocationMapper.selectOne(any())).thenReturn(allocation); + WarehouseInOutPic own = pic(1L, 0); + WarehouseInOutPic allocApply = pic(2L, 0); + allocApply.setInOutAllocationType(2); + WarehouseInOutPic allocAudit = pic(3L, 3); + allocAudit.setInOutAllocationType(2); + when(picMapper.selectList(any())).thenReturn(List.of(own), List.of(allocApply, allocAudit)); + + Map result = tools.warehouseInDetailQuery(20L, null, false, null); + + @SuppressWarnings("unchecked") + Map files = (Map) result.get("files"); + assertEquals(2, ((List) files.get("application")).size()); + assertEquals(0, ((List) files.get("confirmation")).size()); + assertEquals(1, ((List) files.get("allocationAudit")).size()); + } + + @Test + void detailSkipsAllocationLookupWhenOrderIsNotAllocationDerived() { + WarehouseIn order = order(); + order.setAllocationOrderNo(null); + when(warehouseInMapper.selectList(any())).thenReturn(List.of(order)); + when(detailMapper.selectList(any())).thenReturn(List.of()); + when(processMapper.selectList(any())).thenReturn(List.of()); + when(picMapper.selectList(any())).thenReturn(List.of()); + + Map result = tools.warehouseInDetailQuery(20L, null, false, null); + + assertEquals(0, ((List) ((Map) result.get("files")).get("application")).size()); + verifyNoInteractions(allocationMapper); + } + @Test void detailDoesNotReturnSerialRowsUnlessRequested() { WarehouseIn order = order(); diff --git a/xiaoqu-agent-service/src/test/java/me/iiv/agent/mcp/tool/WarehouseTodoToolsTest.java b/xiaoqu-agent-service/src/test/java/me/iiv/agent/mcp/tool/WarehouseTodoToolsTest.java index 3efe0de..1e9fff8 100644 --- a/xiaoqu-agent-service/src/test/java/me/iiv/agent/mcp/tool/WarehouseTodoToolsTest.java +++ b/xiaoqu-agent-service/src/test/java/me/iiv/agent/mcp/tool/WarehouseTodoToolsTest.java @@ -142,6 +142,55 @@ class WarehouseTodoToolsTest { assertEquals("确认出库", records.get(0).get("operationTypeText")); } + @Test + void todoOnlyTruePushesScheduleStateDownToAssembleQuery() { + WarehouseOutProcess stub = new WarehouseOutProcess(); + stub.setWarehouseOutId(20L); + stub.setApplyDate(date(2026, 9, 10)); + WarehouseOutProcess pendingRow = outRow(20L, null); + when(outProcessMapper.selectList(any())).thenReturn(List.of(stub), List.of(pendingRow)); + + tools.warehouseTodoListQuery("out", true, null, null, 1, 20); + + var captor = org.mockito.ArgumentCaptor.forClass( + com.baomidou.mybatisplus.core.conditions.Wrapper.class); + verify(outProcessMapper, times(2)).selectList(captor.capture()); + String assembleSql = captor.getAllValues().get(1).getSqlSegment(); + assertTrue(assembleSql.contains("schedule_state"), assembleSql); + + tools.warehouseTodoListQuery("out", false, null, null, 1, 20); + var captor2 = org.mockito.ArgumentCaptor.forClass( + com.baomidou.mybatisplus.core.conditions.Wrapper.class); + verify(outProcessMapper, times(4)).selectList(captor2.capture()); + String assembleSqlAll = captor2.getAllValues().get(3).getSqlSegment(); + assertTrue(!assembleSqlAll.contains("schedule_state"), assembleSqlAll); + } + + @Test + void allocationTodoIncludesModifyOperationTypeAndSkipsOperationTypeFilter() { + WarehouseAllocationProcess stub = new WarehouseAllocationProcess(); + stub.setWarehouseAllocationId(40L); + stub.setApplyDate(date(2026, 9, 12)); + WarehouseAllocationProcess modifyRow = allocationRow(40L); + modifyRow.setOperationType(3); + when(allocationProcessMapper.selectList(any())).thenReturn(List.of(stub), List.of(modifyRow)); + + Map result = tools.warehouseTodoListQuery("allocation", true, null, null, 1, 20); + + assertEquals(1, result.get("total")); + @SuppressWarnings("unchecked") + List> records = (List>) result.get("records"); + assertEquals(1, records.size()); + assertEquals(3, records.get(0).get("operationType")); + assertEquals("本人修改", records.get(0).get("operationTypeText")); + + var captor = org.mockito.ArgumentCaptor.forClass( + com.baomidou.mybatisplus.core.conditions.Wrapper.class); + verify(allocationProcessMapper, times(2)).selectList(captor.capture()); + String stageOneSql = captor.getAllValues().get(0).getSqlSegment(); + assertTrue(!stageOneSql.contains("operation_type"), stageOneSql); + } + @Test void listRejectsInvalidDocType() { assertThrows(IllegalArgumentException.class, -- 2.54.0