diff --git a/docs/superpowers/plans/2026-07-30-oa-money-field.md b/docs/superpowers/plans/2026-07-30-oa-money-field.md
new file mode 100644
index 00000000..c560f45f
--- /dev/null
+++ b/docs/superpowers/plans/2026-07-30-oa-money-field.md
@@ -0,0 +1,661 @@
+# OA Money Field (金额 + 大写) 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:** Deliver FormCreate `money` control so designers can drag「金额」, configure uppercase display and condition participation, and initiators/approvers see numeric input with optional RMB uppercase (DingTalk-aligned).
+
+**Architecture:** Mirror `dept` / `relatedApproval` four-layer FcDesigner extension: `OaMoneyField.vue` + DragRule + `setupOaMoneyField` on both main app and `oa-fc-host`. Persist `number | null` only; uppercase is display-only via `toRmbUpper`. Backend validates non-negative finite numbers on start. Condition eligibility already includes `money`.
+
+**Tech Stack:** FastAPI · pytest · Vue 3 · FormCreate / `@form-create/designer` · Element Plus · existing `money-format` helpers · Node `node:test` + `tsx` for `rmb-upper` unit tests
+
+**Spec:** `docs/superpowers/specs/2026-07-30-oa-money-field-design.md`
+
+---
+
+## File map
+
+| Path | Responsibility |
+|------|----------------|
+| Create: `web-admin/src/utils/rmb-upper.ts` | `number` → 人民币大写纯函数 |
+| Create: `web-admin/src/utils/rmb-upper.test.ts` | Node test runner cases |
+| Create: `web-admin/src/components/oa-approval/OaMoneyField.vue` | Runtime / designer preview 金额控件 |
+| Create: `web-admin/src/utils/oa-money-field-rule.ts` | DragRule(`showUpper` 默认 true) |
+| Create: `web-admin/src/utils/oa-form-create-money.ts` | `setupOaMoneyField` 注册 |
+| Modify: `web-admin/src/main.ts` | 调用 `setupOaMoneyField` |
+| Modify: `web-admin/src/oa-fc-host/main.ts` | 同上 |
+| Modify: `web-admin/src/utils/oa-approval-print.ts` | 打印金额:数字 + 可选大写 |
+| Create: `backend/app/services/oa_money_field.py` | `validate_money_form_values` |
+| Create: `backend/tests/test_oa_money_field.py` | 校验单测 |
+| Modify: `backend/app/services/oa_engine.py` | `start_instance` 挂入校验 |
+| Modify: `backend/app/services/oa_related_approval.py` | 关联摘要格式化金额为千分位数字 |
+| Modify: `docs/superpowers/specs/2026-07-30-oa-money-field-design.md` | 状态更新;去掉文末多余 fence |
+
+**已具备、本计划不改:** `CONDITION_ELIGIBLE_TYPES`(前后端均含 `money`)、`oa_publish.ALLOWED_CONTROL_TYPES`、`ConditionRuleEditor` 对 `money` 的数值 coerce。
+
+---
+
+### Task 1: RMB uppercase utility (TDD)
+
+**Files:**
+- Create: `web-admin/src/utils/rmb-upper.ts`
+- Create: `web-admin/src/utils/rmb-upper.test.ts`
+
+- [ ] **Step 1: Write failing tests**
+
+```ts
+// web-admin/src/utils/rmb-upper.test.ts
+import { describe, it } from 'node:test'
+import assert from 'node:assert/strict'
+import { toRmbUpper } from './rmb-upper'
+
+describe('toRmbUpper', () => {
+ it('zero', () => {
+ assert.equal(toRmbUpper(0), '零元整')
+ })
+ it('integer', () => {
+ assert.equal(toRmbUpper(1), '壹元整')
+ assert.equal(toRmbUpper(10), '壹拾元整')
+ assert.equal(toRmbUpper(100), '壹佰元整')
+ assert.equal(toRmbUpper(1234), '壹仟贰佰叁拾肆元整')
+ })
+ it('jiao fen', () => {
+ assert.equal(toRmbUpper(1.23), '壹元贰角叁分')
+ assert.equal(toRmbUpper(0.5), '伍角')
+ assert.equal(toRmbUpper(0.05), '伍分')
+ assert.equal(toRmbUpper(1.2), '壹元贰角')
+ assert.equal(toRmbUpper(1.03), '壹元零叁分')
+ })
+ it('nullish and invalid', () => {
+ assert.equal(toRmbUpper(null), '')
+ assert.equal(toRmbUpper(undefined), '')
+ assert.equal(toRmbUpper(Number.NaN), '')
+ assert.equal(toRmbUpper(-1), '')
+ })
+ it('too large', () => {
+ assert.equal(toRmbUpper(1e12), '金额过大')
+ })
+ it('near max has yuan jiao fen', () => {
+ const s = toRmbUpper(999999999999.99)
+ assert.ok(s.includes('元'))
+ assert.ok(s.includes('角') || s.includes('分'))
+ assert.notEqual(s, '金额过大')
+ })
+})
+```
+
+- [ ] **Step 2: Run tests — expect FAIL**
+
+```bash
+cd web-admin && pnpm exec tsx --test src/utils/rmb-upper.test.ts
+```
+
+Expected: FAIL(模块不存在或 `toRmbUpper` 未定义)
+
+- [ ] **Step 3: Implement `toRmbUpper`**
+
+Create `web-admin/src/utils/rmb-upper.ts` with a complete algorithm (no pseudo-code left in the file):
+
+- Digits: `零壹贰叁肆伍陆柒捌玖`;section units `万/亿`;int units `拾佰仟`.
+- Convert via fen: `Math.round(value * 100)`; reject `value > 999999999999.99` → `金额过大`.
+- `null` / `undefined` / `NaN` / negative / non-finite → `''`.
+- `0` → `零元整`.
+- Integer yuan with no jiao/fen → append `整`.
+- Only jiao/fen (no yuan) → e.g. `伍角` / `伍分` without forcing `零元`.
+- `1.03` → include `零` before `叁分` as in bank-style wording.
+
+Export: `export function toRmbUpper(value: number | null | undefined): string`
+
+- [ ] **Step 4: Run tests — expect PASS**
+
+```bash
+cd web-admin && pnpm exec tsx --test src/utils/rmb-upper.test.ts
+```
+
+Expected: PASS
+
+- [ ] **Step 5: Commit**
+
+```bash
+git add web-admin/src/utils/rmb-upper.ts web-admin/src/utils/rmb-upper.test.ts
+git commit -m "$(cat <<'EOF'
+feat(web-admin): add RMB uppercase converter for OA money field
+
+Pure function with node:test coverage for zero, jiao/fen, and range limits.
+EOF
+)"
+```
+
+---
+
+### Task 2: Backend money form_data validation (TDD)
+
+**Files:**
+- Create: `backend/app/services/oa_money_field.py`
+- Create: `backend/tests/test_oa_money_field.py`
+- Modify: `backend/app/services/oa_engine.py`(`start_instance` 中 `validate_dept_form_values` 附近)
+
+- [ ] **Step 1: Write failing tests**
+
+```python
+# backend/tests/test_oa_money_field.py
+import unittest
+
+from app.services.oa_money_field import validate_money_form_values
+
+
+class TestValidateMoneyFormValues(unittest.TestCase):
+ def test_ok_number(self):
+ schema = {"rule": [{"type": "money", "field": "amt", "title": "金额", "$required": True}]}
+ self.assertEqual(validate_money_form_values(schema, {"amt": 1234.56}), [])
+
+ def test_ok_int(self):
+ schema = {"rule": [{"type": "money", "field": "amt"}]}
+ self.assertEqual(validate_money_form_values(schema, {"amt": 10}), [])
+
+ def test_ok_null_optional(self):
+ schema = {"rule": [{"type": "money", "field": "amt", "$required": False}]}
+ self.assertEqual(validate_money_form_values(schema, {"amt": None}), [])
+ self.assertEqual(validate_money_form_values(schema, {}), [])
+
+ def test_required_missing(self):
+ schema = {
+ "rule": [{"type": "money", "field": "amt", "title": "报销金额", "$required": True}]
+ }
+ errs = validate_money_form_values(schema, {})
+ self.assertTrue(any("报销金额" in e for e in errs))
+
+ def test_negative_rejected(self):
+ schema = {"rule": [{"type": "money", "field": "amt"}]}
+ errs = validate_money_form_values(schema, {"amt": -1})
+ self.assertTrue(len(errs) >= 1)
+
+ def test_string_number_accepted(self):
+ schema = {"rule": [{"type": "money", "field": "amt"}]}
+ self.assertEqual(validate_money_form_values(schema, {"amt": "12.5"}), [])
+
+ def test_bool_rejected(self):
+ schema = {"rule": [{"type": "money", "field": "amt"}]}
+ errs = validate_money_form_values(schema, {"amt": True})
+ self.assertTrue(len(errs) >= 1)
+
+ def test_ignore_other_types(self):
+ schema = {"rule": [{"type": "input", "field": "x", "$required": True}]}
+ self.assertEqual(validate_money_form_values(schema, {}), [])
+```
+
+- [ ] **Step 2: Run — expect FAIL**
+
+```bash
+cd backend && source venv/bin/activate && python -m pytest tests/test_oa_money_field.py -v
+```
+
+Expected: FAIL(`oa_money_field` 不存在)
+
+- [ ] **Step 3: Implement validator**
+
+Create `backend/app/services/oa_money_field.py` following the same private helpers pattern as `oa_dept_select.py` (`_walk_rules`, `_schema_rules`, `_is_required`, `_field_label`).
+
+Rules:
+
+- `MAX_MONEY = 999999999999.99`
+- Normalize: `int`/`float` OK;数字字符串去逗号后 `float`;`bool` 拒绝;其它类型拒绝
+- `None` / `""` → 空;required 时空 → `「{label}」为必填金额字段`
+- `< 0` → `「{label}」金额不能为负数`
+- `> MAX_MONEY` / 非有限 → 对应错误文案
+- 仅处理 `type == "money"`
+
+Export: `validate_money_form_values(schema, form_data) -> List[str]`
+
+- [ ] **Step 4: Wire into `start_instance`**
+
+In `backend/app/services/oa_engine.py` immediately after `validate_dept_form_values`:
+
+```python
+from app.services.oa_money_field import validate_money_form_values
+
+money_errs = validate_money_form_values(schema, data)
+if money_errs:
+ raise OaEngineError(money_errs[0])
+```
+
+- [ ] **Step 5: Run tests — expect PASS**
+
+```bash
+cd backend && source venv/bin/activate && python -m pytest tests/test_oa_money_field.py -v
+```
+
+- [ ] **Step 6: Commit**
+
+```bash
+git add backend/app/services/oa_money_field.py backend/tests/test_oa_money_field.py backend/app/services/oa_engine.py
+git commit -m "$(cat <<'EOF'
+feat(backend): validate OA money field values on instance start
+
+Reject negatives, non-finite values, and missing required money controls.
+EOF
+)"
+```
+
+---
+
+### Task 3: DragRule + FormCreate registration
+
+**Files:**
+- Create: `web-admin/src/utils/oa-money-field-rule.ts`
+- Create: `web-admin/src/utils/oa-form-create-money.ts`
+- Create: `web-admin/src/components/oa-approval/OaMoneyField.vue`(最小 stub,Task 4 充实)
+- Modify: `web-admin/src/main.ts`
+- Modify: `web-admin/src/oa-fc-host/main.ts`
+
+- [ ] **Step 1: Add DragRule**
+
+```ts
+// web-admin/src/utils/oa-money-field-rule.ts
+let _uidSeq = 0
+function uniqueId() {
+ const num = 370 + ++_uidSeq
+ return (
+ 'F' +
+ Math.random().toString(36).slice(3, 6) +
+ Number(`${Date.now()}`).toString(36) +
+ num.toString(36)
+ )
+}
+
+export const moneyFieldDragRule = {
+ menu: 'main' as const,
+ icon: 'icon-number',
+ label: '金额',
+ name: 'money',
+ input: true,
+ mask: true,
+ validate: ['number', 'string'],
+ event: ['change'],
+ rule() {
+ return {
+ type: 'money',
+ field: uniqueId(),
+ title: '金额(元)',
+ info: '',
+ $required: false,
+ props: {
+ showUpper: true,
+ placeholder: '请输入金额'
+ }
+ }
+ },
+ props() {
+ return [
+ {
+ type: 'switch',
+ field: 'showUpper',
+ title: '显示大写金额',
+ value: true,
+ props: { activeText: '是', inactiveText: '否' }
+ },
+ {
+ type: 'input',
+ field: 'placeholder',
+ title: '占位提示',
+ value: '请输入金额'
+ },
+ {
+ type: 'input',
+ field: 'formCreateInfo',
+ title: '提示信息'
+ }
+ ]
+ }
+}
+```
+
+若 `icon-number` 在设计器不显示,改为仓库中部门/数字类已用过的 `fc-icon` 名(打开 `@form-create/designer` 图标表核对)。
+
+- [ ] **Step 2: Stub component + setup helper**
+
+```vue
+
+
+ 金额
+
+
+```
+
+```ts
+// web-admin/src/utils/oa-form-create-money.ts
+import FcDesigner from '@form-create/designer'
+import OaMoneyField from '@/components/oa-approval/OaMoneyField.vue'
+import { moneyFieldDragRule } from '@/utils/oa-money-field-rule'
+
+const FLAG = '__oaMoneyFieldConfigured'
+
+export function setupOaMoneyField(formCreate?: { component?: (name: string, c: unknown) => void }) {
+ if ((FcDesigner as any)[FLAG]) return
+ FcDesigner.component('money', OaMoneyField)
+ if (typeof formCreate?.component === 'function') {
+ formCreate.component('money', OaMoneyField)
+ }
+ if (typeof FcDesigner.addDragRule === 'function') {
+ FcDesigner.addDragRule(moneyFieldDragRule)
+ }
+ ;(FcDesigner as any)[FLAG] = true
+}
+```
+
+- [ ] **Step 3: Register both hosts**
+
+In `web-admin/src/main.ts` after `setupOaDeptSelect`:
+
+```ts
+import { setupOaMoneyField } from '@/utils/oa-form-create-money'
+setupOaMoneyField(FcDesigner.formCreate)
+```
+
+Same pair of lines in `web-admin/src/oa-fc-host/main.ts`.
+
+- [ ] **Step 4: Smoke check(人工)**
+
+```bash
+cd web-admin && pnpm dev
+```
+
+打开审批设计器 →「2 表单设计」→ 左侧「组件」出现「金额」。
+
+- [ ] **Step 5: Commit**
+
+```bash
+git add web-admin/src/utils/oa-money-field-rule.ts \
+ web-admin/src/utils/oa-form-create-money.ts \
+ web-admin/src/components/oa-approval/OaMoneyField.vue \
+ web-admin/src/main.ts \
+ web-admin/src/oa-fc-host/main.ts
+git commit -m "$(cat <<'EOF'
+feat(web-admin): register OA money field in FormCreate designer
+
+Add DragRule and dual-host setup so 金额 appears in the component palette.
+EOF
+)"
+```
+
+---
+
+### Task 4: Runtime `OaMoneyField.vue`
+
+**Files:**
+- Modify: `web-admin/src/components/oa-approval/OaMoneyField.vue`
+
+- [ ] **Step 1: Replace stub with full component**
+
+Behavior per spec §5; structure mirrors `OaDeptSelect` (`disabled` / designer host):
+
+```vue
+
+
+
+
+ 大写金额预览
+
+
+
+
{{ displayNumber || '—' }}
+
{{ upperText }}
+
+
+
+
+ {{ upperText }}
+
+
+
+
+
+
+
+```
+
+Constraints:
+
+- Persist `number | null` only; never emit uppercase string.
+- `showUpper` comes from rule `props` (FormCreate expands props onto the component).
+- Explicitly import `AmountInput` (same as production-material).
+
+- [ ] **Step 2: Manual verify**
+
+1. Designer: drag 金额;「显示大写金额」default on.
+2. Publish + start: enter `1234.56`, uppercase shows; toggle off → no uppercase line.
+3. Detail readonly: number + uppercase.
+4. Check「参与流程条件分支」→ required locked → field appears in flow condition candidates.
+
+- [ ] **Step 3: Commit**
+
+```bash
+git add web-admin/src/components/oa-approval/OaMoneyField.vue
+git commit -m "$(cat <<'EOF'
+feat(web-admin): implement OA money field with optional RMB uppercase
+
+Numeric AmountInput plus muted uppercase line; designer host shows preview only.
+EOF
+)"
+```
+
+---
+
+### Task 5: Print + related-summary formatting
+
+**Files:**
+- Modify: `web-admin/src/utils/oa-approval-print.ts`
+- Modify: `backend/app/services/oa_related_approval.py`
+
+- [ ] **Step 1: Print path**
+
+In `oa-approval-print.ts`, beside the `dept` branch:
+
+```ts
+import { toRmbUpper } from '@/utils/rmb-upper'
+import { formatMoneyDisplay } from '@/utils/money-format'
+
+if (type === 'money') {
+ const showUpper = rule.props?.showUpper !== false
+ const n =
+ value == null || value === ''
+ ? null
+ : typeof value === 'number'
+ ? value
+ : Number(String(value).replace(/,/g, ''))
+ if (n == null || !Number.isFinite(n)) {
+ out.push({ label, text: '-' })
+ continue
+ }
+ const numText = formatMoneyDisplay(n, 2)
+ const upper = showUpper ? toRmbUpper(n) : ''
+ out.push({
+ label,
+ text: upper ? `${numText}(${upper})` : numText
+ })
+ continue
+}
+```
+
+Use `(大写)` inline so single-line print rows remain compatible with `OaApprovalPrintSheet`.
+
+- [ ] **Step 2: Related approval summary**
+
+In `oa_related_approval.py` flatten display (next to `typ == "dept"`):
+
+```python
+if typ == "money":
+ if raw is None or raw == "":
+ return "", False
+ try:
+ if isinstance(raw, bool):
+ return "", False
+ v = float(str(raw).replace(",", "").strip())
+ except (TypeError, ValueError):
+ return str(raw), False
+ text = f"{v:,.2f}"
+ return text, False
+```
+
+- [ ] **Step 3: Manual print check** on an instance that includes a money field.
+
+- [ ] **Step 4: Commit**
+
+```bash
+git add web-admin/src/utils/oa-approval-print.ts backend/app/services/oa_related_approval.py
+git commit -m "$(cat <<'EOF'
+feat(oa): format money fields in print and related-approval summary
+
+Show formatted amount and optional uppercase on print; summary uses numeric text.
+EOF
+)"
+```
+
+---
+
+### Task 6: Spec status + verification
+
+**Files:**
+- Modify: `docs/superpowers/specs/2026-07-30-oa-money-field-design.md`
+
+- [ ] **Step 1: Spec polish**
+
+- Set status to `已实现`.
+- Remove the stray trailing ` ``` ` at end of §10 if still present.
+
+- [ ] **Step 2: Regression commands**
+
+```bash
+cd web-admin && pnpm exec tsx --test src/utils/rmb-upper.test.ts
+cd backend && source venv/bin/activate && python -m pytest tests/test_oa_money_field.py tests/test_oa_publish.py -v --tb=short
+cd web-admin && pnpm build:typecheck
+```
+
+Expected: tests pass; typecheck has no new errors from this change.
+
+- [ ] **Step 3: Acceptance checklist(spec §9)**
+
+1. Designer palette shows「金额」
+2.「显示大写金额」default on; toggle works
+3. Condition participation + required lock + flow candidates
+4. Start form: `1234.56` + uppercase; `form_data` numeric
+5. Uppercase off → no uppercase line
+6. Detail/print consistent
+7. `rmb-upper` tests pass
+
+- [ ] **Step 4: Commit**
+
+```bash
+git add docs/superpowers/specs/2026-07-30-oa-money-field-design.md
+git commit -m "$(cat <<'EOF'
+docs: mark OA money field design as implemented
+
+Update status after money control delivery and remove stray fence.
+EOF
+)"
+```
+
+- [ ] **Step 5: Update knowledge graph**
+
+```bash
+./scripts/update-graph.sh all
+```
+
+---
+
+## Spec coverage self-check
+
+| Spec 项 | Task |
+|---------|------|
+| 自定义 `money` + 双侧注册 | 3–4 |
+| `showUpper` 默认 true | 3–4 |
+| 只存 number | 2 + 4 |
+| 大写实时换算 / 范围 / 单测 | 1 |
+| 参与条件(现有基础设施) | 4 手工验收 |
+| 后端发起校验 | 2 |
+| 打印 + 大写 | 5 |
+| 非目标(多币种/小程序/落库大写) | 不实现 |
+
+## Placeholder scan
+
+No TBD steps. Icon name may need one-line swap if `icon-number` missing from FcDesigner icon set.
+
+---
+
+## Execution handoff
+
+Plan complete and saved to `docs/superpowers/plans/2026-07-30-oa-money-field.md`.
+
+**Two execution options:**
+
+1. **Subagent-Driven (recommended)** — fresh subagent per task, review between tasks
+2. **Inline Execution** — execute in this session with `executing-plans` and checkpoints
+
+Which approach?
diff --git a/docs/superpowers/specs/2026-07-30-oa-money-field-design.md b/docs/superpowers/specs/2026-07-30-oa-money-field-design.md
index e70e150a..0024cb14 100644
--- a/docs/superpowers/specs/2026-07-30-oa-money-field-design.md
+++ b/docs/superpowers/specs/2026-07-30-oa-money-field-design.md
@@ -3,7 +3,7 @@
**日期:** 2026-07-30
**范围:** `web-admin` 审批设计器步骤 2(表单设计)+ 发起/详情/打印运行时;条件分支已预留 `money`;后端发布白名单已含 `money`
**关联:** `docs/superpowers/specs/2026-07-14-oa-approval-dingtalk-design.md`;`docs/superpowers/specs/2026-07-15-oa-condition-from-form-design.md`;钉钉开放平台 `MoneyField`
-**状态:** 待用户评审
+**状态:** 已实现
---
@@ -187,5 +187,4 @@ type MoneyFormValue = number | null
- FormCreate 若内置同名 `money` 控件,注册时需确认覆盖策略与 DragRule `name` 不冲突;以本仓库自定义组件为准。
- 千分位仅影响输入展示,条件与存值必须是纯 number,避免字符串比较。
-- Tailwind 不进入 `oa-fc-host`;设计器内预览样式写在宿主局部 CSS。
-```
+- Tailwind 不进入 `oa-fc-host`;设计器内预览样式写在宿主局部 CSS。