# 还车应结款 · 运维转交 Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** 在还车应结款运维段增加「转交办理人」(办理人主动转 + 主管代转),并放开待审批/审批中费用明细只读查看。 **Architecture:** 列表仍在 `03-还车应结款.jsx`;「去处理」进入 `VehicleReturnSettlementHandle.jsx`。`VehicleReturnSettlementApp.jsx` 持有 `opsHandlerByKey` / `opsTransferLogsByKey` 覆盖层,转交后回写列表办理人展示。演示身份用 Handle 页顶栏切换(办理人 / 主管 / 其他人)。 **Tech Stack:** React + Ant Design(Modal / Select / Input.TextArea / Button / Drawer);本地种子人员清单;无真实 API。 **Spec:** [docs/superpowers/specs/2026-07-22-vehicle-return-settlement-ops-transfer-design.md](../specs/2026-07-22-vehicle-return-settlement-ops-transfer-design.md) --- ## File map | Path | Responsibility | |------|----------------| | `src/prototypes/vehicle-return-settlement/ops-transfer-demo.js` | 运维同事种子、演示身份、转交校验纯函数 | | `src/prototypes/vehicle-return-settlement/VehicleReturnSettlementApp.jsx` | 覆盖层状态;把 row + callbacks 传给 Handle | | `src/prototypes/vehicle-return-settlement/VehicleReturnSettlementHandle.jsx` | 转交弹窗、记录抽屉、审批中运维只读、身份切换 | | `src/prototypes/oneos-web-finance/pages/03-还车应结款.jsx` | `opsHandler` 种子、`resolveDeptHandler`、费用明细入口规则 | | `src/prototypes/vehicle-return-settlement/.spec/ops-transfer.md` | 业务判定规格 | | `src/prototypes/vehicle-return-settlement/.spec/requirements-prd.md` | AutoPRD | | `src/prototypes/vehicle-return-settlement/annotation-source.json` | 标注目录 PRD 节点 | | `src/resources/prd/vehicle-return-settlement-autoprd.md` | 资源侧 PRD 副本 | --- ### Task 1: 转交校验与演示数据模块 **Files:** - Create: `src/prototypes/vehicle-return-settlement/ops-transfer-demo.js` - [ ] **Step 1: 新增种子与纯函数** ```js /** 运维转交 · 原型演示数据与校验(未接真实 API) */ export var OPS_COLLEAGUES = [ { id: 'ops-chen', name: '陈还车', active: true }, { id: 'ops-zhou', name: '周还车', active: true }, { id: 'ops-liu', name: '刘还车', active: true }, { id: 'ops-wang', name: '王五', active: true }, { id: 'ops-left', name: '已离职-赵工', active: false } ]; export var DEMO_IDENTITIES = [ { id: 'handler', label: '当前办理人' }, { id: 'supervisor', label: '运维主管' }, { id: 'other', label: '其他运维(无权限)' } ]; export function canInitiateOpsTransfer(identityId, currentHandlerName, viewerName) { if (identityId === 'supervisor') return true; if (identityId === 'handler') return true; return false; } export function isWholeOrderTransferBlocked(approvalStatus) { return String(approvalStatus || '') === '审批完成'; } /** 整单待审批/审批中:运维费用只读(不可保存/提交/撤回) */ export function isOpsFeeReadOnly(approvalStatus) { var st = String(approvalStatus || ''); return st === '待审批' || st === '审批中'; } export function listTransferCandidates(colleagues, currentHandlerName) { return (colleagues || []).filter(function (p) { return p.active && p.name !== currentHandlerName; }); } export function validateOpsTransfer(input) { var errors = []; if (!input || !input.toName) errors.push('请选择新办理人'); if (!input || !String(input.reason || '').trim()) errors.push('请填写转交原因'); if (input && input.toName && input.toName === input.fromName) errors.push('不能转交给自己'); if (input && isWholeOrderTransferBlocked(input.approvalStatus)) errors.push('审批完成后不可转交'); return errors; } export function buildTransferLog(input) { return { at: input.at || new Date().toISOString().slice(0, 16).replace('T', ' '), operator: input.operator, fromName: input.fromName, toName: input.toName, reason: String(input.reason || '').trim(), opsStatus: input.opsStatus || '', approvalStatus: input.approvalStatus || '' }; } ``` - [ ] **Step 2: 手动核验校验函数(Node 一行)** Run: ```bash node -e " const m = require('./src/prototypes/vehicle-return-settlement/ops-transfer-demo.js'); console.log(m.validateOpsTransfer({ fromName:'陈还车', toName:'周还车', reason:'离职', approvalStatus:'审批中' })); console.log(m.validateOpsTransfer({ fromName:'陈还车', toName:'陈还车', reason:'x', approvalStatus:'待提交' })); console.log(m.isOpsFeeReadOnly('审批中'), m.isWholeOrderTransferBlocked('审批完成')); " ``` Expected: 第一行 `[]`;第二行含「不能转交给自己」;第三行 `true true`。 若该包为 ESM 无 `require`,改为在文件末尾临时加 `typeof module !== 'undefined' && (module.exports = {...})` 双端导出,或用 `node --input-type=module -e "import(...)"`。 - [ ] **Step 3: Commit(仅当用户要求提交时执行)** ```bash git add src/prototypes/vehicle-return-settlement/ops-transfer-demo.js git commit -m "$(cat <<'EOF' feat(vehicle-return-settlement): add ops transfer demo helpers EOF )" ``` --- ### Task 2: 列表 — opsHandler 与费用明细入口 **Files:** - Modify: `src/prototypes/oneos-web-finance/pages/03-还车应结款.jsx` - [ ] **Step 1: 列表组件接收覆盖层 props** 在列表 `Component` 顶部读取: ```js var opsHandlerByKey = (props && props.opsHandlerByKey) || {}; ``` - [ ] **Step 2: 种子行增加 `opsHandler`** 每条 mock 行增加: ```js opsHandler: '<与该行 returnPerson 相同的姓名>', opsTransferLogs: [] ``` 例如 `returnPerson: '陈还车'` 的行写 `opsHandler: '陈还车'`。 - [ ] **Step 3: 改 `resolveDeptHandler`** ```js case 'submitOperation': return displayCellText( (row.key && opsHandlerByKey[row.key]) || row.opsHandler || row.returnPerson ); ``` 注意:`resolveDeptHandler` 若在 `useMemo`/闭包外,需能读到最新 `opsHandlerByKey`(不要写死进仅挂载一次的 columns memo;columns 的 `useMemo` 依赖补上 `opsHandlerByKey`)。 - [ ] **Step 4: 改费用明细入口可见性** 将操作列中: ```js var showFeeDetail = !(st === '待审批' || st === '审批中' || st === '审批完成'); ``` 改为: ```js var showFeeDetail = st !== '审批完成'; ``` 同步改文件内嵌需求注释/说明字符串里「待审批、审批中、审批完成时不显示费用明细」为:「审批完成时不显示费用明细;待审批/审批中可进入查看,运维段不可编辑」。 - [ ] **Step 5: 浏览器验收列表** 打开 `/prototypes/vehicle-return-settlement`: 1. 找审批状态为「待审批」或「审批中」的行 → 操作里应有「去处理」 2. 「审批完成」行 → 无「去处理」 3. 运维组标签办理人显示为 `opsHandler` / `returnPerson` - [ ] **Step 6: Commit(用户要求时)** --- ### Task 3: App 覆盖层打通列表 ↔ Handle **Files:** - Modify: `src/prototypes/vehicle-return-settlement/VehicleReturnSettlementApp.jsx` - [ ] **Step 1: 增加覆盖层 state 并下发** ```jsx var _handlers = useState({}); var opsHandlerByKey = _handlers[0]; var setOpsHandlerByKey = _handlers[1]; var _logs = useState({}); var opsTransferLogsByKey = _logs[0]; var setOpsTransferLogsByKey = _logs[1]; function applyOpsTransfer(rowKey, payload) { setOpsHandlerByKey(function (p) { var n = {}; for (var k in p) n[k] = p[k]; n[rowKey] = payload.toName; return n; }); setOpsTransferLogsByKey(function (p) { var n = {}; for (var k in p) n[k] = p[k]; var prev = n[rowKey] ? n[rowKey].slice() : []; prev.unshift(payload.log); n[rowKey] = prev; return n; }); } // list: React.createElement(ReturnSettlementListPage, { opsHandlerByKey: opsHandlerByKey, onGoHandle: function (row) { setSelectedRow(row || null); setView('handle'); } }); // handle: React.createElement(VehicleReturnSettlementHandle, { row: selectedRow, opsHandler: selectedRow && (opsHandlerByKey[selectedRow.key] || selectedRow.opsHandler || selectedRow.returnPerson), opsTransferLogs: selectedRow ? (opsTransferLogsByKey[selectedRow.key] || selectedRow.opsTransferLogs || []) : [], onOpsTransfer: function (payload) { if (!selectedRow) return; applyOpsTransfer(selectedRow.key, payload); }, onBack: function () { setSelectedRow(null); setView('list'); } }); ``` - [ ] **Step 2: 确认进入 Handle 后顶栏能读到办理人姓名**(下一 Task 做 UI) --- ### Task 4: Handle 页 — 转交 / 只读 / 记录 **Files:** - Modify: `src/prototypes/vehicle-return-settlement/VehicleReturnSettlementHandle.jsx` - Modify: `src/prototypes/vehicle-return-settlement/styles/index.css`(弹窗/记录样式按需) - [ ] **Step 1: 引入 demo 模块与本地状态** ```js import { OPS_COLLEAGUES, DEMO_IDENTITIES, canInitiateOpsTransfer, isWholeOrderTransferBlocked, isOpsFeeReadOnly, listTransferCandidates, validateOpsTransfer, buildTransferLog } from './ops-transfer-demo'; ``` State:`demoIdentity`(默认 `handler`)、`transferOpen`、`transferTo`、`transferReason`、`logsOpen`。 从 `props.row` 读 `approvalStatus`;运维段本地 `opsStatus`(待提交/已提交,可由 deptData 初始);`opsHandler` 以 props 为准。 - [ ] **Step 2: 顶栏演示身份切换** 在页头增加 Select:`当前办理人` / `运维主管` / `其他运维(无权限)`,便于验收权限。 - [ ] **Step 3: 运维区操作按钮按规则渲染** ```js var approvalStatus = (props.row && props.row.approvalStatus) || '待提交'; var feeReadOnly = isOpsFeeReadOnly(approvalStatus); var transferBlocked = isWholeOrderTransferBlocked(approvalStatus); var canTransfer = !transferBlocked && canInitiateOpsTransfer(demoIdentity); // header actions: // - feeReadOnly: 无保存/提交/撤回;canTransfer 则显示转交 // - !feeReadOnly && opsStatus===已提交: 撤回 + 转交 // - !feeReadOnly && opsStatus===待提交: 保存 + 提交 + 转交 // 另:始终提供「转交记录」文字按钮(有日志或可空状态) ``` 保存/提交在 `feeReadOnly` 时不渲染;表格金额列只读(禁用 Input 或纯文本)。 - [ ] **Step 4: 转交 Modal** 字段:当前办理人只读、新办理人 Select(`listTransferCandidates(OPS_COLLEAGUES, opsHandler)`)、原因 TextArea 必填。 确认时: ```js var errors = validateOpsTransfer({ fromName: opsHandler, toName: transferTo, reason: transferReason, approvalStatus: approvalStatus }); if (errors.length) { message.error(errors[0]); return; } var log = buildTransferLog({ operator: demoIdentity === 'supervisor' ? '运维主管' : opsHandler, fromName: opsHandler, toName: transferTo, reason: transferReason, opsStatus: opsStatus, approvalStatus: approvalStatus }); if (typeof props.onOpsTransfer === 'function') { props.onOpsTransfer({ toName: transferTo, log: log }); } message.success('转交成功'); setTransferOpen(false); setTransferTo(undefined); setTransferReason(''); ``` 二次确认可用 `Modal.confirm` 包一层,文案:`确认后将由【{transferTo}】继续处理本单运维段`。 - [ ] **Step 5: 转交记录 Drawer** 列表展示 `props.opsTransferLogs`:时间、操作人、原→新、原因、运维状态、整单状态。 - [ ] **Step 6: 浏览器验收(对照规格 §7)** 1. 身份=办理人,待提交单:转交成功 → 返回列表运维办理人变新人 2. 已提交:可转;转后身份切主管/新人可撤(非审批中时) 3. 身份=其他人:无转交按钮 4. 待审批/审批中:可进明细;运维费用不可编;仍可转交 5. 审批完成:列表无「去处理」 6. 原因空 / 转给自己:报错 - [ ] **Step 7: Commit(用户要求时)** --- ### Task 5: 业务规格 + AutoPRD + 标注 **Files:** - Create: `src/prototypes/vehicle-return-settlement/.spec/ops-transfer.md` - Create: `src/prototypes/vehicle-return-settlement/.spec/requirements-prd.md` - Create: `src/prototypes/vehicle-return-settlement/annotation-source.json`(若尚无则按 sibling 原型模板) - Create: `src/resources/prd/vehicle-return-settlement-autoprd.md` - Modify: Handle/index 挂 `PrototypeAnnotationHost`(若入口尚未挂标注) - [ ] **Step 1: 写 `.spec/ops-transfer.md`** 从设计规格摘判定表:发起人、接收人、状态、只读、入口、字段 `opsHandler` / `opsTransferLogs`、未接真实 API。含优先级表。 - [ ] **Step 2: 写 `requirements-prd.md`** 按 oneos-autoprd 结构:背景、用户、故事(忙不过来 / 离职代转 / 审批中只读查看)、正逆向、验收;链到 `ops-transfer.md`。**不写**表结构/接口字段代码名(规格文件可写字段名)。 - [ ] **Step 3: annotation-source + resources PRD 同步** 目录节点「产品需求说明(PRD)」markdown 与 `.spec/requirements-prd.md` 一致;`src/resources/prd/vehicle-return-settlement-autoprd.md` 同文。 - [ ] **Step 4: 导航同步** ```bash npm run nav:sync -- --prototype vehicle-return-settlement --note "运维部办理人转交;审批中费用明细只读可进" ``` - [ ] **Step 5: Commit(用户要求时)** --- ### Task 6: 不纳入本期(显式跳过) - `03-还车应结款.jsx` 内 `ComponentFeeDetail` 历史残留页:不接转交(入口未使用) - 列表批量转交、操作列转交 - 其他部门转交 --- ## Spec coverage | Spec 节 | Task | |---------|------| | §3.1 发起人 | 1 `canInitiateOpsTransfer`, 4 身份切换 | | §3.2 接收人 | 1 `listTransferCandidates` | | §3.3 / 3.3.1 状态与只读入口 | 2 列表入口, 4 `isOpsFeeReadOnly` | | §3.4 转交效果 | 3 覆盖层, 4 Modal | | §3.5 原因必填 | 1 `validateOpsTransfer`, 4 | | §4 弹窗与记录 | 4 | | §5 数据模型 | 2 `opsHandler`, 3 logs | | §6 文档 | 5 | | §7 验收 | 2 Step5, 4 Step6 | ## Placeholder / 一致性自检 - 无 TBD;字段名统一 `opsHandler` / `opsTransferLogs` / `onOpsTransfer` - 主路径为 Handle,非 `ComponentFeeDetail` - 提交仅在用户明确要求时执行 --- ## Execution handoff Plan complete and saved to `docs/superpowers/plans/2026-07-22-vehicle-return-settlement-ops-transfer.md`. **两种执行方式:** 1. **Subagent-Driven(推荐)** — 每任务派生子代理,任务间复审 2. **Inline Execution** — 本会话按 executing-plans 逐任务推进 你要哪一种?