diff --git a/docs/superpowers/plans/2026-10-16-oa-history-dept-filter.md b/docs/superpowers/plans/2026-10-16-oa-history-dept-filter.md
new file mode 100644
index 00000000..aeb859d3
--- /dev/null
+++ b/docs/superpowers/plans/2026-10-16-oa-history-dept-filter.md
@@ -0,0 +1,346 @@
+# OA 历史记录按部门检索 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:** 在 web-admin OA「历史记录」检索区增加按发起部门(本级+下级)过滤,并让列表与导出 API 共用该条件。
+
+**Architecture:** 前端用 `fetchDeptTree` + `el-tree-select` 传 `initiatorDeptId`;后端在现有 `can_view_all` 收窄之后,用 `data_scope._get_child_dept_ids` 展开部门树,对 `OaInstance.initiator_dept_id` 做 `IN` 过滤。不改角色数据范围模型。
+
+**Tech Stack:** FastAPI + SQLAlchemy、pytest、Vue 3 + Element Plus、`@/utils/tree-path`
+
+**Spec:** `docs/superpowers/specs/2026-10-16-oa-history-dept-filter-design.md`
+
+---
+
+## File structure
+
+| 文件 | 职责 |
+|------|------|
+| `backend/tests/test_oa_instance_history.py` | 部门过滤单测(含 SysDept 树) |
+| `backend/app/services/oa_engine.py` | `list_instance_history` / `_history_export_query` / `export_instance_history` 增加 `initiator_dept_id` |
+| `backend/app/api/oa_approval.py` | history / export Query 透传 `initiatorDeptId` |
+| `web-admin/src/api/oaApproval.ts` | 请求参数类型增加 `initiatorDeptId` |
+| `web-admin/src/views/oa-approval/history/index.vue` | 检索区部门树选 + 查询/导出透传 |
+
+---
+
+### Task 1: Backend failing tests for dept filter
+
+**Files:**
+- Modify: `backend/tests/test_oa_instance_history.py`
+
+- [ ] **Step 1: Extend setUp with SysDept tree and initiator_dept_id on instances**
+
+Import `SysDept` from `app.models.models`.
+
+In `Base.metadata.create_all` tables list, add `SysDept.__table__`.
+
+In `setUp` `add_all`, add three depts and set instance dept fields:
+
+```python
+SysDept(
+ id="dept-root",
+ parent_id="0",
+ dept_name="总部",
+ status="1",
+ tenant_no="zkxw",
+ create_dept_id="",
+ create_time=t1,
+ update_time=t1,
+ del_flag=0,
+),
+SysDept(
+ id="dept-sales",
+ parent_id="dept-root",
+ dept_name="销售部",
+ status="1",
+ tenant_no="zkxw",
+ create_dept_id="",
+ create_time=t1,
+ update_time=t1,
+ del_flag=0,
+),
+SysDept(
+ id="dept-sales-east",
+ parent_id="dept-sales",
+ dept_name="华东销售",
+ status="1",
+ tenant_no="zkxw",
+ create_dept_id="",
+ create_time=t1,
+ update_time=t1,
+ del_flag=0,
+),
+```
+
+On `inst-alice-done` set `initiator_dept_id="dept-sales"`.
+On `inst-bob-run` set `initiator_dept_id="dept-sales-east"`.
+
+- [ ] **Step 2: Add three tests**
+
+```python
+def test_dept_filter_includes_descendants(self):
+ data = oa_engine.list_instance_history(
+ self.db,
+ tenant_no="zkxw",
+ user_id="u-a",
+ can_view_all=True,
+ app_id="app-leave",
+ initiator_dept_id="dept-sales",
+ )
+ self.assertEqual(data["total"], 2)
+ ids = {r["id"] for r in data["list"]}
+ self.assertEqual(ids, {"inst-alice-done", "inst-bob-run"})
+
+def test_dept_filter_child_only(self):
+ data = oa_engine.list_instance_history(
+ self.db,
+ tenant_no="zkxw",
+ user_id="u-a",
+ can_view_all=True,
+ app_id="app-leave",
+ initiator_dept_id="dept-sales-east",
+ )
+ self.assertEqual(data["total"], 1)
+ self.assertEqual(data["list"][0]["id"], "inst-bob-run")
+
+def test_dept_filter_respects_self_scope(self):
+ data = oa_engine.list_instance_history(
+ self.db,
+ tenant_no="zkxw",
+ user_id="u-a",
+ can_view_all=False,
+ app_id="app-leave",
+ initiator_dept_id="dept-sales-east",
+ )
+ self.assertEqual(data["total"], 0)
+```
+
+- [ ] **Step 3: Run tests — expect FAIL (unknown kwarg)**
+
+```bash
+cd backend && source venv/bin/activate && pytest tests/test_oa_instance_history.py::TestOaInstanceHistory::test_dept_filter_includes_descendants tests/test_oa_instance_history.py::TestOaInstanceHistory::test_dept_filter_child_only tests/test_oa_instance_history.py::TestOaInstanceHistory::test_dept_filter_respects_self_scope -v
+```
+
+Expected: FAIL with `TypeError: list_instance_history() got an unexpected keyword argument 'initiator_dept_id'` (or similar).
+
+- [ ] **Step 4: Commit**
+
+```bash
+git add backend/tests/test_oa_instance_history.py
+git commit -m "test(oa): add history initiator dept filter cases"
+```
+
+---
+
+### Task 2: Backend engine + API for initiatorDeptId
+
+**Files:**
+- Modify: `backend/app/services/oa_engine.py`
+- Modify: `backend/app/api/oa_approval.py`
+
+- [ ] **Step 1: Add helper near history list helpers in `oa_engine.py`**
+
+Near `_apply_instance_list_filters` / `list_instance_history`, add:
+
+```python
+def _filter_by_initiator_dept(q, db: Session, *, tenant_no: str, initiator_dept_id: Optional[str]):
+ """按发起部门(含下级)过滤;空参数不改 query。"""
+ did = (initiator_dept_id or "").strip()
+ if not did:
+ return q
+ from app.core.data_scope import _get_child_dept_ids
+
+ dept_ids = _get_child_dept_ids(db, did, tenant_no)
+ if not dept_ids:
+ return q.filter(OaInstance.id == "") # 无匹配
+ return q.filter(OaInstance.initiator_dept_id.in_(dept_ids))
+```
+
+- [ ] **Step 2: Wire into `list_instance_history`**
+
+Add param `initiator_dept_id: Optional[str] = None` after `instance_status`.
+
+After the `can_view_all` block (or after instance_id like filter), apply:
+
+```python
+q = _filter_by_initiator_dept(
+ q, db, tenant_no=tenant_no, initiator_dept_id=initiator_dept_id
+)
+```
+
+Place it **after** `can_view_all` narrowing and before or after other filters — AND is commutative; prefer after `can_view_all` and before pagination count.
+
+- [ ] **Step 3: Wire into `_history_export_query` and `export_instance_history`**
+
+Add the same optional param to both; call `_filter_by_initiator_dept` inside `_history_export_query` after `can_view_all` block (same as list). Pass through from `export_instance_history` into `_history_export_query`.
+
+- [ ] **Step 4: API Query params**
+
+In `list_oa_instance_history` and `export_oa_instance_history`:
+
+```python
+initiatorDeptId: str = Query(""),
+```
+
+Pass `initiator_dept_id=initiatorDeptId or None` into engine calls.
+
+- [ ] **Step 5: Run tests — expect PASS**
+
+```bash
+cd backend && source venv/bin/activate && pytest tests/test_oa_instance_history.py -v
+```
+
+Expected: all PASS.
+
+- [ ] **Step 6: Commit**
+
+```bash
+git add backend/app/services/oa_engine.py backend/app/api/oa_approval.py
+git commit -m "feat(oa): filter instance history by initiator dept subtree"
+```
+
+---
+
+### Task 3: Frontend API types + history page dept filter
+
+**Files:**
+- Modify: `web-admin/src/api/oaApproval.ts`
+- Modify: `web-admin/src/views/oa-approval/history/index.vue`
+
+- [ ] **Step 1: Extend API param types**
+
+In `fetchOaInstanceHistory` and `fetchOaInstanceHistoryExport` params, add:
+
+```typescript
+initiatorDeptId?: string
+```
+
+(Params object is already forwarded as Query — no body change.)
+
+- [ ] **Step 2: Add dept tree select in template**
+
+In `filter-grid`, after「完成时间」item (before「申请人」), insert:
+
+```vue
+
+
+
+ {{ formatDeptPathLabel(value) }}
+
+
+
+```
+
+- [ ] **Step 3: Script — imports, state, tree helpers, wire load/export/reset**
+
+Add imports:
+
+```typescript
+import { fetchDeptTree } from '@/api/admin'
+import { buildPathLabelMapFromTreeRoots } from '@/utils/tree-path'
+```
+
+Extend `searchForm`:
+
+```typescript
+const searchForm = reactive({
+ instanceStatus: '',
+ instanceId: '',
+ initiatorName: '',
+ initiatorDeptId: '' as string
+})
+```
+
+Add dept tree state (mirror user page pattern, keep local helpers small):
+
+```typescript
+type DeptTreeSelectNode = { value: string; label: string; children?: DeptTreeSelectNode[] }
+
+const deptTree = ref([])
+
+function buildDeptTreeSelectData(nodes: Api.Dept.DeptItem[]): DeptTreeSelectNode[] {
+ return (nodes || []).map((n) => ({
+ value: n.id,
+ label: n.deptName || '',
+ children: n.children?.length ? buildDeptTreeSelectData(n.children) : undefined
+ }))
+}
+
+const deptTreeSelectData = computed(() => buildDeptTreeSelectData(deptTree.value))
+
+const deptPathLabelMap = computed(() =>
+ buildPathLabelMapFromTreeRoots(deptTree.value, {
+ idKey: 'id',
+ nameKey: 'deptName',
+ childrenKey: 'children'
+ })
+)
+
+function formatDeptPathLabel(nodeId: string | null | undefined) {
+ const id = String(nodeId ?? '').trim()
+ if (!id) return ''
+ return deptPathLabelMap.value.get(id) || ''
+}
+
+async function loadDeptTree() {
+ try {
+ deptTree.value = (await fetchDeptTree()) || []
+ } catch {
+ deptTree.value = []
+ }
+}
+```
+
+In `resetFilters`, clear `searchForm.initiatorDeptId = ''`.
+
+In `load()` params and `buildExportParams()`, when `searchForm.initiatorDeptId` is non-empty, set `params.initiatorDeptId = searchForm.initiatorDeptId`.
+
+Call `void loadDeptTree()` once on setup (alongside existing bootstrap/`onMounted` pattern used by this page — if page uses `onActivated`/`bootstrapFromRoute`, call `loadDeptTree` there or in `onMounted` so KeepAlive re-entry still has tree).
+
+- [ ] **Step 4: Manual smoke**
+
+1. Open `#/system-manage/oa-approval/history/?appName=...`
+2. Confirm「部门」可选、路径展示、清空有效
+3. With `oa:start:history:all`: parent dept shows child-dept rows
+4. Without that perm: other-dept selection yields empty (own rows only)
+
+- [ ] **Step 5: Update graph + commit**
+
+```bash
+./scripts/update-graph.sh backend
+./scripts/update-graph.sh frontend
+git add web-admin/src/api/oaApproval.ts web-admin/src/views/oa-approval/history/index.vue backend/graphify-out web-admin/graphify-out
+git commit -m "feat(web-admin): add OA history department filter"
+```
+
+---
+
+## Spec coverage checklist
+
+| Spec requirement | Task |
+|------------------|------|
+| 检索区部门树(全部部门) | Task 3 |
+| 本级+下级匹配 `initiator_dept_id` | Task 2 (`_get_child_dept_ids`) |
+| 全员权限模型不变 | Task 1 `test_dept_filter_respects_self_scope` + Task 2 |
+| 列表 + 导出共用参数 | Task 2 + Task 3 |
+| 单测 | Task 1–2 |
+| 非目标:不改数据范围 / 不改移动端 | 无对应改动 |
+
+---
+
+## Self-review notes
+
+- Param name consistent: API `initiatorDeptId` ↔ engine `initiator_dept_id` ↔ form `initiatorDeptId`
+- Empty / unknown dept → empty list via `IN` empty/`id == ""` fallback
+- No placeholders left in steps