Explorar el Código

docs: 补充未排舱筛选项权限设计

jackson hace 1 semana
padre
commit
139a87ddb9

+ 61 - 0
docs/superpowers/specs/2026-07-14-mcp-export-pending-outbound-orders-design.md

@@ -12,9 +12,11 @@
 ## 目标
 
 - 新增 MCP 工具 `export_pending_outbound_orders`。
+- 新增独立工具 `list_pending_outbound_export_filter_options`,让 AI 先把用户提供的筛选名称解析成当前员工有权使用的值。
 - 筛选参数名称、业务含义、类型和多选传参方式与 `outbound/add.html` 完全一致。
 - 复用 `ExportPendOutboundOrderLogic` 和 `OutboundModel::getPendOutboundOrder()`,确保导出内容与页面一致。
 - 使用当前 MCP 员工身份、公司和数据权限,不接受调用方传入身份或权限字段。
+- 筛选项列表沿用 add 页面当前员工权限,禁止返回其他公司、其他客户或当前员工无权使用的筛选值。
 - 成功时返回可下载的文件 URL;失败时返回明确业务错误且不终止 MCP 会话。
 
 ## 参数契约
@@ -57,6 +59,18 @@
 Gateway 只接受白名单中的参数。`property_2`、`export_type`、`is_sync`、`company_id`、`operator_id`、
 `is_super`、`timezone` 等页面内部字段或身份字段不放入 MCP Schema。
 
+### 名称解析规则
+
+用户提供产品、仓库、国家、进口商等业务名称时,AI 必须先调用
+`list_pending_outbound_export_filter_options` 查询当前员工有权使用的选项,再把返回的 `value` 原样传给
+导出工具。不得根据名称、历史调用或示例猜测 ID。
+
+- `product_id`、`product_type_id`、`order_warehouse_id`、`importer_id` 使用筛选项返回的 ID。
+- `receiver_country` 使用国家选项返回的国家代码。
+- `packing_type` 使用货物类型选项返回的名称值;多选时以英文逗号拼接。
+- 商品属性使用选项返回的字段名,并把对应字段设置为 `Y`。
+- `status` 使用订单状态选项返回的整数值。
+
 ## 架构与数据流
 
 ### Python Gateway
