# 合同模板上传与地区隔离改造方案

## 1. 背景

当前合同模板模块已具备以下只读能力：

- `/api/v3/contract-templates/categories`
- `/api/v3/contract-templates`
- `/api/v3/contract-templates/search`
- `/api/v3/contract-templates/{id}`

当前实现可以支撑模板分类、列表、搜索、详情展示，但还不具备正式的模板管理上传能力。现阶段新增需求包括：

- 在 `/contract-template/list` 页面支持上传合同模板
- 新模板必须存入 `leaudit_platform` 主库，不再依赖旧项目库
- 模板数据需要支持地区区分
- 模板数据需要支持完整审计字段
- 模板数据需要支持软删除
- 前端与后端需统一按当前 LeAudit/FastAPI 风格实现

本方案仅覆盖“合同模板管理与上传”，不扩展“合同起草”独立业务模块。

## 2. 现状问题

### 2.1 数据表不足

当前 `contract_templates` 仅包含：

- `template_code`
- `title`
- `category_id`
- `description`
- `file_path`
- `file_format`
- `pdf_file_path`
- `is_featured`
- `created_at`
- `updated_at`

存在以下问题：

- 没有 `region`，无法做地区隔离
- 没有 `created_by / updated_by`，无法追踪操作者
- 没有 `deleted_at`，无法软删除
- 没有 `original_file_name / mime_type / file_size`，无法完整表达上传文件元数据
- 当前唯一约束仅为 `template_code` 全局唯一，不适合多地区模板管理

### 2.2 读接口缺少统一数据范围控制

当前合同模板读接口没有套用平台现有“按用户地区可见”的规则，无法满足：

- 省级管理员查看全部或指定地区
- 地市管理员仅查看本地区及公共模板
- 非管理用户限制可见范围

### 2.3 OSS 路径未带地区

当前 `BuildContractTemplateKey()` 只按：

- 分类
- 模板编码
- 文件角色

生成路径，未带 `region`，多地区下会出现路径命名冲突与后期归档困难。

### 2.4 权限不足

当前仅具备读权限：

- `contract_template:list:read`
- `contract_template:search:read`
- `contract_template:detail:read`

尚未具备：

- 上传创建权限
- 编辑权限
- 删除权限

## 3. 设计目标

本次改造目标如下：

1. 为合同模板模块补齐上传能力
2. 按地区隔离模板数据
3. 支持审计字段与软删除
4. 保持与现有 `govdoc` / `document` 模块相同的权限和地区控制风格
5. 新上传模板统一走新 OSS 路径规范
6. 不影响现有搜索、列表、详情页面的继续使用

## 4. 数据模型设计

### 4.1 `contract_categories`

分类暂不做地区化，保留为全局字典表，但补齐审计与软删除字段。

建议字段：

- `id BIGSERIAL/SERIAL PRIMARY KEY`
- `name VARCHAR(100) NOT NULL`
- `icon VARCHAR(100) NULL`
- `description TEXT NULL`
- `sort_order INTEGER NOT NULL DEFAULT 0`
- `created_by BIGINT NULL`
- `created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()`
- `updated_by BIGINT NULL`
- `updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()`
- `deleted_at TIMESTAMPTZ NULL`

索引建议：

- `UNIQUE INDEX uq_contract_categories_name_active ON contract_categories(name) WHERE deleted_at IS NULL`
- `INDEX idx_contract_categories_sort_active ON contract_categories(sort_order) WHERE deleted_at IS NULL`

### 4.2 `contract_templates`

模板表补齐地区、上传元数据、审计字段、软删除字段。

建议字段：

- `id BIGSERIAL PRIMARY KEY`
- `template_code VARCHAR(50) NOT NULL`
- `title VARCHAR(200) NOT NULL`
- `category_id INTEGER NOT NULL REFERENCES contract_categories(id)`
- `region VARCHAR(50) NOT NULL DEFAULT '省级'`
- `description TEXT NULL`
- `file_path VARCHAR(500) NULL`
- `pdf_file_path VARCHAR(500) NULL`
- `file_format VARCHAR(10) NOT NULL`
- `original_file_name VARCHAR(500) NOT NULL DEFAULT ''`
- `mime_type VARCHAR(200) NULL`
- `file_size BIGINT NOT NULL DEFAULT 0`
- `pdf_file_size BIGINT NULL`
- `is_featured BOOLEAN NOT NULL DEFAULT FALSE`
- `created_by BIGINT NULL`
- `created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()`
- `updated_by BIGINT NULL`
- `updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()`
- `deleted_at TIMESTAMPTZ NULL`

