Files
OneOS1.2/docs/superpowers/plans/2026-07-22-vehicle-return-settlement-ops-transfer.md
王冕 b14425da5a feat(vehicle-return-settlement): 运维办理人转交与审批中费用明细只读
支持办理人/主管转交运维段任务并记录原因;待审批与审批中可进费用明细但运维费用只读,审批完成不可进入。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-22 17:52:40 +08:00

14 KiB
Raw Blame History

还车应结款 · 运维转交 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.jsxVehicleReturnSettlementApp.jsx 持有 opsHandlerByKey / opsTransferLogsByKey 覆盖层,转交后回写列表办理人展示。演示身份用 Handle 页顶栏切换(办理人 / 主管 / 其他人)。

Tech Stack: React + Ant DesignModal / Select / Input.TextArea / Button / Drawer本地种子人员清单无真实 API。

Spec: docs/superpowers/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: 新增种子与纯函数

/** 运维转交 · 原型演示数据与校验(未接真实 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:

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仅当用户要求提交时执行
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 顶部读取:

var opsHandlerByKey = (props && props.opsHandlerByKey) || {};
  • Step 2: 种子行增加 opsHandler

每条 mock 行增加:

opsHandler: '<与该行 returnPerson 相同的姓名>',
opsTransferLogs: []

例如 returnPerson: '陈还车' 的行写 opsHandler: '陈还车'

  • Step 3: 改 resolveDeptHandler
case 'submitOperation':
	return displayCellText(
		(row.key && opsHandlerByKey[row.key]) || row.opsHandler || row.returnPerson
	);

注意:resolveDeptHandler 若在 useMemo/闭包外,需能读到最新 opsHandlerByKey(不要写死进仅挂载一次的 columns memocolumns 的 useMemo 依赖补上 opsHandlerByKey)。

  • Step 4: 改费用明细入口可见性

将操作列中:

var showFeeDetail = !(st === '待审批' || st === '审批中' || st === '审批完成');

改为:

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 并下发

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 模块与本地状态

import {
	OPS_COLLEAGUES,
	DEMO_IDENTITIES,
	canInitiateOpsTransfer,
	isWholeOrderTransferBlocked,
	isOpsFeeReadOnly,
	listTransferCandidates,
	validateOpsTransfer,
	buildTransferLog
} from './ops-transfer-demo';

StatedemoIdentity(默认 handler)、transferOpentransferTotransferReasonlogsOpen

props.rowapprovalStatus;运维段本地 opsStatus(待提交/已提交,可由 deptData 初始);opsHandler 以 props 为准。

  • Step 2: 顶栏演示身份切换

在页头增加 Select当前办理人 / 运维主管 / 其他运维(无权限),便于验收权限。

  • Step 3: 运维区操作按钮按规则渲染
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

字段:当前办理人只读、新办理人 SelectlistTransferCandidates(OPS_COLLEAGUES, opsHandler))、原因 TextArea 必填。

确认时:

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: 导航同步
npm run nav:sync -- --prototype vehicle-return-settlement --note "运维部办理人转交;审批中费用明细只读可进"
  • Step 5: Commit用户要求时

Task 6: 不纳入本期(显式跳过)

  • 03-还车应结款.jsxComponentFeeDetail 历史残留页:不接转交(入口未使用)
  • 列表批量转交、操作列转交
  • 其他部门转交

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
  • 主路径为 HandleComponentFeeDetail
  • 提交仅在用户明确要求时执行

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 逐任务推进

你要哪一种?