@@ -72,6 +86,20 @@ Gateway 只接受白名单中的参数。`property_2`、`export_type`、`is_sync
 
 本地 stdio 与公网 Gateway 都注册该工具;是否展示和允许调用继续由 fmsoperate 的动态工具注册表控制。
 
+### 筛选项工具
+
+新增 `ListPendingOutboundExportFilterOptionsTool`:
+
+- 工具名为 `list_pending_outbound_export_filter_options`。
+- 路由为 `/mcp/tools/listPendingOutboundExportFilterOptions`。
+- 必填 `filter_type`,可选 `keyword`、`page`、`limit`;查询物流产品时可选传
+  `product_type_id`,实现与 add 页面相同的产品分类联动。
+- `filter_type` 支持 `product_type`、`product`、`warehouse`、`country`、`importer`、
+  `packing_type`、`order_status`、`goods_attribute`、`is_remove`。
+- 返回统一的 `records`,每项包含可直接用于导出参数的 `value`、显示名称 `label` 和辅助识别字段
+  `code`;分页信息放在 `meta`。
+- 工具说明明确要求返回值只能用于对应导出字段,不得跨字段使用或猜测未返回的 ID。
+
 ### fmsoperate
 
 新增 MCP Controller 路由、Validate 规则和专用 Logic。Controller 仅接收请求、调用 Logic 并返回统一响应。
@@ -87,6 +115,32 @@ Logic 执行以下步骤:
 为了保证与后台页面权限一致,MCP 工具必须复用后台“新增排舱/导出未排舱订单”对应菜单权限,并继续经过
 动态工具白名单校验。不得仅依赖工具注册表绕过后台菜单权限。
 
+### 筛选项权限边界
+
+筛选项工具必须先通过动态工具白名单和后台“新增排舱/导出未排舱订单”菜单权限,再查询选项。所有查询只
+使用 MCP token 恢复出的服务端会话,禁止接收 `company_id`、`admin_id`、`is_super`、`account_type`、
+`country_auth`、`platform_company_id`、`platform_customer_id` 等外部身份或权限参数。
+
+各类筛选项必须复用 add 页面的数据来源和权限口径:
+
+| `filter_type` | add 页面数据来源与权限要求 | 返回 `value` |
+|---|---|---|
+| `product_type` | `OutboundTypeModel::getAllOutboundType()`,按当前公司和模型现有条件返回可用排舱分类 | 分类 ID |
+| `product` | `ProductModel::getProductList()`,保留当前公司或平台客户产品配置限制,以及页面对产品状态的展示条件;传 `product_type_id` 时只能在已授权产品内继续过滤 | 产品 ID |
+| `warehouse` | `OrderModel::getSortWarehouse()`,保留当前公司、账号和仓库权限排序/过滤规则 | 仓库 ID |
+| `country` | `getTcAddCountryCode()` 可用国家范围;非超级管理员且账号类型包含 `3` 时,继续按当前员工 `country_auth` 取交集;无授权返回空数组 | 国家代码 |
+| `importer` | `fms_importer.company_id=session('company_id')` 且 `status=1`,并保留页面的“由EAC提供”选项 | 进口商 ID,或 `-1` |
+| `packing_type` | `dictionary('fms_packing_type')` 当前有效字典值 | 页面提交的货物类型名称 |
+| `order_status` | `OrderModel::getOperateOrderStatus()` 中 add 页面实际展示的 `0/10/20/30/40` | 整数状态值 |
+| `goods_attribute` | `GoodsModel::getGoodsAttr()` 及页面附加的“无属性” | 对应属性字段名,如 `is_battery` 或 `no_property` |
+| `is_remove` | 页面固定选项“是/否” | 整数 `1/0` |
+
+不得为了分页或关键字搜索改成绕过上述业务方法的无权限全表查询。关键字过滤必须在授权结果集内执行;先按
+关键字查全表再补权限过滤也不允许。对于静态字典项,仍需先通过工具和菜单权限后才返回。
+
+筛选项查询只负责减少错误输入,不授予数据权限。导出接口收到筛选值后,仍由
+`OutboundModel::getPendOutboundOrder()` 和现有导出链路再次按当前员工上下文限制实际订单数据。
+
 ## 返回与错误处理
 
 成功响应示例:
@@ -126,6 +180,8 @@ JSON-RPC 协议错误,不中断会话。
 - 验证 `is_remove=0` 不会被当作空值删除。
 - 验证 stdio、公网工具列表、动态启用和调用转发。
 - 验证成功 URL、业务错误及缺失 URL 的处理。
+- 验证筛选项工具的全部 `filter_type`、产品分类联动、分页结构和名称解析引导。
+- 验证筛选项工具不接受任何身份或权限覆盖参数。
 
 ### PHP
 
@@ -134,6 +190,11 @@ JSON-RPC 协议错误,不中断会话。
 - 断言服务端固定导出类型、同步标记和会话身份字段,外部无法覆盖。
 - 断言员工菜单权限、动态工具状态和公司数据权限继续生效。
 - 断言成功响应返回 `file_url`,无数据和导出失败返回明确错误。
+- 使用 A/B 两个公司或客户上下文验证选项隔离,A 员工不得看到 B 公司的产品、仓库、进口商和排舱分类。
+- 验证平台客户只能看到其产品配置范围内的物流产品。
+- 验证受限头程账号只看到 `country_auth` 允许的目的国,无国家授权时返回空列表。
+- 验证 `product_type_id` 只能缩小已授权产品范围,不能通过分类联动扩大权限。
+- 验证无菜单权限、工具被停用和伪造身份字段均被拒绝并记录访问日志。
 
 ## 非目标