索引建议：

- `UNIQUE INDEX uq_contract_templates_region_code_active ON contract_templates(region, template_code) WHERE deleted_at IS NULL`
- `INDEX idx_contract_templates_region_active ON contract_templates(region) WHERE deleted_at IS NULL`
- `INDEX idx_contract_templates_category_active ON contract_templates(category_id) WHERE deleted_at IS NULL`
- `INDEX idx_contract_templates_updated_active ON contract_templates(updated_at DESC) WHERE deleted_at IS NULL`

### 4.3 软删除策略

所有列表、搜索、详情查询统一增加：

- `deleted_at IS NULL`

删除接口只做：

- `deleted_at = NOW()`
- `updated_at = NOW()`
- `updated_by = 当前用户`

本期不执行物理删除 OSS 对象，避免误删无法恢复。

## 5. 地区隔离策略

### 5.1 用户上下文来源

复用现有用户上下文判断逻辑，基于：

- `sso_users.area`
- 用户角色 `super_admin / provincial_admin / admin / 普通用户`

参考现有实现：

- `documentServiceImpl._getCurrentUserContext`
- `documentServiceImpl._buildDocumentScopeFilters`
- `govdocServiceImpl._resolve_upload_region`

### 5.2 模板可见性规则

采用“省级公共模板 + 地区私有模板”的一层可见性模型：

- `region = '省级'`：全局公共模板
- `region = 用户地区`：当前地区私有模板

读取规则：

- `super_admin / provincial_admin`
  - 默认可查看全部地区
  - 支持显式传 `region` 做筛选
- `admin`
  - 默认可查看 `('省级', 自己地区)` 模板
  - 若传入其他地区参数，返回空或直接拒绝
- 普通用户
  - 默认可查看 `('省级', 自己地区)` 模板
  - 不允许跨地区筛选

### 5.3 上传地区规则

上传接口中，`region` 使用以下规则解析：

- `super_admin / provincial_admin`
  - 可选择上传到任意地区，含 `省级`
- `admin`
  - 只能上传到自己地区
- 普通用户
  - 默认不开放上传权限

### 5.4 为什么本期不做“地区模板覆盖省级模板”

本期不做同 `template_code` 的覆盖优先级逻辑，原因：

- 列表/搜索会出现同编码多条模板
- 详情页需要增加“实际命中版本”优先级
- 后续还会影响起草、下载、预览引用

本期先做“地区隔离 + 独立模板记录”，后续若业务明确需要，再做二期“本地覆盖省级模板”。

## 6. OSS 存储设计

### 6.1 新路径规范

建议合同模板 OSS key 改为：

`contract-templates/{region}/{category}/{template_code}/{role}__{filename}`

例如：

- `contract-templates/省级/房屋租赁/HT-RL-001/source__房屋租赁合同.docx`
- `contract-templates/梅州/房屋租赁/HT-RL-001/preview__房屋租赁合同.pdf`

### 6.2 文件角色

- 主模板文件：`source`
- 预览 PDF 文件：`preview`

### 6.3 老数据策略

老数据短期不强制迁移到新地区路径：

- 已有历史数据继续可读
- 新上传统一走新路径

后续若需要统一整洁，再单独执行历史迁移脚本，将存量模板迁到 `contract-templates/省级/...`

## 7. 接口设计

### 7.1 新增接口

#### 7.1.1 上传模板

- `POST /api/v3/contract-templates`
- 权限：`contract_template:create`
- Content-Type：`multipart/form-data`

表单字段：

- `title: str`
- `template_code: str`
- `category_id: int`
- `region: str`
- `description: str | None`
- `is_featured: bool`
- `file: UploadFile`
- `pdf_file: UploadFile | None`

返回：

- 模板基础信息
- 文件路径
- 地区信息
- 审计时间

#### 7.1.2 更新模板

