feat: update audit platform workspace
This commit is contained in:
@@ -0,0 +1,407 @@
|
||||
# RAG DSL Bridge Merge 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:** Safely bring `gitea/jande-feature-dsl` RAG-backed rule DSL execution into current `wren-dev` without overwriting tenant, permission, or page-quality work already present.
|
||||
|
||||
**Architecture:** Add a shared `RagRetriever` service that both RAG chat and the leaudit evaluation pipeline can call. Wire the pipeline to pass a retriever into `leaudit.evaluate_extraction`, where the installed/local `leaudit` package already supports `stage.rag.query_template`. Keep current tenant-aware RAG chat code as the source of truth and only replace duplicate retrieval helpers with calls into the shared retriever.
|
||||
|
||||
**Tech Stack:** Python 3.12, FastAPI, SQLAlchemy async sessions, Chroma, httpx, pytest, leaudit DSL/evaluation engine.
|
||||
|
||||
---
|
||||
|
||||
## File Structure
|
||||
|
||||
- Create `fastapi_modules/fastapi_leaudit/rag_engine/retriever.py`: shared RAG retrieval implementation copied from `gitea/jande-feature-dsl`, with vector search, keyword fallback, source filtering, document hydration, and source formatting.
|
||||
- Modify `fastapi_modules/fastapi_leaudit/leaudit_bridge/pipeline.py`: inject `RagRetriever` into `LauditPipeline` and pass it to `evaluate_extraction`.
|
||||
- Modify `fastapi_modules/fastapi_leaudit/services/impl/ragChatServiceImpl.py`: preserve current tenant-aware chat API and delegate retrieval internals to `RagRetriever`.
|
||||
- Modify `rules/行政处罚/rules.yaml`: append only the new `JZ-JD-005` RAG-backed rule from `gitea/jande-feature-dsl`; do not rewrite existing rules.
|
||||
- Create `tests/test_rag_retriever.py`: validates vector retrieval source filtering and keyword fallback.
|
||||
- Create `tests/test_leaudit_rag_bridge.py`: validates pipeline passes the injected retriever to leaudit evaluation.
|
||||
|
||||
## Execution Constraints
|
||||
|
||||
- Do not run `git merge gitea/jande-feature-dsl` in the working tree.
|
||||
- Do not touch current untracked files unless explicitly requested.
|
||||
- Do not overwrite `fastapi_modules/fastapi_leaudit/services/impl/documentServiceImpl.py`; it has unrelated page-quality work in progress.
|
||||
- Stage and commit only after explicit user approval.
|
||||
- If `ragChatServiceImpl.py` behavior changes beyond retrieval helper delegation, stop and re-review before continuing.
|
||||
|
||||
### Task 1: Copy Shared Retriever And Tests
|
||||
|
||||
**Files:**
|
||||
- Create: `fastapi_modules/fastapi_leaudit/rag_engine/retriever.py`
|
||||
- Create: `tests/test_rag_retriever.py`
|
||||
- Create: `tests/test_leaudit_rag_bridge.py`
|
||||
|
||||
- [ ] **Step 1: Restore files from source branch**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
git checkout gitea/jande-feature-dsl -- fastapi_modules/fastapi_leaudit/rag_engine/retriever.py tests/test_rag_retriever.py tests/test_leaudit_rag_bridge.py
|
||||
```
|
||||
|
||||
Expected: three files appear in working tree; no existing tenant code is modified.
|
||||
|
||||
- [ ] **Step 2: Verify copied files are isolated**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
git status --short fastapi_modules/fastapi_leaudit/rag_engine/retriever.py tests/test_rag_retriever.py tests/test_leaudit_rag_bridge.py
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
```text
|
||||
A fastapi_modules/fastapi_leaudit/rag_engine/retriever.py
|
||||
A tests/test_leaudit_rag_bridge.py
|
||||
A tests/test_rag_retriever.py
|
||||
```
|
||||
|
||||
### Task 2: Wire Retriever Into Pipeline
|
||||
|
||||
**Files:**
|
||||
- Modify: `fastapi_modules/fastapi_leaudit/leaudit_bridge/pipeline.py`
|
||||
- Test: `tests/test_leaudit_rag_bridge.py`
|
||||
|
||||
- [ ] **Step 1: Inspect current pipeline imports and constructor**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
sed -n '1,120p' fastapi_modules/fastapi_leaudit/leaudit_bridge/pipeline.py
|
||||
```
|
||||
|
||||
Expected: confirm current imports include `StorageAdapter` and constructor currently has `ocr_client`, `llm_client`, `storage_adapter`.
|
||||
|
||||
- [ ] **Step 2: Add retriever import**
|
||||
|
||||
Add this import next to the existing platform imports:
|
||||
|
||||
```python
|
||||
from fastapi_modules.fastapi_leaudit.rag_engine.retriever import RagRetriever
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Extend constructor without changing existing arguments**
|
||||
|
||||
Change constructor signature to include:
|
||||
|
||||
```python
|
||||
rag_retriever: RagRetriever | None = None,
|
||||
```
|
||||
|
||||
Then initialize:
|
||||
|
||||
```python
|
||||
self.rag_retriever = rag_retriever or RagRetriever()
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Pass retriever to evaluation**
|
||||
|
||||
In the `evaluate_extraction(...)` call, add:
|
||||
|
||||
```python
|
||||
retriever=self.rag_retriever,
|
||||
```
|
||||
|
||||
Expected: this is a pure dependency-injection change; OCR, extraction, storage, and tenant behavior are unchanged.
|
||||
|
||||
### Task 3: Preserve Tenant-Aware RAG Chat And Delegate Retrieval
|
||||
|
||||
**Files:**
|
||||
- Modify: `fastapi_modules/fastapi_leaudit/services/impl/ragChatServiceImpl.py`
|
||||
|
||||
- [ ] **Step 1: Inspect current import area and constructor**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
sed -n '1,110p' fastapi_modules/fastapi_leaudit/services/impl/ragChatServiceImpl.py
|
||||
```
|
||||
|
||||
Expected: current file imports `build_openai_embeddings_url`, `get_chroma`, and `TenantResolver`; constructor initializes `self.TenantResolver`.
|
||||
|
||||
- [ ] **Step 2: Update imports minimally**
|
||||
|
||||
Remove unused direct retrieval imports after delegation:
|
||||
|
||||
```python
|
||||
build_openai_embeddings_url,
|
||||
```
|
||||
|
||||
Remove:
|
||||
|
||||
```python
|
||||
from fastapi_modules.fastapi_leaudit.rag_engine.chroma_client import get_chroma
|
||||
```
|
||||
|
||||
Add:
|
||||
|
||||
```python
|
||||
from fastapi_modules.fastapi_leaudit.rag_engine.retriever import RagRetriever
|
||||
```
|
||||
|
||||
Keep:
|
||||
|
||||
```python
|
||||
from fastapi_modules.fastapi_leaudit.services.impl.tenantResolver import TenantResolver
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Preserve constructor and add retriever**
|
||||
|
||||
Change constructor to:
|
||||
|
||||
```python
|
||||
def __init__(self, retriever: RagRetriever | None = None) -> None:
|
||||
self.TenantResolver = TenantResolver()
|
||||
self.retriever = retriever or RagRetriever()
|
||||
```
|
||||
|
||||
Expected: existing tenant resolver remains active.
|
||||
|
||||
- [ ] **Step 4: Replace `_retrieve_context` implementation only**
|
||||
|
||||
Replace the body of `_retrieve_context` with:
|
||||
|
||||
```python
|
||||
result = await self.retriever.retrieve(query=query, dataset_id=dataset_id)
|
||||
return result.chunks, result.dataset_name
|
||||
```
|
||||
|
||||
Do not change method signature.
|
||||
|
||||
- [ ] **Step 5: Delegate `_embed_texts`**
|
||||
|
||||
Replace the body of `_embed_texts` with:
|
||||
|
||||
```python
|
||||
return await self.retriever._embed_texts(texts, model_name)
|
||||
```
|
||||
|
||||
- [ ] **Step 6: Delegate keyword fallback helper**
|
||||
|
||||
Replace the body of `_keyword_retrieve_context` with:
|
||||
|
||||
```python
|
||||
chunks = await self.retriever._keyword_retrieve_context(
|
||||
dataset_id=dataset_id,
|
||||
collection_name=collection_name,
|
||||
dataset_name=dataset_name,
|
||||
query=query,
|
||||
top_k=top_k,
|
||||
score_threshold=score_threshold,
|
||||
source_names=None,
|
||||
)
|
||||
return chunks[:top_k]
|
||||
```
|
||||
|
||||
- [ ] **Step 7: Delegate keyword utility methods**
|
||||
|
||||
Replace helper bodies:
|
||||
|
||||
```python
|
||||
def _build_keyword_terms(self, query: str) -> list[str]:
|
||||
return self.retriever._build_keyword_terms(query)
|
||||
|
||||
def _normalize_keyword_query(self, query: str) -> str:
|
||||
return self.retriever._normalize_keyword_query(query)
|
||||
|
||||
def _score_keyword_chunk(self, *, query: str, terms: list[str], content: str, document_name: str) -> float:
|
||||
return self.retriever._score_keyword_chunk(
|
||||
query=query,
|
||||
terms=terms,
|
||||
content=content,
|
||||
document_name=document_name,
|
||||
)
|
||||
```
|
||||
|
||||
- [ ] **Step 8: Delegate source building and hydration**
|
||||
|
||||
Replace `_build_sources` body:
|
||||
|
||||
```python
|
||||
build_sources = getattr(self.retriever, "build_sources", None)
|
||||
if callable(build_sources):
|
||||
return build_sources(context_chunks, dataset_name)
|
||||
return RagRetriever(hydrate_documents=False).build_sources(context_chunks, dataset_name)
|
||||
```
|
||||
|
||||
Replace `_hydrate_document_hits` body:
|
||||
|
||||
```python
|
||||
return await self.retriever._hydrate_document_hits(dataset_id, chunks)
|
||||
```
|
||||
|
||||
Expected: public chat behavior, tenant filtering, app resolution, session ownership, and permission feedback remain current `wren-dev` behavior.
|
||||
|
||||
### Task 4: Append Administrative Penalty RAG Rule
|
||||
|
||||
**Files:**
|
||||
- Modify: `rules/行政处罚/rules.yaml`
|
||||
|
||||
- [ ] **Step 1: Confirm new rule is not already present**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
grep -n "JZ-JD-005" rules/行政处罚/rules.yaml
|
||||
```
|
||||
|
||||
Expected: no output before insertion.
|
||||
|
||||
- [ ] **Step 2: Append only the source-branch rule**
|
||||
|
||||
Extract from `gitea/jande-feature-dsl` and insert the `JZ-JD-005` block after the existing `JZ-JD-004` rule in `rules/行政处罚/rules.yaml`.
|
||||
|
||||
The inserted rule must include:
|
||||
|
||||
```yaml
|
||||
- rule_id: JZ-JD-005
|
||||
name: 案由及裁量标准适用准确性
|
||||
desc: 结合处罚决定书认定依据、处罚依据、罚款项目和罚款金额,检索案由与裁量标准,判断处罚种类和罚款幅度是否适用准确。
|
||||
risk: medium
|
||||
score: 10
|
||||
scope:
|
||||
- 处罚决定书
|
||||
rag:
|
||||
collection: general_legal_kb
|
||||
top_k: 5
|
||||
source_names:
|
||||
- 广东省烟草专卖行政处罚裁量执行标准-rag.md
|
||||
- 案由_行政处罚与反走私管理治理办法.md
|
||||
query_template: |
|
||||
认定依据:{{处罚决定书.认定依据}}
|
||||
处罚依据:{{处罚决定书.处罚依据}}
|
||||
罚款项目:{{处罚决定书.罚款项目}}
|
||||
罚款基数:{{处罚决定书.罚款基数}}
|
||||
罚款比例:{{处罚决定书.罚款比例}}
|
||||
罚款总额:{{处罚决定书.罚款总额}}
|
||||
问题:检索对应案由、裁量档次、处罚种类和罚款幅度
|
||||
inject_as: rag_context
|
||||
resources_as: rag_resources
|
||||
stages:
|
||||
- id: '1'
|
||||
check: required
|
||||
fields:
|
||||
- 处罚决定书.认定依据
|
||||
- 处罚决定书.处罚依据
|
||||
- 处罚决定书.罚款项目
|
||||
- 处罚决定书.罚款基数
|
||||
- 处罚决定书.罚款比例
|
||||
- 处罚决定书.罚款总额
|
||||
- id: '2'
|
||||
check: ai
|
||||
prompt: |
|
||||
请结合检索到的法律知识和卷宗处罚决定书字段,判断案由、裁量档次、处罚种类和罚款幅度是否适用准确。
|
||||
|
||||
【检索依据】
|
||||
{{rag_context}}
|
||||
|
||||
【处罚决定书字段】
|
||||
认定依据:{{处罚决定书.认定依据}}
|
||||
处罚依据:{{处罚决定书.处罚依据}}
|
||||
罚款项目:{{处罚决定书.罚款项目}}
|
||||
罚款基数:{{处罚决定书.罚款基数}}
|
||||
罚款比例:{{处罚决定书.罚款比例}}
|
||||
罚款总额:{{处罚决定书.罚款总额}}
|
||||
|
||||
【判断要求】
|
||||
1. 判断违法事实对应案由是否准确;
|
||||
2. 判断处罚依据是否能支撑对应处罚种类;
|
||||
3. 判断罚款基数、比例、总额是否落在裁量标准允许幅度内;
|
||||
4. 若检索依据不足以确认,应给出 warn,不要编造依据。
|
||||
logic: 1 AND 2
|
||||
messages:
|
||||
pass: 案由、裁量档次、处罚种类和罚款幅度适用准确。
|
||||
fail: 案由、裁量档次、处罚种类或罚款幅度可能适用不准确,请核对。
|
||||
references_laws:
|
||||
- 《中华人民共和国行政处罚法》第五十九条
|
||||
type: ai_rule
|
||||
```
|
||||
|
||||
Expected: YAML diff only contains the new rule block.
|
||||
|
||||
### Task 5: Verify Compile And Targeted Tests
|
||||
|
||||
**Files:**
|
||||
- Verify: `fastapi_modules/fastapi_leaudit/rag_engine/retriever.py`
|
||||
- Verify: `fastapi_modules/fastapi_leaudit/leaudit_bridge/pipeline.py`
|
||||
- Verify: `fastapi_modules/fastapi_leaudit/services/impl/ragChatServiceImpl.py`
|
||||
- Verify: `tests/test_rag_retriever.py`
|
||||
- Verify: `tests/test_leaudit_rag_bridge.py`
|
||||
|
||||
- [ ] **Step 1: Run Python compile check**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
.venv/bin/python -m py_compile fastapi_modules/fastapi_leaudit/rag_engine/retriever.py fastapi_modules/fastapi_leaudit/leaudit_bridge/pipeline.py fastapi_modules/fastapi_leaudit/services/impl/ragChatServiceImpl.py
|
||||
```
|
||||
|
||||
Expected: exit code 0, no syntax errors.
|
||||
|
||||
- [ ] **Step 2: Run targeted tests**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
.venv/bin/pytest tests/test_rag_retriever.py tests/test_leaudit_rag_bridge.py -q
|
||||
```
|
||||
|
||||
Expected: all tests pass.
|
||||
|
||||
- [ ] **Step 3: Run current relevant RAG chat tests if present**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
find tests -maxdepth 2 -type f -iname '*rag*chat*' -o -iname '*rag*permission*'
|
||||
```
|
||||
|
||||
If files are found, run them with `.venv/bin/pytest <files> -q`. Expected: pass or pre-existing unrelated failure documented.
|
||||
|
||||
### Task 6: Final Diff Review
|
||||
|
||||
**Files:**
|
||||
- Review all touched files.
|
||||
|
||||
- [ ] **Step 1: Show changed files**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
git status --short
|
||||
```
|
||||
|
||||
Expected: touched files include only the planned files plus pre-existing unrelated files.
|
||||
|
||||
- [ ] **Step 2: Review diff summary**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
git diff --stat
|
||||
```
|
||||
|
||||
Expected: planned files dominate; no accidental frontend, database, or page-quality code changes except pre-existing `documentServiceImpl.py`.
|
||||
|
||||
- [ ] **Step 3: Confirm no conflict markers**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
grep -R "<<<<<<<\\|=======\\|>>>>>>>" -n fastapi_modules tests rules || true
|
||||
```
|
||||
|
||||
Expected: no output.
|
||||
|
||||
- [ ] **Step 4: Report status instead of committing**
|
||||
|
||||
Do not commit automatically. Report:
|
||||
|
||||
- Files changed.
|
||||
- Tests run and result.
|
||||
- Whether rules YAML needs OSS/database re-import to become active.
|
||||
- Any known risk, especially dependency on installed `leaudit` package supporting `retriever`.
|
||||
@@ -0,0 +1,171 @@
|
||||
# Route Permission Guard 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:** Prevent users from opening hidden/unauthorized frontend pages by manually entering URLs.
|
||||
|
||||
**Architecture:** Keep backend API permission checks unchanged, and add a server-side Next.js page-route guard inside the authenticated `(audit)` layout. The guard uses backend user route authorization, normalizes feature subpages to their controlled route, and redirects unauthorized page requests before rendering content.
|
||||
|
||||
**Tech Stack:** Next.js 15 App Router, TypeScript, Node test runner, existing RBAC `/api/rbac/user/routes` data.
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Add Route Access Unit Tests
|
||||
|
||||
**Files:**
|
||||
- Create: `legal-platform-frontend/lib/auth/route-access.ts`
|
||||
- Test: `legal-platform-frontend/tests/govdoc-audit/route-access.test.mts`
|
||||
- Modify: `legal-platform-frontend/lib/utils/route-alias.shared.js`
|
||||
|
||||
- [ ] **Step 1: Write failing tests for direct URL authorization**
|
||||
|
||||
Create tests that assert route guard behavior:
|
||||
|
||||
```ts
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
import { isRoutePathAllowed, flattenMenuPaths } from "../../lib/auth/route-access.ts";
|
||||
import { normalizeRoutePathForPermission } from "../../lib/utils/route-alias.shared.js";
|
||||
|
||||
const allowedPaths = flattenMenuPaths([
|
||||
{ id: "home", title: "系统概览", path: "/home", icon: "", order: 1 },
|
||||
{ id: "rules", title: "规则管理", path: "/rules", icon: "", order: 2 },
|
||||
{ id: "contract", title: "合同管理", path: "/contract-template", icon: "", order: 3, children: [
|
||||
{ id: "contract-list", title: "模板列表", path: "/contract-template/list", icon: "", order: 1 },
|
||||
] },
|
||||
]);
|
||||
|
||||
test("route guard allows exact authorized route", () => {
|
||||
assert.equal(isRoutePathAllowed("/rules", allowedPaths), true);
|
||||
});
|
||||
|
||||
test("route guard allows feature detail page through alias", () => {
|
||||
assert.equal(isRoutePathAllowed("/rules-test/detail?packId=3&ruleId=MM-ENT-001", allowedPaths), true);
|
||||
});
|
||||
|
||||
test("route guard rejects direct URL when route is hidden from role", () => {
|
||||
assert.equal(isRoutePathAllowed("/tenants", allowedPaths), false);
|
||||
});
|
||||
|
||||
test("route guard maps current govdoc pages to govdoc root permission", () => {
|
||||
assert.equal(normalizeRoutePathForPermission("/govdoc/audits"), "/govdoc");
|
||||
assert.equal(normalizeRoutePathForPermission("/govdoc/detail/A-108bce03"), "/govdoc");
|
||||
});
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run tests and verify failure**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
cd legal-platform-frontend
|
||||
node --experimental-strip-types --test tests/govdoc-audit/route-access.test.mts
|
||||
```
|
||||
|
||||
Expected: fail because `route-access.ts` does not exist yet and `/govdoc/*` is not normalized.
|
||||
|
||||
### Task 2: Implement Pure Route Access Helper
|
||||
|
||||
**Files:**
|
||||
- Create: `legal-platform-frontend/lib/auth/route-access.ts`
|
||||
- Modify: `legal-platform-frontend/lib/utils/route-alias.shared.js`
|
||||
|
||||
- [ ] **Step 1: Implement helper**
|
||||
|
||||
Add functions:
|
||||
- `flattenMenuPaths(menuItems)` to extract authorized paths from route tree.
|
||||
- `normalizeRequestPath(pathname)` to strip query/hash/trailing slash and apply aliases.
|
||||
- `isRoutePathAllowed(pathname, allowedPaths)` to allow exact authorized routes, authorized route subtrees, and `/home`.
|
||||
|
||||
- [ ] **Step 2: Add `/govdoc/*` route alias**
|
||||
|
||||
Map current internal document pages to `/govdoc`, matching the existing legacy `/govdoc-audit/*` behavior.
|
||||
|
||||
- [ ] **Step 3: Run focused tests**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
cd legal-platform-frontend
|
||||
node --experimental-strip-types --test tests/govdoc-audit/route-access.test.mts tests/govdoc-audit/home-routing.test.mts
|
||||
```
|
||||
|
||||
Expected: all tests pass.
|
||||
|
||||
### Task 3: Wire Server-Side Guard Into Authenticated Layout
|
||||
|
||||
**Files:**
|
||||
- Modify: `legal-platform-frontend/app/(audit)/layout.tsx`
|
||||
|
||||
- [ ] **Step 1: Read current pathname**
|
||||
|
||||
Use `headers().get("x-pathname")`, which is already set by `middleware.ts`.
|
||||
|
||||
- [ ] **Step 2: Fetch user authorized routes**
|
||||
|
||||
Call `getUserRoutesByRole(userRole, frontendJWT, true)` from server layout.
|
||||
|
||||
- [ ] **Step 3: Redirect unauthorized pages**
|
||||
|
||||
If routes load successfully and `isRoutePathAllowed(pathname, flattenMenuPaths(routes))` is false, redirect to:
|
||||
|
||||
```ts
|
||||
/home?error=insufficient_permissions
|
||||
```
|
||||
|
||||
Do not block `/home`.
|
||||
|
||||
- [ ] **Step 4: Fail closed when routes cannot load**
|
||||
|
||||
If route loading fails because the session is expired, redirect to login. For non-auth failures, redirect to `/home?error=permission_check_failed` except when already on `/home`.
|
||||
|
||||
### Task 4: Verify Build and Regression
|
||||
|
||||
**Files:**
|
||||
- No new production files unless tests expose type issues.
|
||||
|
||||
- [ ] **Step 1: Run focused tests**
|
||||
|
||||
```bash
|
||||
cd legal-platform-frontend
|
||||
node --experimental-strip-types --test tests/govdoc-audit/route-access.test.mts tests/govdoc-audit/home-routing.test.mts
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run full existing frontend node tests**
|
||||
|
||||
```bash
|
||||
cd legal-platform-frontend
|
||||
node --experimental-strip-types --test tests/govdoc-audit/*.test.mts
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Run lint**
|
||||
|
||||
```bash
|
||||
cd legal-platform-frontend
|
||||
npm run lint -- --quiet
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Run production build**
|
||||
|
||||
```bash
|
||||
cd legal-platform-frontend
|
||||
npm run build
|
||||
```
|
||||
|
||||
### Task 5: Manual Acceptance Checklist
|
||||
|
||||
- [ ] A role without `/tenants` cannot open `/tenants` by URL.
|
||||
- [ ] A role without `/rules` cannot open `/rules-test/list` or `/rules-test/detail?...` by URL.
|
||||
- [ ] A role with `/rules` can still open `/rules-test/list` and `/rules-test/detail?...`.
|
||||
- [ ] `/home` remains reachable for logged-in users.
|
||||
- [ ] Sidebar menu hiding remains unchanged.
|
||||
- [ ] Backend API 403 behavior remains independent.
|
||||
|
||||
---
|
||||
|
||||
### Self-Review
|
||||
|
||||
- Spec coverage: Direct URL access is blocked at the server layout before page render.
|
||||
- Placeholder scan: No TBD/TODO remains in implementation steps.
|
||||
- Type consistency: Helper consumes existing `MenuItem` shape from `lib/auth/user-routes.ts`.
|
||||
@@ -0,0 +1,652 @@
|
||||
# 业务入口与业务类型交互优化实施计划
|
||||
|
||||
> **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:** 把当前“入口模块管理 + 文档类型管理 + 规则分组 + 上传”的用户理解成本降下来,让用户按“业务入口、业务大类、业务类型、规则”完成配置,而不是理解 `entry_module_id/type_id/group_id`。
|
||||
|
||||
**Architecture:** 不改后端核心数据模型,不自动创建业务大类或业务类型。前端做语义重构和交互包装:入口模块页只选择已有业务范围;业务类型管理页负责维护大类/类型;上传页保留两级选择但改成业务语言;规则分组页继续承接规则绑定。
|
||||
|
||||
**Tech Stack:** Next.js App Router、React、现有 `Card/Button/FormSelect/Table/Toast` 组件、现有 CSS 变量、FastAPI 现有入口模块/文档类型/规则分组接口、Playwright UI 验收。
|
||||
|
||||
---
|
||||
|
||||
## 一、设计结论
|
||||
|
||||
### 1.1 用户语义
|
||||
|
||||
统一使用下面这套用户可理解的语言:
|
||||
|
||||
```text
|
||||
业务入口 = 首页工作台,对应 leaudit_entry_modules
|
||||
业务大类 = 上传第一步选择,对应一级分组/当前文档类型大类
|
||||
业务类型 = 上传第二步选择,对应二级分组/运行子类型
|
||||
规则配置 = 给业务类型绑定规则集
|
||||
```
|
||||
|
||||
前端不再重点暴露这些技术词:
|
||||
|
||||
```text
|
||||
入口模块绑定文档类型
|
||||
一级分组
|
||||
二级分组
|
||||
document_type_id
|
||||
group_id
|
||||
entry_module_id
|
||||
```
|
||||
|
||||
技术词可以保留在调试信息、接口字段、开发文档里,但不能作为普通后台用户的主要操作语言。
|
||||
|
||||
### 1.2 业务边界
|
||||
|
||||
入口模块新建/编辑页只允许选择已有业务范围:
|
||||
|
||||
```text
|
||||
不能在入口模块页自动创建业务大类
|
||||
不能在入口模块页自动创建业务类型
|
||||
不能在入口模块页自动创建规则绑定
|
||||
```
|
||||
|
||||
如果用户找不到业务类型,页面只做引导:
|
||||
|
||||
```text
|
||||
未找到需要的业务类型?请先到“业务类型管理”维护。
|
||||
```
|
||||
|
||||
### 1.3 最终操作链路
|
||||
|
||||
```text
|
||||
1. 业务类型管理:维护业务大类和业务类型
|
||||
2. 业务入口管理:创建入口,选择菜单、租户、业务范围
|
||||
3. 规则配置:给业务类型绑定规则集
|
||||
4. 上传页:选择业务大类、业务类型,上传文件
|
||||
5. 文档列表/评查:按入口、租户、大类、类型过滤和命中规则
|
||||
```
|
||||
|
||||
## 二、UI 风格约束
|
||||
|
||||
所有新增 UI 必须和当前系统保持一致:
|
||||
|
||||
```text
|
||||
继续使用现有 Card、Button、FormSelect、Table、Toast
|
||||
继续使用现有 ant-btn / ant-btn-primary 按钮风格
|
||||
继续使用绿色主色 #00684a
|
||||
继续使用 var(--color-primary-text)、var(--color-primary-text-muted)、var(--color-primary-border-soft)、var(--color-primary-surface)
|
||||
继续使用当前后台页面 24px 外边距、Card 容器、表格、圆角、阴影风格
|
||||
继续沿用当前入口模块、文档类型、规则分组页面的表单布局
|
||||
```
|
||||
|
||||
禁止:
|
||||
|
||||
```text
|
||||
不要引入新的 UI 框架
|
||||
不要重做视觉体系
|
||||
不要大面积渐变、特殊字体、花哨动效
|
||||
不要把入口模块管理页做成和系统其他管理页不一致的风格
|
||||
不要改变现有按钮颜色体系
|
||||
```
|
||||
|
||||
## 三、页面信息架构调整
|
||||
|
||||
### 3.1 菜单命名
|
||||
|
||||
建议调整左侧菜单文案:
|
||||
|
||||
```text
|
||||
文档类型 -> 业务类型
|
||||
入口模块 -> 业务入口
|
||||
规则分组 -> 规则配置
|
||||
```
|
||||
|
||||
如果担心一次改名影响用户习惯,可以第一阶段使用过渡文案:
|
||||
|
||||
```text
|
||||
业务类型(原文档类型)
|
||||
业务入口(原入口模块)
|
||||
规则配置
|
||||
```
|
||||
|
||||
### 3.2 页面职责
|
||||
|
||||
```text
|
||||
业务入口管理:配置工作台、菜单、租户、业务范围
|
||||
业务类型管理:维护业务大类和业务类型,不绑定规则集
|
||||
规则配置:给业务类型绑定规则集
|
||||
上传文档:选择业务大类和业务类型后上传
|
||||
```
|
||||
|
||||
## 四、入口模块管理 UI 方案
|
||||
|
||||
### 4.1 列表页优化
|
||||
|
||||
文件:
|
||||
|
||||
```text
|
||||
legal-platform-frontend/app/(audit)/entry-modules/EntryModulesClient.tsx
|
||||
legal-platform-frontend/styles/pages/entry-modules.css
|
||||
```
|
||||
|
||||
列表页保留现有表格风格,但字段改成业务可读:
|
||||
|
||||
```text
|
||||
入口名称
|
||||
工作台类型
|
||||
适用租户
|
||||
业务范围
|
||||
功能菜单
|
||||
状态
|
||||
操作
|
||||
```
|
||||
|
||||
业务范围列显示摘要:
|
||||
|
||||
```text
|
||||
合同 3 个类型 / 公文 2 个类型
|
||||
```
|
||||
|
||||
如果接口暂时没有业务范围摘要,第一阶段先显示:
|
||||
|
||||
```text
|
||||
已配置业务范围
|
||||
未配置业务范围
|
||||
```
|
||||
|
||||
操作按钮:
|
||||
|
||||
```text
|
||||
编辑入口
|
||||
配置业务范围
|
||||
预览入口
|
||||
删除/停用
|
||||
```
|
||||
|
||||
权限仍走现有 RBAC:
|
||||
|
||||
```text
|
||||
entry_module:list:read
|
||||
entry_module:create:write
|
||||
entry_module:update:write
|
||||
entry_module:delete:delete
|
||||
entry_module:image:write
|
||||
```
|
||||
|
||||
### 4.2 新建/编辑页改成 4 步向导
|
||||
|
||||
文件:
|
||||
|
||||
```text
|
||||
legal-platform-frontend/app/(audit)/entry-modules/new/EntryModuleNewClient.tsx
|
||||
legal-platform-frontend/styles/pages/entry-modules.css
|
||||
```
|
||||
|
||||
页面顶部增加步骤条:
|
||||
|
||||
```text
|
||||
1 基础信息 -> 2 功能菜单 -> 3 适用租户 -> 4 业务范围
|
||||
```
|
||||
|
||||
步骤条只控制页面分区滚动/显示,不做复杂路由拆分。保存仍然是一个表单整体提交。
|
||||
|
||||
#### Step 1 基础信息
|
||||
|
||||
字段:
|
||||
|
||||
```text
|
||||
入口名称
|
||||
入口描述
|
||||
Logo
|
||||
跳转入口
|
||||
```
|
||||
|
||||
跳转入口不要直接展示裸路径作为主选项,改成业务文案:
|
||||
|
||||
```text
|
||||
通用文档评查(/documents)
|
||||
内部公文评查(/govdoc/audits)
|
||||
交叉评查(/cross-checking)
|
||||
```
|
||||
|
||||
保存时仍提交原 `route_path`。
|
||||
|
||||
#### Step 2 功能菜单
|
||||
|
||||
把菜单模板做成当前系统风格的轻量卡片:
|
||||
|
||||
```text
|
||||
通用文档评查
|
||||
合同工作台
|
||||
公文工作台
|
||||
交叉评查
|
||||
自定义工作台
|
||||
```
|
||||
|
||||
选中模板后自动带出默认功能。
|
||||
|
||||
功能勾选默认折叠为“高级功能设置”,避免用户一进来看到一大堆 checkbox。
|
||||
|
||||
高级区域展开后显示现有功能勾选:
|
||||
|
||||
```text
|
||||
首页
|
||||
文件上传
|
||||
文档列表
|
||||
规则管理
|
||||
规则分组
|
||||
模板搜索
|
||||
模板列表
|
||||
公文列表
|
||||
公文上传
|
||||
交叉评查
|
||||
创建任务
|
||||
评查任务列表
|
||||
使用统计
|
||||
```
|
||||
|
||||
#### Step 3 适用租户
|
||||
|
||||
保持 checkbox,但分组显示:
|
||||
|
||||
```text
|
||||
公共
|
||||
PUBLIC
|
||||
|
||||
地区租户
|
||||
云浮
|
||||
揭阳
|
||||
梅州
|
||||
```
|
||||
|
||||
提示文案:
|
||||
|
||||
```text
|
||||
未勾选的租户首页不会看到该业务入口。
|
||||
```
|
||||
|
||||
#### Step 4 业务范围
|
||||
|
||||
标题:
|
||||
|
||||
```text
|
||||
这个入口可以处理哪些业务?
|
||||
```
|
||||
|
||||
交互:
|
||||
|
||||
```text
|
||||
[ ] 合同
|
||||
[ ] 建设工程合同
|
||||
[ ] 买卖合同
|
||||
[ ] 租赁合同
|
||||
|
||||
[ ] 公文
|
||||
[ ] 请示
|
||||
[ ] 通知
|
||||
[ ] 会议纪要
|
||||
```
|
||||
|
||||
规则:
|
||||
|
||||
```text
|
||||
勾选业务大类 = 勾选其下全部业务类型
|
||||
取消某个业务类型 = 当前入口不包含这个类型
|
||||
只允许选择已有业务大类和业务类型
|
||||
不自动创建业务大类和业务类型
|
||||
```
|
||||
|
||||
右侧摘要:
|
||||
|
||||
```text
|
||||
已选择 2 个业务大类,5 个业务类型
|
||||
上传页将只显示这些业务范围
|
||||
文档列表将只显示这些业务范围
|
||||
规则配置将只显示这些业务范围
|
||||
```
|
||||
|
||||
空状态:
|
||||
|
||||
```text
|
||||
暂无可选业务类型
|
||||
请先到“业务类型管理”维护业务大类和业务类型
|
||||
[去业务类型管理]
|
||||
```
|
||||
|
||||
### 4.3 业务范围接口策略
|
||||
|
||||
第一阶段优先复用现有接口:
|
||||
|
||||
```text
|
||||
GET /api/v3/document-type-roots?entry_module_id=...
|
||||
GET /api/evaluation-point-groups?entry_module_id=...
|
||||
GET /api/evaluation-point-groups/by-document-types
|
||||
```
|
||||
|
||||
如果现有接口难以一次返回完整树,再新增轻量聚合接口:
|
||||
|
||||
```text
|
||||
GET /api/entry-modules/{id}/business-scope-options
|
||||
PUT /api/entry-modules/{id}/business-scope
|
||||
```
|
||||
|
||||
返回结构建议:
|
||||
|
||||
```json
|
||||
{
|
||||
"roots": [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "合同",
|
||||
"code": "root.contract",
|
||||
"selected": true,
|
||||
"children": [
|
||||
{
|
||||
"groupId": 101,
|
||||
"documentTypeId": 11,
|
||||
"name": "建设工程合同",
|
||||
"code": "contract.construction",
|
||||
"selected": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
保存结构建议:
|
||||
|
||||
```json
|
||||
{
|
||||
"selectedGroupIds": [101, 102, 201]
|
||||
}
|
||||
```
|
||||
|
||||
注意:
|
||||
|
||||
```text
|
||||
这里保存的是入口可用业务范围,不创建新业务类型。
|
||||
```
|
||||
|
||||
## 五、业务类型管理 UI 方案
|
||||
|
||||
### 5.1 页面改名和说明
|
||||
|
||||
文件:
|
||||
|
||||
```text
|
||||
legal-platform-frontend/app/(audit)/document-types/DocumentTypesIndexClient.tsx
|
||||
legal-platform-frontend/app/(audit)/document-types/new/DocumentTypesNewClient.tsx
|
||||
legal-platform-frontend/styles/pages/document-types_index.css
|
||||
legal-platform-frontend/styles/pages/document-types_new.css
|
||||
```
|
||||
|
||||
页面标题:
|
||||
|
||||
```text
|
||||
业务类型管理
|
||||
```
|
||||
|
||||
顶部说明:
|
||||
|
||||
```text
|
||||
这里维护系统可用的业务大类和业务类型。
|
||||
业务大类用于上传时第一步选择。
|
||||
业务类型用于上传时第二步选择,并用于绑定规则集。
|
||||
```
|
||||
|
||||
### 5.2 列表页展示
|
||||
|
||||
当前表格保留,但列名改成:
|
||||
|
||||
```text
|
||||
编码
|
||||
业务大类
|
||||
所属业务入口
|
||||
业务类型数量
|
||||
已绑定规则集
|
||||
状态
|
||||
操作
|
||||
```
|
||||
|
||||
“一级文档类型”“一级分组”“二级分组”这些词从主表格文案里去掉。
|
||||
|
||||
### 5.3 新建/编辑页文案
|
||||
|
||||
当前页先只维护业务大类,不让用户误会它会自动创建运行类型。
|
||||
|
||||
标题:
|
||||
|
||||
```text
|
||||
新建业务大类
|
||||
编辑业务大类
|
||||
```
|
||||
|
||||
说明:
|
||||
|
||||
```text
|
||||
业务大类是上传时第一步选择,例如合同、公文、案卷。具体业务类型请在规则配置中维护。
|
||||
```
|
||||
|
||||
如果后续要把业务类型维护也放到这里,需要单独设计二级列表,不和本轮混在一起。
|
||||
|
||||
## 六、上传页 UI 方案
|
||||
|
||||
文件:
|
||||
|
||||
```text
|
||||
legal-platform-frontend/app/(audit)/files/upload/FilesUploadClient.tsx
|
||||
legal-platform-frontend/styles/pages/files_upload.css
|
||||
```
|
||||
|
||||
上传页保留两级选择,但统一文案:
|
||||
|
||||
```text
|
||||
业务大类
|
||||
业务类型
|
||||
```
|
||||
|
||||
选择逻辑:
|
||||
|
||||
```text
|
||||
先按当前入口模块过滤业务大类
|
||||
选择业务大类后,只显示当前入口允许的业务类型
|
||||
```
|
||||
|
||||
默认规则:
|
||||
|
||||
```text
|
||||
当前入口只有一个业务大类 -> 默认选中并展示提示
|
||||
当前业务大类只有一个业务类型 -> 默认选中并展示提示
|
||||
```
|
||||
|
||||
提示:
|
||||
|
||||
```text
|
||||
当前业务入口:合同评查
|
||||
上传文件将归属到所选业务大类和业务类型。
|
||||
```
|
||||
|
||||
提交字段不变:
|
||||
|
||||
```text
|
||||
entryModuleId
|
||||
typeId
|
||||
groupId
|
||||
tenantCode
|
||||
```
|
||||
|
||||
## 七、规则配置页 UI 方案
|
||||
|
||||
文件:
|
||||
|
||||
```text
|
||||
legal-platform-frontend/app/(audit)/rule-groups/RuleGroupsClient.tsx
|
||||
legal-platform-frontend/styles/pages/rule-groups_index.css
|
||||
```
|
||||
|
||||
文案调整:
|
||||
|
||||
```text
|
||||
规则分组 -> 规则配置
|
||||
一级分组 -> 业务大类
|
||||
二级分组 -> 业务类型
|
||||
绑定规则集 -> 配置评查规则
|
||||
```
|
||||
|
||||
操作规则不变:
|
||||
|
||||
```text
|
||||
业务大类不能绑定规则集
|
||||
业务类型才可以绑定规则集
|
||||
```
|
||||
|
||||
按钮文案:
|
||||
|
||||
```text
|
||||
新增一级分组 -> 新增业务大类
|
||||
新增二级分组 -> 新增业务类型
|
||||
绑定规则集 -> 配置规则
|
||||
```
|
||||
|
||||
## 八、实施任务清单
|
||||
|
||||
### 8.1 准备和基线
|
||||
|
||||
- [x] 执行 `./leaudit.sh status`,确认后端、前端、Worker、Beat 运行。
|
||||
- [ ] 使用 Playwright 登录 `000/admin06111` 截取当前入口模块、文档类型、上传、规则分组页面现状。
|
||||
- [ ] 记录当前主要入口 URL:`/entry-modules`、`/entry-modules/new`、`/document-types`、`/files/upload`、`/rule-groups`。
|
||||
|
||||
### 8.2 入口模块列表页优化
|
||||
|
||||
- [x] 修改 `EntryModulesClient.tsx` 页面标题和文案:`入口模块管理` 改为 `业务入口管理`。
|
||||
- [x] 修改列表列名,隐藏或弱化技术字段。
|
||||
- [x] 增加功能菜单摘要展示,沿用现有 tag/badge 风格。
|
||||
- [x] 增加业务范围摘要占位,接口未完成前显示 `待配置` 或 `已配置`。
|
||||
- [ ] 跑 Playwright 验证列表页没有布局溢出,按钮权限仍正确。
|
||||
|
||||
### 8.3 入口模块新建/编辑页 4 步向导
|
||||
|
||||
- [x] 在 `EntryModuleNewClient.tsx` 增加步骤条状态。
|
||||
- [x] 把现有基础字段移动到 `基础信息` 分区。
|
||||
- [x] 把 `route_path` 下拉改成业务文案 + 路径说明。
|
||||
- [x] 把菜单模板改成模板卡片,选中后仍更新 `menu_profile/features`。
|
||||
- [x] 把功能勾选移动到 `高级功能设置` 折叠区。
|
||||
- [x] 把租户勾选拆成 `公共` 和 `地区租户` 两组。
|
||||
- [x] 增加 `业务范围` 分区,先展示只读/占位状态和跳转 `业务类型管理` 按钮。
|
||||
- [x] 保持提交 payload 字段不变:`name/description/route_path/menu_profile/features/tenants`。
|
||||
- [x] 跑 TypeScript 检查。
|
||||
- [ ] 跑 Playwright 验证新建、编辑、权限只读、Logo 上传入口不破坏。
|
||||
|
||||
### 8.4 业务范围选择器接入
|
||||
|
||||
- [x] 新增前端组件 `BusinessScopeSelector`,放在入口模块页面附近,复用现有 checkbox、Card、empty state 风格。
|
||||
- [x] 优先用现有文档类型/规则分组接口组装业务大类和业务类型树。
|
||||
- [ ] 如果现有接口不足,补后端聚合接口 `business-scope-options`。
|
||||
- [x] 选择器支持勾选大类自动勾选子类型。
|
||||
- [x] 选择器支持右侧摘要:大类数、业务类型数。
|
||||
- [x] 保存时只保存已有业务类型范围,不创建新大类/类型。
|
||||
- [ ] 跑 Playwright 验证入口编辑页业务范围选择、保存、回显。
|
||||
|
||||
### 8.5 业务类型管理文案优化
|
||||
|
||||
- [x] 修改 `DocumentTypesIndexClient.tsx` 标题为 `业务大类管理`。
|
||||
- [x] 修改表格列名:业务大类、所属业务入口、业务类型数量、已绑定规则集。
|
||||
- [x] 修改空状态文案。
|
||||
- [x] 修改 `DocumentTypesNewClient.tsx` 标题为 `新建业务大类/编辑业务大类`。
|
||||
- [x] 修改说明文案,强调这里维护上传第一步的业务大类。
|
||||
- [x] 修改 `document-types/page.tsx` 和 `document-types/new/page.tsx` metadata,避免浏览器标题继续显示文档类型。
|
||||
- [x] 修改面包屑 `文档类型管理/新建文档类型` 为 `业务大类管理/新建业务大类`。
|
||||
- [x] 修改侧栏 fallback 菜单 `文档类型` 为 `业务大类管理`。
|
||||
- [x] 修改上传失败诊断文案中的 `文档类型管理/当前文档类型` 为 `业务大类管理/当前业务大类`。
|
||||
- [x] 保持现有创建/编辑接口字段不变。
|
||||
- [ ] 跑 Playwright 验证新建和编辑页字段仍能保存。
|
||||
|
||||
### 8.6 上传页交互文案优化
|
||||
|
||||
- [x] 修改上传页选择项文案:`文档类型` 改为 `业务大类`。
|
||||
- [x] 修改子类型选择项文案:`子类型/二级分组` 改为 `业务类型`。
|
||||
- [x] 当前入口只有一个业务大类时自动选中并展示提示。
|
||||
- [x] 当前业务大类只有一个业务类型时自动选中并展示提示。
|
||||
- [x] 保持提交字段 `entryModuleId/typeId/groupId/tenantCode` 不变。
|
||||
- [ ] 跑 Playwright 真实上传表单测试,确认字段落库正确。
|
||||
|
||||
### 8.7 规则配置页文案优化
|
||||
|
||||
- [x] 修改页面标题:`规则分组` 改为 `规则配置`。
|
||||
- [x] 修改树节点文案:一级分组显示为业务大类,二级分组显示为业务类型。
|
||||
- [x] 修改按钮文案:新增业务大类、新增业务类型、配置规则。
|
||||
- [x] 一级节点不显示配置规则入口。
|
||||
- [x] 二级节点显示配置规则入口。
|
||||
- [x] 跑 Playwright 验证规则配置页业务文案、业务大类/业务类型行展示和二级配置入口可见。
|
||||
|
||||
### 8.8 权限和多租户回归
|
||||
|
||||
- [ ] 验证没有 `entry_module:list:read` 不显示业务入口管理菜单。
|
||||
- [ ] 验证没有 `entry_module:create:write` 不显示新建业务入口按钮。
|
||||
- [ ] 验证没有 `entry_module:update:write` 编辑页不可保存业务入口。
|
||||
- [ ] 验证没有 `entry_module:image:write` 不可上传入口 Logo。
|
||||
- [ ] 验证云浮、揭阳、梅州租户只看到自己或 `PUBLIC` 入口。
|
||||
|
||||
### 8.9 最终验收
|
||||
|
||||
- [x] `legal-platform-frontend` 下运行 `npx tsc --noEmit --pretty false`。
|
||||
- [x] 运行目标 eslint,要求 `0 error`。
|
||||
- [x] 使用 Playwright 跑入口模块编辑页:业务范围步骤、业务范围摘要、业务大类列表/空状态回显。
|
||||
- [ ] 使用 Playwright 跑上传页:业务大类、业务类型选择和真实提交。
|
||||
- [x] 使用 Playwright 跑规则配置页:业务大类/业务类型树、一级不显示配置入口、二级显示配置入口。
|
||||
- [ ] 更新 `docs/superpowers/plans/2026-05-23-entry-module-menu-profile-multitenant-refactor.md` 执行记录。
|
||||
|
||||
### 8.10 执行记录 2026-05-23
|
||||
|
||||
- [x] 新增业务入口 UI helper:`legal-platform-frontend/lib/business-entry/business-entry-ui.ts`,统一维护工作台入口文案、菜单模板文案、功能菜单摘要。
|
||||
- [x] 新增单测:`legal-platform-frontend/tests/govdoc-audit/business-entry-ui.test.mts`,覆盖路由业务文案、模板文案、功能摘要。
|
||||
- [x] 使用 TDD 跑红灯:helper 缺失时测试失败,随后实现 helper 并通过。
|
||||
- [x] 优化业务入口列表页:标题、说明、列名、工作台入口、工作台类型、业务范围占位、功能菜单摘要改为业务语义。
|
||||
- [x] 列表页 Logo 预览从 `<img>` 改为 `next/image`,目标 eslint 无 warning。
|
||||
- [x] 优化业务入口新建/编辑页:增加 `基础信息/功能菜单/适用租户/业务范围` 四步结构。
|
||||
- [x] 工作台入口下拉改为 `通用文档评查/内部公文评查/交叉评查`,保存仍使用原始 `route_path`。
|
||||
- [x] 菜单模板改为卡片选择,功能勾选移动到 `高级功能设置` 折叠区域。
|
||||
- [x] 租户选择拆成 `公共` 和 `地区租户` 两组。
|
||||
- [x] 业务范围先做占位和跳转 `业务类型管理`,不自动创建、不保存业务范围,避免破坏现有接口。
|
||||
- [x] 验证:`node --test --experimental-strip-types tests/govdoc-audit/business-entry-ui.test.mts` 结果 `5 pass / 0 fail`。
|
||||
- [x] 验证:目标 eslint 结果 `0 error / 0 warning`。
|
||||
- [x] 验证:`npx tsc --noEmit --pretty false` 通过。
|
||||
- [x] 新增业务分类 UI helper:`legal-platform-frontend/lib/business-entry/business-taxonomy-ui.ts`,统一维护 `业务大类/业务类型` 标签和上传必填提示。
|
||||
- [x] 新增单测:`legal-platform-frontend/tests/govdoc-audit/business-taxonomy-ui.test.mts`,覆盖业务大类/业务类型文案。
|
||||
- [x] 优化业务类型管理列表页:标题、说明、列名、空状态统一为业务语义。
|
||||
- [x] 优化业务大类新建/编辑页:把 `一级文档类型/入口模块/二级分组/评查点分组` 主文案替换为 `业务大类/业务入口/业务类型/规则配置`。
|
||||
- [x] 优化上传页:选择卡片改为 `选择业务范围`,两级选择改为 `业务大类/业务类型`,错误提示和说明同步改为业务语义。
|
||||
- [x] 验证:`node --test --experimental-strip-types tests/govdoc-audit/business-taxonomy-ui.test.mts tests/govdoc-audit/business-entry-ui.test.mts` 结果 `7 pass / 0 fail`。
|
||||
- [x] 验证:业务类型管理和上传页目标 eslint 结果 `0 error / 0 warning`。
|
||||
- [x] 验证:`npx tsc --noEmit --pretty false` 通过。
|
||||
- [x] 优化规则配置页:页面标题、指标卡、说明条、筛选项、业务大类/业务类型树、配置弹窗、删除确认和 toast 统一改成业务语义。
|
||||
- [x] 规则配置页保持接口和 payload 不变,仅替换用户可见文案;业务大类仍不显示配置规则入口,业务类型显示 `配置规则`。
|
||||
- [x] 修复规则配置页目标 eslint 的 hook dependency warning:把规则预览加载改为稳定 `useCallback` + ref 读取状态。
|
||||
- [x] 验证:`npx eslint app/\(audit\)/rule-groups/RuleGroupsClient.tsx --max-warnings=0` 结果 `0 error / 0 warning`。
|
||||
- [x] 验证:`npx tsc --noEmit --pretty false` 通过。
|
||||
- [x] 新增业务范围 helper:`legal-platform-frontend/lib/business-entry/business-scope-ui.ts`,覆盖选择摘要和保存差异计算。
|
||||
- [x] 新增业务范围 API 封装:复用 `GET /api/v3/document-type-roots`、`GET /api/rule-groups/tree`、`PUT /api/v3/document-type-roots/{id}`。
|
||||
- [x] 入口模块新建/编辑页接入业务范围选择器:用户勾选业务大类,下属业务类型只读展示;保存只更新业务大类归属,不自动创建业务大类或业务类型。
|
||||
- [x] 验证:`node --test --experimental-strip-types tests/business-entry/business-scope-ui.test.mts tests/govdoc-audit/business-taxonomy-ui.test.mts tests/govdoc-audit/business-entry-ui.test.mts` 结果 `9 pass / 0 fail`。
|
||||
- [x] 验证:`npx eslint app/\(audit\)/entry-modules/new/EntryModuleNewClient.tsx lib/business-entry/business-scope-ui.ts --max-warnings=0` 结果 `0 error / 0 warning`。
|
||||
- [x] 验证:`npx eslint lib/api/legacy/entry-modules/business-scope.ts --no-warn-ignored --max-warnings=0` 结果 `0 error / 0 warning`。
|
||||
- [x] 验证:`npx tsc --noEmit --pretty false` 通过。
|
||||
- [x] 修正遗漏的文档类型管理外露文案:页面 metadata、面包屑、侧栏 fallback、上传失败诊断统一改成 `业务大类管理/新建业务大类/业务大类`。
|
||||
- [x] 新增面包屑回归测试:`business category management breadcrumb uses business wording`。
|
||||
- [x] 验证:`node --test --experimental-strip-types tests/govdoc-audit/home-routing.test.mts tests/govdoc-audit/business-taxonomy-ui.test.mts` 结果 `12 pass / 0 fail`。
|
||||
- [x] 验证:目标 eslint 结果 `0 error / 0 warning`。
|
||||
- [x] 验证:`npx tsc --noEmit --pretty false` 通过。
|
||||
- [x] 定位并修复 `/rule-groups` 服务端路由守卫误拦截:后端 RBAC 权限树当前返回 `/rules` 且聚合了 `evaluation_group:*` 权限,但未返回 `/rule-groups` 路由;布局只按 route path 放行导致跳转 `/home?error=insufficient_permissions`。
|
||||
- [x] 新增 `isRouteAllowedByBusinessPermission`:仅当访问 `/rule-groups` 且用户拥有 `evaluation_group:*` 权限时放行,避免扩大其他路由访问范围。
|
||||
- [x] 新增路由守卫单测:`route guard allows rule configuration when user has evaluation group permissions`,先红灯后实现,最终通过。
|
||||
- [x] Playwright 真实账号 `000/admin06111` 验收:`node /tmp/leaudit-business-entry-ux-smoke.js` 通过,结果 `scopeItems=38 / rootRows=37 / childRows=58 / errorCount=0`。
|
||||
- [x] 验证:`node --test --experimental-strip-types tests/business-entry/business-scope-ui.test.mts tests/govdoc-audit/business-taxonomy-ui.test.mts tests/govdoc-audit/business-entry-ui.test.mts tests/govdoc-audit/route-access.test.mts` 结果 `16 pass / 0 fail`。
|
||||
- [x] 验证:`npx eslint app/\(audit\)/layout.tsx lib/auth/route-access.ts tests/govdoc-audit/route-access.test.mts app/\(audit\)/entry-modules/new/EntryModuleNewClient.tsx app/\(audit\)/rule-groups/RuleGroupsClient.tsx lib/business-entry/business-scope-ui.ts --max-warnings=0` 结果 `0 error / 0 warning`。
|
||||
- [x] 验证:`npx tsc --noEmit --pretty false` 通过。
|
||||
- [x] 环境说明:本轮 `./leaudit.sh start/restart` 在当前工具会话里出现启动后进程立即消失的现象;为完成 Playwright 验收,临时用同等命令在 PTY 会话中启动后端 `.venv/bin/python run.py` 和前端 `npm run dev:dev`。
|
||||
|
||||
## 九、风险控制
|
||||
|
||||
- [ ] 第一阶段优先改文案和页面组织,不改数据库。
|
||||
- [ ] 入口模块页不自动创建业务大类/业务类型,避免隐式脏数据。
|
||||
- [ ] 上传页保留两级选择,避免用户传错类型。
|
||||
- [ ] 所有菜单生成仍经过 RBAC 权限过滤。
|
||||
- [ ] 所有入口范围仍经过租户过滤。
|
||||
- [ ] Playwright 真实验收前先完成 UI 交互优化,避免测试脚本固化错误交互。
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,248 @@
|
||||
# 入口模块重构第二阶段实施计划
|
||||
|
||||
> **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:** 第一阶段已完成入口上下文、菜单 features 过滤、公文列表/上传入口归属。第二阶段继续沿用 `selectedEntryModuleContext`,所有业务页面从该上下文读取 `entryModuleId/documentTypeIds`,后端继续用 `entry_module_id/type_id/group_id/tenant_code` 做数据收口,不新增权限体系。
|
||||
|
||||
**Tech Stack:** FastAPI、SQLAlchemy 原生 SQL、Next.js、React、现有 RBAC 权限、现有 `leaudit_entry_modules / leaudit_document_types / leaudit_evaluation_point_groups / leaudit_documents` 表。
|
||||
|
||||
---
|
||||
|
||||
## 一、执行原则
|
||||
|
||||
- 不再用入口名称判断业务类型。
|
||||
- 不改现有 UI 视觉风格,只补数据范围、参数和必要提示。
|
||||
- 不删除旧接口,先补安全约束和兼容字段。
|
||||
- 先做普通文档链路,再做规则分组链路,最后处理重复侧边栏和验收。
|
||||
- 每一批改完都跑 `py_compile`、`tsc`、目标 `eslint`。
|
||||
|
||||
## 二、文件责任
|
||||
|
||||
- `legal-platform-frontend/lib/auth/entry-module-context.ts`:继续作为前端入口上下文唯一读写入口。
|
||||
- `legal-platform-frontend/lib/api/legacy/files/documents.ts`:普通文档列表、上传 API 参数补入口模块字段。
|
||||
- `legal-platform-frontend/app/(audit)/files/upload/*` 或实际上传客户端文件:上传表单带 `entryModuleId/typeId/groupId`。
|
||||
- `legal-platform-frontend/app/(audit)/documents/*` 或实际文档列表客户端文件:文档列表按入口上下文传 `entryModuleId/documentTypeIds`。
|
||||
- `fastapi_modules/fastapi_leaudit/controllers/documentController.py`:普通上传接口接收 `entryModuleId`。
|
||||
- `fastapi_modules/fastapi_leaudit/services/impl/documentServiceImpl.py`:创建文档写入 `entry_module_id`,校验 `group_id` 是二级分组且属于当前入口。
|
||||
- `legal-platform-frontend/app/(audit)/rule-groups/RuleGroupsClient.tsx`:规则分组页读取入口上下文,只展示当前入口分组。
|
||||
- `fastapi_modules/fastapi_leaudit/controllers/evaluationPointGroupController.py`:分组列表/创建/更新接收或校验入口模块参数。
|
||||
- `fastapi_modules/fastapi_leaudit/services/impl/evaluationPointGroupServiceImpl.py`:一级/二级分组边界、入口模块归属、绑定二级分组校验。
|
||||
- `legal-platform-frontend/components/layout/Layout.tsx`:处理公文详情页内部工作区侧栏和平台 Sidebar 重复问题。
|
||||
|
||||
---
|
||||
|
||||
## 三、任务拆分
|
||||
|
||||
### Task 1:普通文档上传写入入口模块
|
||||
|
||||
**目标:** 用户从入口模块进入上传页时,上传出来的文档写入 `entry_module_id/type_id/group_id/tenant_code`。
|
||||
|
||||
**Files:**
|
||||
- Modify: `fastapi_modules/fastapi_leaudit/controllers/documentController.py`
|
||||
- Modify: `fastapi_modules/fastapi_leaudit/services/impl/documentServiceImpl.py`
|
||||
- Modify: `legal-platform-frontend/lib/api/legacy/files/documents.ts`
|
||||
- Modify: 实际上传页面客户端,先用 `rg -n "DocumentUpload|uploadDocument|/upload" legal-platform-frontend/app legal-platform-frontend/components`
|
||||
|
||||
- [x] 后端 `UploadDocument` 增加 `entryModuleId: int | None = Form(None)`。
|
||||
- [x] `DocumentService.Upload(...)` 增加 `EntryModuleId` 参数。
|
||||
- [x] 创建 `LeauditDocument` 后写入 `entry_module_id`。
|
||||
- [x] 如果传了 `groupId`,校验该分组 `pid <> 0`。
|
||||
- [x] 如果传了 `entryModuleId + groupId`,校验二级分组所属入口等于 `entryModuleId`。
|
||||
- [x] 如果没传 `groupId`,只允许唯一二级分组时后端兜底;多个二级分组时报 400,让前端用户明确选择。
|
||||
- [x] 前端上传 API 从 `readEntryModuleContext()` 读取 `entryModuleId/documentTypeIds`。
|
||||
- [x] 上传表单把 `entryModuleId`、选中的 `typeId`、选中的 `groupId` 一起提交。
|
||||
|
||||
**验收:**
|
||||
- 上传成功后数据库 `leaudit_documents.entry_module_id` 有值。
|
||||
- 多个二级分组时,不选 `groupId` 不能上传。
|
||||
- 传一级分组 ID 时后端返回 400。
|
||||
|
||||
### Task 2:普通文档列表按入口模块过滤
|
||||
|
||||
**目标:** 从不同入口进入普通文档列表,只看到当前入口范围的文档。
|
||||
|
||||
**Files:**
|
||||
- Modify: `legal-platform-frontend/lib/api/legacy/files/documents.ts`
|
||||
- Modify: 普通文档列表客户端,先用 `rg -n "listDocuments|getDocuments|documents.searchParams" legal-platform-frontend/app legal-platform-frontend/components`
|
||||
- Backend already has: `documentController.py` 的 `entry_module_id/type_ids` 和 `documentServiceImpl.py` 的过滤基础。
|
||||
|
||||
- [x] 文档列表页面读取入口上下文作用域(URL/session,由入口首页写入)。
|
||||
- [x] 请求参数带 `entry_module_id`。
|
||||
- [x] 请求参数带 `type_ids`,作为入口文档类型范围的兜底过滤。
|
||||
- [x] 保留现有搜索、分页、状态过滤,不改 UI。
|
||||
- [x] 切换入口模块时清理 `documents.searchParams`,避免旧入口搜索条件污染新入口。
|
||||
|
||||
**验收:**
|
||||
- 入口 A 上传的文档不出现在入口 B。
|
||||
- 没有入口上下文时,保留原有列表行为。
|
||||
|
||||
### Task 3:规则分组页按入口模块收口
|
||||
|
||||
**目标:** 规则分组页只展示当前入口模块下的一级分组和二级分组。
|
||||
|
||||
**Files:**
|
||||
- Modify: `legal-platform-frontend/app/(audit)/rule-groups/RuleGroupsClient.tsx`
|
||||
- Modify: `legal-platform-frontend/app/api/rule-groups/*`
|
||||
- Modify: `fastapi_modules/fastapi_leaudit/controllers/evaluationPointGroupController.py`
|
||||
- Modify: `fastapi_modules/fastapi_leaudit/services/impl/evaluationPointGroupServiceImpl.py`
|
||||
|
||||
- [x] 前端读取 `entryModuleId`。
|
||||
- [x] 分组列表请求带 `entry_module_id`。
|
||||
- [x] 后端列表用 `entry_module_id` 过滤一级分组。
|
||||
- [x] 二级分组根据父级一级分组自然收口。
|
||||
- [x] 新建一级分组默认带当前入口 `entry_module_id`。
|
||||
- [x] 新建二级分组必须带 `document_type_id`,且文档类型属于当前入口。
|
||||
- [x] 编辑二级分组时禁止移动到其他入口的一级分组下。
|
||||
|
||||
**验收:**
|
||||
- 合同入口看不到公文入口的分组。
|
||||
- 公文入口看不到合同入口的分组。
|
||||
- 二级分组不能跨入口挂载。
|
||||
|
||||
### Task 4:规则绑定二级分组硬校验
|
||||
|
||||
**目标:** 规则只能绑定二级分组,不能绑定一级分组,也不能跨租户/跨入口污染。
|
||||
|
||||
**Files:**
|
||||
- Modify: `fastapi_modules/fastapi_leaudit/services/impl/evaluationPointGroupServiceImpl.py`
|
||||
- Review: `fastapi_modules/fastapi_leaudit/services/impl/ruleServiceImpl.py`
|
||||
|
||||
- [x] 保留新接口已有 `pid == 0` 拒绝绑定逻辑。
|
||||
- [x] 在绑定保存时再次校验 group 所属入口对当前用户可见。
|
||||
- [x] `_load_binding_map` 增加绑定级租户过滤:当前租户、`PUBLIC`、`PROVINCIAL` 可见,其它租户不可见。
|
||||
- [x] 旧规则绑定兼容接口只允许唯一可访问二级分组场景写入。
|
||||
- [x] 旧接口写入新绑定表时必须带 `tenant_code/scope_type`,保持第一阶段补丁。
|
||||
|
||||
**验收:**
|
||||
- 一级分组绑定返回 400。
|
||||
- 普通租户看不到其它租户绑定。
|
||||
- 全局管理员能看到公共/省级范围绑定。
|
||||
|
||||
### Task 5:重复侧边栏处理
|
||||
|
||||
**目标:** 公文详情页不要同时出现平台 Sidebar 和公文工作区 Sidebar。
|
||||
|
||||
**Files:**
|
||||
- Modify: `legal-platform-frontend/components/layout/Layout.tsx`
|
||||
- Review: `legal-platform-frontend/components/govdoc-audit/GovdocAuditResultPage.tsx`
|
||||
|
||||
- [x] 确认哪些公文页面内部有 `GovdocWorkspaceSidebar`。
|
||||
- [x] `Layout.tsx` 对公文详情/结果页隐藏平台 Sidebar。
|
||||
- [x] 公文列表、上传、概览仍保留平台 Sidebar。
|
||||
- [x] 不改公文工作区内部 UI,只处理外层布局冲突。
|
||||
|
||||
**验收:**
|
||||
- `/govdoc/audits` 只有平台 Sidebar。
|
||||
- `/govdoc/[id]` 或详情页只有公文工作区 Sidebar。
|
||||
- 移动端布局不新增横向溢出。
|
||||
|
||||
### Task 6:前端权限和 UI 一致性复查
|
||||
|
||||
**目标:** features 只是入口功能编排,不能绕过 RBAC 页面/API 权限。
|
||||
|
||||
**Files:**
|
||||
- Review: `legal-platform-frontend/components/layout/Sidebar.tsx`
|
||||
- Review: `legal-platform-frontend/lib/auth/entry-module-menu.ts`
|
||||
- Review: `legal-platform-frontend/lib/auth/user-routes.ts`
|
||||
|
||||
- [x] 确认菜单先从 RBAC 路由结果生成,再按 features 过滤。
|
||||
- [x] 没有合同模板页面权限时,即使入口启用合同模板 feature,也不显示模板菜单。
|
||||
- [x] 没有规则分组权限时,即使入口启用 `rule_groups`,也不显示规则分组菜单。
|
||||
- [x] 所有新增提示沿用现有 CSS 变量和组件风格。
|
||||
|
||||
**验收:**
|
||||
- 入口 features 不能“放大权限”,只能“收窄菜单”。
|
||||
|
||||
---
|
||||
|
||||
## 四、并行安排
|
||||
|
||||
建议用 subagent 并行:
|
||||
|
||||
- Worker A:Task 1 普通文档上传链路。
|
||||
- Worker B:Task 2 普通文档列表过滤。
|
||||
- Worker C:Task 3/4 规则分组和绑定后端校验。
|
||||
- Worker D:Task 5 重复侧边栏处理。
|
||||
- 主线程:整合冲突、跑验证、更新计划文档。
|
||||
|
||||
注意:Worker A 和 Worker B 可能同时改 `lib/api/legacy/files/documents.ts`,需要一个人先做 API 类型,另一个只改页面调用,避免冲突。
|
||||
|
||||
---
|
||||
|
||||
## 五、验证命令
|
||||
|
||||
后端:
|
||||
|
||||
```bash
|
||||
python -m py_compile fastapi_modules/fastapi_leaudit/controllers/documentController.py fastapi_modules/fastapi_leaudit/services/impl/documentServiceImpl.py fastapi_modules/fastapi_leaudit/controllers/evaluationPointGroupController.py fastapi_modules/fastapi_leaudit/services/impl/evaluationPointGroupServiceImpl.py fastapi_modules/fastapi_leaudit/services/impl/ruleServiceImpl.py
|
||||
```
|
||||
|
||||
前端:
|
||||
|
||||
```bash
|
||||
cd legal-platform-frontend
|
||||
npx tsc --noEmit --pretty false
|
||||
npx eslint --no-ignore lib/auth/entry-module-context.ts lib/auth/entry-module-menu.ts components/layout/Sidebar.tsx components/layout/Layout.tsx
|
||||
```
|
||||
|
||||
手工验收:
|
||||
|
||||
- 创建一个名称不含“合同”的合同入口,启用合同模板功能,检查模板搜索/模板列表显示。
|
||||
- 创建一个名称不含“公文”的公文入口,启用公文功能,检查公文列表/上传显示。
|
||||
- 从入口 A 上传普通文档,入口 B 不应看到。
|
||||
- 从入口 A 进入规则分组,只看到入口 A 的一级/二级分组。
|
||||
- 尝试把规则绑到一级分组,应失败。
|
||||
- 公文详情页不出现双侧栏。
|
||||
|
||||
执行记录(2026-05-23):
|
||||
|
||||
- 已跑后端目标编译:`python -m py_compile ...`,通过。
|
||||
- 已跑前端类型检查:`npx tsc --noEmit --pretty false`,通过。
|
||||
- 已跑目标 eslint:0 error,剩余 31 个 warning 为既有未使用变量 / any / hook dependency 类警告。
|
||||
- 已用 Playwright 真实账号 `000/admin06111` 做首页和侧边栏 smoke:
|
||||
- 登录成功。
|
||||
- 点击入口后进入 `/documents/list?entryModuleId=...&documentTypeIds=...`。
|
||||
- 侧边栏顺序为:首页、文件上传、文档列表、规则管理。
|
||||
- 文档列表没有重复。
|
||||
- 上传文档按钮可见。
|
||||
- 已用 Playwright 真实后端链路跑优先项 `2/3/4/5`:`node /tmp/leaudit-playwright-priority-2-5-v3.js`,结果 `25 pass / 0 fail`。
|
||||
- 上传携带 `entryModuleId/groupId` 成功,文档详情返回正确 `typeId/groupId/tenantCode`。
|
||||
- 文档列表按入口 A/B 隔离通过。
|
||||
- 规则分组按入口 A/B 隔离通过。
|
||||
- 一级分组绑定规则集返回 400。
|
||||
- 二级分组绑定规则集返回 200。
|
||||
- 无入口模块创建/编辑/删除权限用户直接调接口返回 403。
|
||||
- 修复一次 Playwright 暴露的后端 500:
|
||||
- 文件:`fastapi_modules/fastapi_leaudit/services/impl/evaluationPointGroupServiceImpl.py`
|
||||
- 方法:`_get_binding_row`
|
||||
- 原因:SQL 文本没有使用 f-string,`{access_filter}` 原样发送到 PostgreSQL。
|
||||
- 修复:把查询文本改为 f-string,入口模块/租户可见性过滤正常注入。
|
||||
- 已跑后端规则绑定范围单测:`pytest -q tests/test_rule_group_binding_scope.py`,结果 `4 passed`。
|
||||
- 已重新跑前端类型检查:`npx tsc --noEmit --pretty false`,通过。
|
||||
- 已重新跑目标 eslint:0 error,剩余 6 个 warning,为既有 `<img>`、未使用变量类提示。
|
||||
- 仍需继续做真实浏览器页面级验收:
|
||||
- 入口模块管理页功能勾选保存和回显。
|
||||
- 上传页真实表单选择文档类型、二级分组后提交。
|
||||
- 规则分组页面真实 UI 下只展示当前入口范围。
|
||||
- 公文列表、公文上传、公文详情页侧边栏真实页面检查。
|
||||
- 多租户可见性需要用不同租户账号逐个验证。
|
||||
|
||||
下一步执行顺序:
|
||||
|
||||
1. Playwright 页面级验收入口模块管理页:新建/编辑入口,检查 `menu_profile/features/tenants` 保存和回显。
|
||||
2. Playwright 页面级验收上传页:确认文件上传在 UI 上第二位,选择文档类型和二级分组后真实提交。
|
||||
3. Playwright 页面级验收规则分组页:入口 A/B 切换后页面树、绑定按钮、绑定弹窗范围正确。
|
||||
4. Playwright 页面级验收公文入口:名称不含“公文”但配置公文功能时,公文列表/上传可进入且标题不写死。
|
||||
5. 多租户账号验证:分别用云浮、揭阳、梅州或可用测试账号验证入口模块可见性和入口管理权限。
|
||||
6. 根据页面级验收结果修复前端展示问题,再回跑 `tsc`、目标 eslint、Playwright。
|
||||
|
||||
---
|
||||
|
||||
## 六、暂缓项
|
||||
|
||||
- 暂不删除旧 `leaudit_rule_type_bindings` 表。
|
||||
- 暂不重写评查运行引擎。
|
||||
- 暂不做同一入口模块按租户覆盖 features。
|
||||
- 暂不大改页面视觉,只做现有 UI 风格内的补充。
|
||||
@@ -0,0 +1,71 @@
|
||||
# Qichacha Migration 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:** Migrate the legacy Qichacha company verification module into the current LeAudit backend and keep the existing frontend company-info modal working.
|
||||
|
||||
**Architecture:** Add a `[QICHACHA]` TOML settings section, a new `qcc_company_info` active-record model, DTO/VO objects, a service interface plus implementation, and a small HTTP client wrapper for Qichacha signing and API parsing. Controllers expose `/api/v2/qichacha/*` using unified `Result[...]`; the frontend wrapper unwraps both the new `Result` response and the old raw legacy shape.
|
||||
|
||||
**Tech Stack:** FastAPI, SQLAlchemy async ORM, Pydantic v2, httpx, pytest, Next.js TypeScript.
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Configuration And Response Contract
|
||||
|
||||
**Files:**
|
||||
- Modify: `app.toml`
|
||||
- Modify: `fastapi_admin/config/_settings.py`
|
||||
- Modify: `fastapi_admin/config/__init__.py`
|
||||
- Create: `tests/test_qichacha_config_client.py`
|
||||
|
||||
- [ ] Write failing tests proving `QICHACHA_*` settings are exported and the client generates the expected MD5 token.
|
||||
- [ ] Add `QichachaSettings` and `[QICHACHA]` placeholders.
|
||||
- [ ] Implement `QichachaClient` with signing, retry-friendly GET calls, and response validation.
|
||||
- [ ] Run `pytest tests/test_qichacha_config_client.py -q`.
|
||||
|
||||
### Task 2: Backend Domain And Cache Service
|
||||
|
||||
**Files:**
|
||||
- Create: `fastapi_common/fastapi_common_web/exception/QichachaException.py`
|
||||
- Create: `fastapi_modules/fastapi_leaudit/domian/Dto/qichachaDto.py`
|
||||
- Create: `fastapi_modules/fastapi_leaudit/domian/vo/qichachaVo.py`
|
||||
- Create: `fastapi_modules/fastapi_leaudit/models/qichachaCompanyInfo.py`
|
||||
- Create: `fastapi_modules/fastapi_leaudit/services/qichachaService.py`
|
||||
- Create: `fastapi_modules/fastapi_leaudit/services/impl/qichachaServiceImpl.py`
|
||||
- Modify: `fastapi_modules/fastapi_leaudit/models/__init__.py`
|
||||
- Modify: `fastapi_modules/fastapi_leaudit/services/__init__.py`
|
||||
- Create: `tests/test_qichacha_service.py`
|
||||
|
||||
- [ ] Write failing tests for cache hit, stale refresh, force refresh, and dishonesty count mapping.
|
||||
- [ ] Implement model classmethods for keyword lookup and upsert.
|
||||
- [ ] Implement service interface and implementation returning VO objects.
|
||||
- [ ] Run `pytest tests/test_qichacha_service.py -q`.
|
||||
|
||||
### Task 3: API Routes, Permissions, SQL
|
||||
|
||||
**Files:**
|
||||
- Create: `fastapi_modules/fastapi_leaudit/controllers/qichachaController.py`
|
||||
- Modify: `fastapi_modules/fastapi_leaudit/services/impl/rbacAdminServiceImpl.py`
|
||||
- Modify: `scripts/创建sql/user_rbac_seed.sql`
|
||||
- Create: `scripts/创建sql/schema_qichacha_company_info.sql`
|
||||
|
||||
- [ ] Add authenticated routes for `/v2/qichacha/company`, `/enterprise`, `/dishonesty`, `/batch`, `/status`.
|
||||
- [ ] Add action permissions `qichacha:company:query` and `qichacha:status:read` without creating a route/menu entry.
|
||||
- [ ] Add SQL schema for `qcc_company_info`.
|
||||
- [ ] Run backend import and focused pytest checks.
|
||||
|
||||
### Task 4: Frontend Compatibility
|
||||
|
||||
**Files:**
|
||||
- Modify: `legal-platform-frontend/lib/api/legacy/corporate-information/qichacha.ts`
|
||||
|
||||
- [ ] Add a local unwrap helper that returns `response.data.data` for new `Result` responses and raw `response.data` for legacy responses.
|
||||
- [ ] Use it in `queryCompanyInfo`, `queryEnterpriseInfo`, and `queryDishonestyInfo`.
|
||||
- [ ] Run TypeScript or lint check for the changed file scope.
|
||||
|
||||
### Task 5: Verification
|
||||
|
||||
- [ ] Run focused backend tests: `pytest tests/test_qichacha_config_client.py tests/test_qichacha_service.py -q`.
|
||||
- [ ] Run Python compile/import checks for new backend files.
|
||||
- [ ] Run frontend lint/type verification if available.
|
||||
- [ ] Review `git diff` to ensure no unrelated dirty files were reverted.
|
||||
Reference in New Issue
Block a user