# 合同模板上传与地区隔离改造方案 ## 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. 权限种子补齐 这套范围足以让合同模板模块从“只读展示”升级为“可管理上传的地区化模板库”。