本卡屬 FR-161(母卡見「母卡」欄),第 1 棒:建資料表與大類 API。後續五張卡都吃這張的表。
「事前資料」是客戶在顧問訪談前上傳資料的地方,資料分成幾個大類(網路架構、組織架構……)。這張卡做地基:建三張資料表(大類、資料、資料的各個版本),出貨帶四個起始大類,開兩顆權限,在側選單登記兩個入口,並做大類的新增、改名、排序、刪除 API。畫面在卡 4 做,這張只做後端。
~/Projects/Billows/Audit-Manager/compliance-manager-be/docs/features/FR-161-2610-pre-engagement-materials/design.md(決策表 D1~D10、詳細設計、拆分表);雛形:~/Projects/Billows/Audit-Manager/compliance-manager-be/docs/features/FR-161-2610-pre-engagement-materials/prototype/index.html(三頁,雙擊即可開)。本卡已帶足開工所需細節,不必先讀完 design。① Migration(主線、phase=active、envs=*):新檔 scripts/sql/2026-10-2x-fr161-pre-engagement-tables.sql,在 scripts/sql/manifest.tsv 末尾加一列。三張表都在 compliance schema:
compliance.pre_engagement_categories
id BIGSERIAL PK, uid VARCHAR(36) UNIQUE, name VARCHAR(100) NOT NULL, description TEXT,
sort INT DEFAULT 0, seed_key VARCHAR(50), -- 從 system_menus 帶進來的才有值
tenant_id BIGINT NOT NULL REFERENCES public.tenants(id),
org_unit_id INTEGER REFERENCES public.org_units(id),
created_at/updated_at timestamptz, created_user/updated_user VARCHAR(255)
UNIQUE (tenant_id, name)
compliance.pre_engagement_files
id, uid, category_id FK→pre_engagement_categories(id), title VARCHAR(255) NOT NULL,
is_obsolete BOOLEAN DEFAULT false, tenant_id, org_unit_id, 審計四欄
compliance.pre_engagement_file_versions
id, uid, pre_engagement_file_id FK ON DELETE CASCADE, version_no INT NOT NULL,
file_id FK→public.upload_files(id), created_user, created_at
UNIQUE (pre_engagement_file_id, version_no)
org_unit_id 一定要在 CREATE TABLE 裡:TenantScopedMixinModel 在多租戶模式下 ORM 會帶這欄,缺了 INSERT 直接 500(FR-043 踩過)。跨 schema FK 要寫 public.。app.is_super_admin = 't' OR tenant_id = ANY(app.allowed_tenant_paths)(形狀抄 docs/claude/database-schema.md 檢測工具系列)。GRANT SELECT, INSERT, UPDATE, DELETE ... TO cm_app,sequence 也要 GRANT USAGE, SELECT。每個語句加日期註解、檔頭 -- Date:,收尾 INSERT public.schema_migrations。② 起始值:另開一支 seed migration(phase=seed),system_menus group pre_engagement_category 四筆,ON CONFLICT ("group", key) DO NOTHING:
key=network value=網路架構 sort=1
key=org value=組織架構 sort=2
key=asset value=資產盤點 sort=3
key=policy value=政策文件與程序書 sort=4
先例:scripts/sql/2026-10-22-fr158-seed-pdf-parser-menu.sql。i18n 標籤走 lang.pre_engagement_category.<key>。
③ 權限與路由(併在 ① 那支或另開一支 active migration):抄 scripts/sql/2026-10-22-fr157-agent-console-capabilities.sql 的三段式。
oscal.pre-engagement.upload(事前資料:上傳與查看)、oscal.pre-engagement.manage-category(事前資料:管理大類)。前綴用 oscal,DEV 實查現有前綴沒有 governance。ui_routes 兩筆:pre-engagement(側選單「事前資料」)、pre-engagement-category-settings(系統設定區「事前資料大類」);route_capabilities 綁對應能力點。oscal.resource-library.update 的系統角色給兩顆;*.*.* 的 Administrator 自動涵蓋。受稽方要拿到 upload 那顆,請先查 DEV 現有系統角色名稱再決定 SQL,查不出合適角色就回寫問決策者。④ BE 模組:新模組 pre_engagement,結構照既有模組(參考 governance):domain/pre_engagement/(entity、repo interface、domain service)、infra/pre_engagement/(model 繼承 TenantScopedMixinModel、mapper、repo impl)、app/pre_engagement/、api/pre_engagement/、di_containers/pre_engagement/,並在 config/app_modules.py 註冊。這張只做大類;資料與版本的 model、repo 也建好,給卡 2 用。
⑤ 大類 API(守門用 common.authz 的 capability decorator):
GET /pre-engagement/categories upload 或 manage-category 都可讀;回每類資料數
POST /pre-engagement/categories manage-category;{name, description}
PUT /pre-engagement/categories/<uid> manage-category;改 name、description
DELETE /pre-engagement/categories/<uid> manage-category;底下有資料回 PRE_409002
PUT /pre-engagement/categories/order manage-category;[uid, ...] 依序寫 sort