- `PUT /api/v3/contract-templates/{id}`
- 权限：`contract_template:update`

本期先可只支持元数据更新，文件替换可选一起补。

#### 7.1.3 删除模板

- `DELETE /api/v3/contract-templates/{id}`
- 权限：`contract_template:delete`

行为：

- 软删除，不物理删 OSS

### 7.2 既有接口增强

#### 7.2.1 分类接口

- 过滤 `deleted_at IS NULL`
- 返回 `template_count` 时只统计当前用户可见地区且未删除模板

#### 7.2.2 列表接口

新增可选参数：

- `region`

逻辑：

- 仅返回当前用户可见模板
- 默认按 `updated_at DESC`

#### 7.2.3 搜索接口

新增可选参数：

- `region`

逻辑：

- 仅搜索当前用户可见模板

#### 7.2.4 详情接口

逻辑：

- 校验模板存在
- 校验模板未删除
- 校验当前用户对模板所在地区可见

## 8. 权限设计

新增权限：

- `contract_template:create`
- `contract_template:update`
- `contract_template:delete`

保留权限：

- `contract_template:list:read`
- `contract_template:search:read`
- `contract_template:detail:read`

角色建议：

- `super_admin`
  - 全部读写删
- `provincial_admin`
  - 全部读写删
- `admin`
  - 读
  - 上传
  - 编辑本地区模板
  - 删除本地区模板
- 普通用户
  - 仅按需开放读权限

## 9. 前端设计

### 9.1 列表页入口

在 `/contract-template/list` 页面右上角增加：

- “上传模板”按钮

仅有 `contract_template:create` 权限时展示。

### 9.2 上传弹窗字段

- 模板标题
- 模板编码
- 模板分类
- 所属地区
- 模板简介
- 是否推荐
- 模板主文件
- 预览 PDF 文件（可选）

### 9.3 前端交互规则

- `admin` 用户地区默认锁定为自己地区
- `provincial_admin` 可选择地区
- 上传成功后：
  - 提示成功
  - 关闭弹窗
  - 刷新当前列表
- 上传失败时：
  - 表单级错误走 toast
  - 403 需给出明确无权限提示

### 9.4 API 封装

在 `lib/api/contract-template/index.ts` 增加：

- `createContractTemplate`
- `updateContractTemplate`
- `deleteContractTemplate`

上传使用 `FormData` 直接请求后端，不走多余代理语义。

## 10. 数据迁移方案

### 10.1 老表扩展

通过增量 SQL：

- 新增缺失列
- 回填默认值
- 修正索引
- 增加 `updated_at` 触发器

### 10.2 老数据回填

建议回填：

- `region = '省级'`
- `original_file_name = ''` 或按旧路径推导
- `file_size = 0`
- `deleted_at = NULL`

### 10.3 回滚策略

若上传接口上线后发现问题：

- 可先关闭前端上传入口
- 已有新字段与索引保持兼容，不影响只读能力

## 11. 开发步骤

### 阶段 1：基础设施

1. 扩展合同模板 SQL
2. 扩展 RBAC seed
3. 更新 OSS 路径工具

### 阶段 2：后端接口

1. 增加 DTO/VO
2. 扩展 Service 接口
3. 实现上传、更新、删除
4. 为列表、搜索、详情补地区过滤

### 阶段 3：前端页面

1. 扩展 API 客户端
2. 在列表页增加上传按钮和弹窗
3. 接地区选择与权限控制

### 阶段 4：验证

1. 省级管理员上传省级模板
2. 地市管理员上传本地区模板
3. 非本地区参数校验
4. 403 提示
5. 列表、搜索、详情联调

## 12. 本期明确不做

- 合同起草模块重构
- 模板占位符自动解析
- docx 自动转 pdf
- 地区模板覆盖省级模板优先级
- 模板版本管理
- OSS 历史模板统一搬迁

## 13. 推荐本期交付范围

建议本期落地以下完整闭环：

1. 合同模板表完成地区化、审计化、软删除改造
2. 合同模板后端新增上传接口
3. 合同模板列表页新增上传弹窗
4. 合同模板读接口支持地区可见性控制
5. 权限种子补齐

这套范围足以让合同模板模块从“只读展示”升级为“可管理上传的地区化模板库”。
