This commit is contained in:
2025-12-05 00:09:32 +08:00
parent bb3d22eabf
commit 3d1dbb3f97
214 changed files with 113060 additions and 1232 deletions
@@ -0,0 +1,82 @@
-- 合同起草功能数据库迁移脚本
-- 创建时间: 2025-01-04
-- 1. 创建起草合同表
CREATE TABLE IF NOT EXISTS drafted_contracts (
id SERIAL PRIMARY KEY,
template_id INTEGER NOT NULL,
file_path TEXT NOT NULL,
title TEXT NOT NULL,
placeholder_values JSONB DEFAULT '{}'::jsonb,
status TEXT DEFAULT 'draft' CHECK (status IN ('draft', 'completed', 'archived')),
created_by INTEGER,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
CONSTRAINT fk_template
FOREIGN KEY (template_id)
REFERENCES contract_templates(id)
ON DELETE CASCADE,
CONSTRAINT fk_created_by
FOREIGN KEY (created_by)
REFERENCES auth.users(id)
ON DELETE SET NULL
);
-- 2. 创建索引优化查询性能
CREATE INDEX idx_drafted_contracts_template_id ON drafted_contracts(template_id);
CREATE INDEX idx_drafted_contracts_created_by ON drafted_contracts(created_by);
CREATE INDEX idx_drafted_contracts_status ON drafted_contracts(status);
CREATE INDEX idx_drafted_contracts_created_at ON drafted_contracts(created_at DESC);
-- 3. 创建更新时间触发器
CREATE OR REPLACE FUNCTION update_drafted_contracts_updated_at()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trigger_update_drafted_contracts_updated_at
BEFORE UPDATE ON drafted_contracts
FOR EACH ROW
EXECUTE FUNCTION update_drafted_contracts_updated_at();
-- 4. 添加注释
COMMENT ON TABLE drafted_contracts IS '起草的合同记录';
COMMENT ON COLUMN drafted_contracts.id IS '主键ID';
COMMENT ON COLUMN drafted_contracts.template_id IS '关联的合同模板ID';
COMMENT ON COLUMN drafted_contracts.file_path IS '起草后的文件路径(MinIO';
COMMENT ON COLUMN drafted_contracts.title IS '合同标题';
COMMENT ON COLUMN drafted_contracts.placeholder_values IS '占位符填充值(JSON格式)';
COMMENT ON COLUMN drafted_contracts.status IS '状态:draft-草稿,completed-已完成,archived-已归档';
COMMENT ON COLUMN drafted_contracts.created_by IS '创建人用户ID';
COMMENT ON COLUMN drafted_contracts.created_at IS '创建时间';
COMMENT ON COLUMN drafted_contracts.updated_at IS '更新时间';
-- 5. 为 contract_templates 表添加占位符配置字段
ALTER TABLE contract_templates
ADD COLUMN IF NOT EXISTS placeholder_schema JSONB DEFAULT NULL;
COMMENT ON COLUMN contract_templates.placeholder_schema IS '占位符配置SchemaJSON格式)';
-- 6. 示例:为测试模板添加占位符配置(可选,根据实际情况调整)
-- UPDATE contract_templates
-- SET placeholder_schema = '{
-- "fields": [
-- {"key": "甲方名称", "label": "甲方名称", "type": "text", "required": true, "group": "甲方信息"},
-- {"key": "甲方地址", "label": "甲方地址", "type": "text", "required": true, "group": "甲方信息"},
-- {"key": "甲方法定代表人", "label": "法定代表人", "type": "text", "required": true, "group": "甲方信息"},
-- {"key": "甲方联系电话", "label": "联系电话", "type": "tel", "required": true, "group": "甲方信息"},
-- {"key": "乙方名称", "label": "乙方名称", "type": "text", "required": true, "group": "乙方信息"},
-- {"key": "乙方地址", "label": "乙方地址", "type": "text", "required": true, "group": "乙方信息"},
-- {"key": "乙方法定代表人", "label": "法定代表人", "type": "text", "required": true, "group": "乙方信息"},
-- {"key": "乙方联系电话", "label": "联系电话", "type": "tel", "required": true, "group": "乙方信息"},
-- {"key": "合同金额", "label": "合同金额(元)", "type": "number", "required": true, "group": "合同条款"},
-- {"key": "签订日期", "label": "签订日期", "type": "date", "required": true, "group": "合同条款"},
-- {"key": "合同编号", "label": "合同编号", "type": "text", "required": false, "group": "基本信息"}
-- ]
-- }'::jsonb
-- WHERE id = 1;
+282
View File
@@ -0,0 +1,282 @@
# 文档版本管理功能 - 部署指南(使用函数名前缀)
## 📋 方案说明
使用 **函数名前缀 `documents_`** 来区分版本管理函数,无需创建 schema。
**3个新函数**
- `documents_get_latest_documents_with_version_info` - 获取文档列表
- `documents_count_latest_documents_with_filters` - 统计文档总数
- `documents_get_document_history` - 获取历史版本
---
## 🚀 快速部署(2步)
### 步骤1:在 Navicat 中执行 SQL 脚本
#### 方法A:查询窗口
1. 打开 Navicat,连接到 PostgreSQL 数据库
2. 点击 **"查询"** → **"新建查询"**
3. 打开 `add_document_version_management.sql` 文件
4. 复制所有内容到查询窗口
5. 点击 **"运行"** ▶️ 按钮
#### 方法B:运行 SQL 文件
1. 右键点击数据库
2. 选择 **"运行 SQL 文件"**
3. 选择 `add_document_version_management.sql`
4. 点击 **"开始"**
### 步骤2:重启应用
```bash
npm run build
pm2 restart all
```
**就这么简单!**
---
## 🔍 验证函数是否创建成功
在 Navicat 中执行:
```sql
-- 查看新创建的函数
SELECT proname
FROM pg_proc
WHERE proname LIKE 'documents_%';
```
**应该返回 3 个函数名**
```
documents_get_latest_documents_with_version_info
documents_count_latest_documents_with_filters
documents_get_document_history
```
### 在 Navicat 中查看函数
```
你的数据库
└─ public (schema)
└─ 函数
├─ documents_get_latest_documents_with_version_info(...)
├─ documents_count_latest_documents_with_filters(...)
└─ documents_get_document_history(...)
```
---
## 🧪 测试函数
在 Navicat 查询窗口执行:
```sql
-- 测试1:获取文档列表(假设用户ID为1)
SELECT * FROM documents_get_latest_documents_with_version_info(1, 1, 10);
-- 测试2:统计文档总数
SELECT documents_count_latest_documents_with_filters(1);
-- 测试3:获取历史版本(如果有同名文档)
SELECT * FROM documents_get_document_history('测试文档.pdf', 1, 123);
```
---
## 📁 在 Navicat 中操作函数
### 查看函数定义
1. 在左侧树形结构中找到 `public``函数`
2. 找到 `documents_get_latest_documents_with_version_info`
3. 右键 → **"设计函数"** 或 **"查看 SQL"**
### 测试函数(图形界面)
1. 右键点击函数
2. 选择 **"执行函数"**
3. 输入参数值(如 `p_user_id: 1, p_page: 1, p_page_size: 10`
4. 点击 **"运行"**
### 删除函数(如果需要重建)
```sql
DROP FUNCTION IF EXISTS documents_get_latest_documents_with_version_info;
DROP FUNCTION IF EXISTS documents_count_latest_documents_with_filters;
DROP FUNCTION IF EXISTS documents_get_document_history;
-- 然后重新执行 SQL 脚本
```
---
## ✅ 功能验证
### 1. 访问文档列表页面
打开浏览器:`http://your-domain/documents`
### 2. 检查显示效果
**有历史版本的文档**
```
[▶️] 📄 合同审查.pdf
合同审查 v1 (共3个历史版本)
问题数量:3 🟢 ↓ -2
```
**单版本文档**
```
📄 新文档.pdf
合同审查
问题数量:4
```
### 3. 测试展开功能
点击 [▶️] 图标,应该能看到:
```
[🔽] 📄 合同审查.pdf
合同审查 v1 (共3个历史版本)
问题数量:3 🟢 ↓ -2
├─ 🕒 v2 版本
│ 问题数量:5 🔴 ↑ +2
├─ 🕒 v3 版本
│ 问题数量:3 🟢 ↓ -1
└─ 🕒 v4 版本 (最早)
问题数量:4
```
---
## ❌ 故障排查
### 问题1:函数未创建
**症状**:查询不到函数
**解决**
```sql
-- 检查是否有错误
-- 在 Navicat 的"消息"窗口查看错误信息
-- 重新执行 SQL 脚本
```
### 问题2:前端报错
**症状**:浏览器控制台显示 `function does not exist`
**解决**
```bash
# 1. 确认函数已创建
# 在 Navicat 中执行:
SELECT proname FROM pg_proc WHERE proname LIKE 'documents_%';
# 2. 确认前端代码已更新
# 检查 app/api/files/documents.ts 中的函数名
# 3. 重新构建应用
npm run build
pm2 restart all
```
### 问题3:数据不显示
**症状**:页面显示空白或加载失败
**检查清单**
- ✅ SQL 脚本是否执行成功
- ✅ 函数是否创建在 `public` schema 中
- ✅ 应用是否重启
- ✅ 浏览器控制台是否有错误
**解决方案**
```bash
# 1. 检查 PostgREST 日志
journalctl -u postgrest -n 50
# 2. 检查应用日志
pm2 logs
# 3. 清除浏览器缓存
# 按 Ctrl + Shift + R (Windows) 或 Cmd + Shift + R (Mac)
```
---
## 📊 性能优化
SQL 脚本自动创建了 4 个索引来优化查询性能:
```sql
-- 1. 用户+名称+时间索引
idx_documents_user_name_created
-- 2. 名称+用户+时间索引
idx_documents_name_user_created
-- 3. 创建时间索引
idx_documents_created_at
-- 4. 文件状态索引
idx_documents_status
```
查看索引:
```sql
SELECT indexname, indexdef
FROM pg_indexes
WHERE tablename = 'documents';
```
---
## 🔐 权限配置(可选)
如果你使用了特定的 PostgREST 用户,需要授权:
```sql
-- 查看你的 PostgREST 用户名
SELECT usename FROM pg_user;
-- 授权函数(替换 'authenticator' 为实际用户名)
GRANT EXECUTE ON FUNCTION documents_get_latest_documents_with_version_info TO authenticator;
GRANT EXECUTE ON FUNCTION documents_count_latest_documents_with_filters TO authenticator;
GRANT EXECUTE ON FUNCTION documents_get_document_history TO authenticator;
```
---
## 📚 相关文档
- **SQL 脚本**`add_document_version_management.sql`
- **完整技术文档**`docs/文档版本管理设计方案.md`
- **UI 原型**`docs/UI设计原型.html`
---
## ✅ 部署检查清单
- [ ] SQL 脚本执行成功
- [ ] 3个函数已创建
- [ ] 应用已重新构建和重启
- [ ] 文档列表页面正常显示
- [ ] 展开/折叠功能正常
- [ ] 问题数量差异正确显示(红色/绿色)
---
## 🎉 完成!
现在你可以:
- ✅ 查看同名文档的版本历史
- ✅ 展开/折叠历史版本
- ✅ 对比问题数量变化
- ✅ 享受高性能的版本管理功能
**部署完成,开始使用吧!** 🚀
@@ -0,0 +1,340 @@
# 文档版本管理 - 使用 documents Schema 部署指南
## 📋 文件说明
本次使用 **`documents` schema** 来组织所有文档版本管理相关的 RPC 函数。
**SQL 脚本位置**`add_document_version_management_with_schema.sql`
---
## 🚀 快速部署(3步)
### 步骤1:在 Navicat 中执行 SQL 脚本
#### 方法A:通过查询窗口
1. 打开 Navicat,连接到你的 PostgreSQL 数据库
2. 点击 **"查询"** → **"新建查询"**
3. 打开 `add_document_version_management_with_schema.sql` 文件
4. 复制所有内容到查询窗口
5. **⚠️ 重要:将所有的 `authenticator` 替换为你的实际 PostgREST 用户名**
6. 点击 **"运行"** 按钮
#### 方法B:通过文件导入
1. 在 Navicat 中右键点击数据库
2. 选择 **"运行 SQL 文件"**
3. 选择 `add_document_version_management_with_schema.sql`
4. 点击 **"开始"**
### 步骤2:验证函数是否创建成功
在 Navicat 查询窗口执行:
```sql
-- 查看 documents schema 中的所有函数
SELECT proname, proargnames
FROM pg_proc p
JOIN pg_namespace n ON p.pronamespace = n.oid
WHERE n.nspname = 'documents';
```
**应该看到 3 个函数**
- `get_latest_documents_with_version_info`
- `count_latest_documents_with_filters`
- `get_document_history`
### 步骤3:测试函数(可选)
```sql
-- 测试1:获取文档列表(假设用户ID为1)
SELECT * FROM documents.get_latest_documents_with_version_info(1, 1, 10);
-- 测试2:统计文档数量
SELECT documents.count_latest_documents_with_filters(1);
```
---
## 📁 Navicat 中查看函数
执行成功后,在 Navicat 左侧树形结构中:
```
你的数据库
└─ 模式 (Schemas)
├─ public
└─ documents ← 新创建的 schema
├─ 表
└─ 函数 ← 3个新函数在这里
├─ get_latest_documents_with_version_info
├─ count_latest_documents_with_filters
└─ get_document_history
```
---
## ⚙️ PostgREST 配置
确保你的 PostgREST 配置文件包含 `documents` schema
```conf
# /etc/postgrest/config (或其他配置文件路径)
# 添加 documents 到 db-schemas
db-schemas = "public, documents"
# 或者,如果之前没有配置,添加这一行
db-schemas = "public, documents"
```
修改配置后重启 PostgREST
```bash
sudo systemctl restart postgrest
```
---
## 🔐 权限配置
### 查找你的 PostgREST 用户名
```sql
-- 方法1:查看所有数据库用户
SELECT usename FROM pg_user;
-- 方法2:查看当前连接用户
SELECT current_user;
```
**常见的 PostgREST 用户名**
- `authenticator`
- `web_anon`
- `postgres`
- `api_user`
### 授权函数给用户
将下面的 `YOUR_POSTGREST_USER` 替换为你的实际用户名:
```sql
-- 授权 schema 使用权限
GRANT USAGE ON SCHEMA documents TO YOUR_POSTGREST_USER;
-- 授权函数执行权限
GRANT EXECUTE ON FUNCTION documents.get_latest_documents_with_version_info TO YOUR_POSTGREST_USER;
GRANT EXECUTE ON FUNCTION documents.count_latest_documents_with_filters TO YOUR_POSTGREST_USER;
GRANT EXECUTE ON FUNCTION documents.get_document_history TO YOUR_POSTGREST_USER;
```
---
## 🎯 前端代码已自动适配
前端代码已经修改为使用 `documents` schema
```typescript
// app/api/files/documents.ts
// ✅ 已修改为:
postgrestPost('rpc/documents.get_latest_documents_with_version_info', ...)
postgrestPost('rpc/documents.count_latest_documents_with_filters', ...)
postgrestPost('rpc/documents.get_document_history', ...)
```
**无需手动修改前端代码!**
---
## 🧪 完整测试流程
### 1. 在 Navicat 中测试 SQL
```sql
-- 测试获取文档列表
SELECT * FROM documents.get_latest_documents_with_version_info(
p_user_id := 1,
p_page := 1,
p_page_size := 10
);
-- 测试搜索功能
SELECT * FROM documents.get_latest_documents_with_version_info(
p_user_id := 1,
p_page := 1,
p_page_size := 10,
p_search_name := '合同'
);
-- 测试历史版本查询
SELECT * FROM documents.get_document_history('测试文档.pdf', 1, 123);
```
### 2. 测试 PostgREST API
```bash
# 测试获取文档列表
curl -X POST http://your-api-url/rpc/documents.get_latest_documents_with_version_info \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-d '{
"p_user_id": 1,
"p_page": 1,
"p_page_size": 10
}'
```
### 3. 测试前端页面
1. 重启应用:
```bash
npm run build
pm2 restart all
```
2. 访问文档列表页面:`http://your-domain/documents`
3. 检查功能:
- ✅ 文档列表正常显示
- ✅ 有历史版本的文档显示展开图标
- ✅ 点击展开图标能看到历史版本
- ✅ 问题数量差异正确显示(红色/绿色)
---
## ❌ 故障排查
### 问题1:函数未创建成功
**症状**:执行查询时提示 `function does not exist`
**解决方案**
```sql
-- 检查 documents schema 是否存在
SELECT schema_name FROM information_schema.schemata WHERE schema_name = 'documents';
-- 如果不存在,手动创建
CREATE SCHEMA IF NOT EXISTS documents;
-- 重新执行 SQL 脚本
```
### 问题2:权限不足
**症状**:前端报错 `permission denied`
**解决方案**
```sql
-- 检查当前用户权限
SELECT grantee, privilege_type
FROM information_schema.role_usage_grants
WHERE object_schema = 'documents';
-- 重新授权(替换 YOUR_USER 为实际用户)
GRANT USAGE ON SCHEMA documents TO YOUR_USER;
GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA documents TO YOUR_USER;
```
### 问题3:前端调用失败
**症状**:浏览器控制台报错 `404 Not Found`
**检查清单**
1. PostgREST 配置是否包含 `documents` schema
2. PostgREST 是否已重启
3. 前端代码是否已更新并重新构建
**解决方案**
```bash
# 1. 检查 PostgREST 配置
cat /etc/postgrest/config
# 2. 确保包含:
# db-schemas = "public, documents"
# 3. 重启 PostgREST
sudo systemctl restart postgrest
# 4. 重新构建前端
npm run build
pm2 restart all
```
---
## 📊 性能优化
SQL 脚本已自动创建以下索引:
```sql
-- 1. 用户+名称+时间索引
CREATE INDEX idx_documents_user_name_created
ON documents(user_id, name, created_at DESC);
-- 2. 名称+用户+时间索引
CREATE INDEX idx_documents_name_user_created
ON documents(name, user_id, created_at DESC);
-- 3. 创建时间索引
CREATE INDEX idx_documents_created_at
ON documents(created_at DESC);
-- 4. 文件状态索引
CREATE INDEX idx_documents_status
ON documents(status);
```
查看索引是否创建成功:
```sql
SELECT indexname, indexdef
FROM pg_indexes
WHERE tablename = 'documents'
AND schemaname = 'public';
```
---
## ✅ 部署检查清单
- [ ] SQL 脚本执行成功
- [ ] 3个函数都已创建在 `documents` schema 中
- [ ] 已授权给 PostgREST 用户
- [ ] PostgREST 配置已更新
- [ ] PostgREST 已重启
- [ ] 前端应用已重新构建
- [ ] 文档列表页面正常显示
- [ ] 展开/折叠功能正常工作
- [ ] 问题数量差异正确显示
---
## 🎉 完成!
部署完成后,你应该能看到:
```
文档列表页面
├─ [▶️] 📄 合同审查.pdf
│ 合同审查 v1 (共3个历史版本)
│ 问题数量:3 🟢 ↓ -2
├─ [▶️] 📄 许可证申请.docx
│ 许可证审查 v1 (共2个历史版本)
│ 问题数量:5 🔴 ↑ +1
└─ 📄 新文档.pdf
合同审查
问题数量:4
```
点击展开图标后:
```
[🔽] 📄 合同审查.pdf
合同审查 v1 (共3个历史版本)
问题数量:3 🟢 ↓ -2
├─ 🕒 v2 版本 (问题:5 🔴 ↑ +2)
├─ 🕒 v3 版本 (问题:3 🟢 ↓ -1)
└─ 🕒 v4 版本 (最早) (问题:4)
```
**功能完美运行!** 🚀
@@ -0,0 +1,255 @@
-- ============================================
-- 文档版本管理 RPC 函数(第4版 - 显式类型转换)
-- 功能:支持同名文档的版本管理和历史查询
-- 修复:所有字段显式转换为函数返回类型
-- 创建时间:2025-11-14
-- ============================================
-- ====================
-- 删除旧函数
-- ====================
DROP FUNCTION IF EXISTS documents_get_latest_documents_with_version_info(integer,integer,integer,text,text,integer[],integer,text,text,text);
DROP FUNCTION IF EXISTS documents_count_latest_documents_with_filters(integer,text,text,integer[],integer,text,text,text);
DROP FUNCTION IF EXISTS documents_get_document_history(text,integer,integer);
-- ====================
-- 1. 获取最新版本的文档列表(带版本信息)
-- ====================
CREATE FUNCTION documents_get_latest_documents_with_version_info(
p_user_id integer,
p_page integer DEFAULT 1,
p_page_size integer DEFAULT 10,
p_search_name text DEFAULT NULL,
p_search_document_number text DEFAULT NULL,
p_search_document_types integer[] DEFAULT NULL,
p_search_audit_status integer DEFAULT NULL,
p_search_file_status text DEFAULT NULL,
p_search_date_from text DEFAULT NULL,
p_search_date_to text DEFAULT NULL
)
RETURNS TABLE (
id integer,
name text,
document_number text,
type_id integer,
type_name text,
file_size integer,
audit_status integer,
status text,
false_count bigint,
created_at timestamp with time zone,
updated_at timestamp with time zone,
path text,
is_test_document boolean,
ocr_result jsonb,
history_count bigint,
previous_issues bigint
) AS $$
BEGIN
RETURN QUERY
WITH latest_docs AS (
-- 使用 DISTINCT ON 获取每个文档名称的最新版本
SELECT DISTINCT ON (d.name)
d.id,
d.name,
d.user_id,
d.created_at
FROM documents d
WHERE d.user_id = p_user_id
-- 文档名称搜索
AND (p_search_name IS NULL OR d.name ILIKE '%' || p_search_name || '%')
-- 文档编号搜索
AND (p_search_document_number IS NULL OR d.document_number ILIKE '%' || p_search_document_number || '%')
-- 文档类型筛选
AND (p_search_document_types IS NULL OR d.type_id = ANY(p_search_document_types))
-- 审核状态筛选
AND (p_search_audit_status IS NULL OR d.audit_status = p_search_audit_status)
-- 文件状态筛选
AND (p_search_file_status IS NULL OR d.status = p_search_file_status)
-- 日期范围筛选
AND (p_search_date_from IS NULL OR d.created_at >= p_search_date_from::timestamp)
AND (p_search_date_to IS NULL OR d.created_at <= p_search_date_to::timestamp)
ORDER BY d.name, d.created_at DESC
)
SELECT
d.id::integer,
d.name::text,
d.document_number::text,
d.type_id::integer,
COALESCE(dt.name::text, '') as type_name,
d.file_size::integer,
d.audit_status::integer,
d.status::text,
-- 计算当前文档的问题数量(从 evaluation_results 表统计)
COALESCE((
SELECT COUNT(*)
FROM evaluation_results er
WHERE er.document_id = d.id
AND (er.evaluated_results ->> 'result')::text = 'false'
), 0)::bigint as false_count,
d.created_at::timestamp with time zone,
d.updated_at::timestamp with time zone,
d.path::text,
d.is_test_document::boolean,
d.ocr_result::jsonb,
-- 计算历史版本数量(不包含当前版本)
COALESCE((
SELECT COUNT(*)
FROM documents d2
WHERE d2.name = d.name
AND d2.user_id = d.user_id
AND d2.id != d.id
), 0)::bigint as history_count,
-- 获取上一个版本的问题数量
COALESCE((
SELECT COUNT(*)
FROM evaluation_results er2
WHERE er2.document_id = (
SELECT d3.id
FROM documents d3
WHERE d3.name = d.name
AND d3.user_id = d.user_id
AND d3.created_at < d.created_at
ORDER BY d3.created_at DESC
LIMIT 1
)
AND (er2.evaluated_results ->> 'result')::text = 'false'
), 0)::bigint as previous_issues
FROM documents d
INNER JOIN latest_docs ld ON d.id = ld.id
LEFT JOIN document_types dt ON d.type_id = dt.id
ORDER BY d.created_at DESC
LIMIT p_page_size OFFSET (p_page - 1) * p_page_size;
END;
$$ LANGUAGE plpgsql;
COMMENT ON FUNCTION documents_get_latest_documents_with_version_info IS '获取最新版本的文档列表,包含历史版本数量和上一个版本的问题数量(从 evaluation_results 表计算)';
-- ====================
-- 2. 统计最新版本文档的总数
-- ====================
CREATE FUNCTION documents_count_latest_documents_with_filters(
p_user_id integer,
p_search_name text DEFAULT NULL,
p_search_document_number text DEFAULT NULL,
p_search_document_types integer[] DEFAULT NULL,
p_search_audit_status integer DEFAULT NULL,
p_search_file_status text DEFAULT NULL,
p_search_date_from text DEFAULT NULL,
p_search_date_to text DEFAULT NULL
)
RETURNS integer AS $$
DECLARE
doc_count integer;
BEGIN
WITH latest_docs AS (
SELECT DISTINCT ON (d.name)
d.id
FROM documents d
WHERE d.user_id = p_user_id
AND (p_search_name IS NULL OR d.name ILIKE '%' || p_search_name || '%')
AND (p_search_document_number IS NULL OR d.document_number ILIKE '%' || p_search_document_number || '%')
AND (p_search_document_types IS NULL OR d.type_id = ANY(p_search_document_types))
AND (p_search_audit_status IS NULL OR d.audit_status = p_search_audit_status)
AND (p_search_file_status IS NULL OR d.status = p_search_file_status)
AND (p_search_date_from IS NULL OR d.created_at >= p_search_date_from::timestamp)
AND (p_search_date_to IS NULL OR d.created_at <= p_search_date_to::timestamp)
ORDER BY d.name, d.created_at DESC
)
SELECT COUNT(*)::integer INTO doc_count FROM latest_docs;
RETURN doc_count;
END;
$$ LANGUAGE plpgsql;
COMMENT ON FUNCTION documents_count_latest_documents_with_filters IS '统计符合条件的最新版本文档总数(用于分页)';
-- ====================
-- 3. 获取文档的历史版本列表
-- ====================
CREATE FUNCTION documents_get_document_history(
p_document_name text,
p_user_id integer,
p_exclude_id integer
)
RETURNS TABLE (
id integer,
name text,
document_number text,
type_id integer,
type_name text,
file_size integer,
audit_status integer,
status text,
false_count bigint,
created_at timestamp with time zone,
updated_at timestamp with time zone,
path text,
is_test_document boolean,
ocr_result jsonb
) AS $$
BEGIN
RETURN QUERY
SELECT
d.id::integer,
d.name::text,
d.document_number::text,
d.type_id::integer,
COALESCE(dt.name::text, '') as type_name,
d.file_size::integer,
d.audit_status::integer,
d.status::text,
-- 计算每个历史版本的问题数量
COALESCE((
SELECT COUNT(*)
FROM evaluation_results er
WHERE er.document_id = d.id
AND (er.evaluated_results ->> 'result')::text = 'false'
), 0)::bigint as false_count,
d.created_at::timestamp with time zone,
d.updated_at::timestamp with time zone,
d.path::text,
d.is_test_document::boolean,
d.ocr_result::jsonb
FROM documents d
LEFT JOIN document_types dt ON d.type_id = dt.id
WHERE d.name = p_document_name
AND d.user_id = p_user_id
AND d.id != p_exclude_id
ORDER BY d.created_at DESC;
END;
$$ LANGUAGE plpgsql;
COMMENT ON FUNCTION documents_get_document_history IS '获取指定文档名称的所有历史版本(不包含指定的当前版本ID),从 evaluation_results 表计算问题数量';
-- ====================
-- 4. 创建索引优化查询性能
-- ====================
-- 复合索引:user_id + name + created_at
CREATE INDEX IF NOT EXISTS idx_documents_user_name_created
ON documents(user_id, name, created_at DESC);
-- 复合索引:name + user_id + created_at
CREATE INDEX IF NOT EXISTS idx_documents_name_user_created
ON documents(name, user_id, created_at DESC);
-- 单列索引:created_at
CREATE INDEX IF NOT EXISTS idx_documents_created_at
ON documents(created_at DESC);
-- 单列索引:status
CREATE INDEX IF NOT EXISTS idx_documents_status
ON documents(status);
-- evaluation_results 的 document_id
CREATE INDEX IF NOT EXISTS idx_evaluation_results_document_id
ON evaluation_results(document_id);
-- evaluation_results 的 evaluated_results->>'result'
CREATE INDEX IF NOT EXISTS idx_evaluation_results_result
ON evaluation_results((evaluated_results ->> 'result'));
@@ -0,0 +1,36 @@
-- 入口模块地区管理 - 数据迁移脚本
-- 将 areas 字段从字符串数组转换为对象数组,支持启用/禁用状态
-- 1. 添加备份列
ALTER TABLE entry_modules ADD COLUMN IF NOT EXISTS areas_backup JSONB;
-- 2. 备份原数据
UPDATE entry_modules SET areas_backup = areas WHERE areas IS NOT NULL;
-- 3. 转换数据格式
UPDATE entry_modules
SET areas = (
SELECT jsonb_agg(
jsonb_build_object(
'area', area_name,
'enabled', true,
'sort_order', row_number
) ORDER BY row_number
)
FROM (
SELECT area_name, ROW_NUMBER() OVER () as row_number
FROM jsonb_array_elements_text(areas) AS area_name
) AS numbered_areas
)
WHERE areas IS NOT NULL AND jsonb_typeof(areas) = 'array';
-- 4. 添加注释
COMMENT ON COLUMN entry_modules.areas IS '地区配置: [{"area": "地区名", "enabled": true/false, "sort_order": 排序号}]';
-- 5. 查看迁移结果
SELECT id, name, areas_backup AS old_format, areas AS new_format
FROM entry_modules WHERE areas IS NOT NULL;
-- 回滚(如需要):
-- UPDATE entry_modules SET areas = areas_backup WHERE areas_backup IS NOT NULL;
-- ALTER TABLE entry_modules DROP COLUMN areas_backup;
@@ -0,0 +1,50 @@
-- 入口模块地区管理 - 常用操作
-- 查看所有配置
SELECT id, name, jsonb_pretty(areas) AS configs FROM entry_modules;
-- 禁用某个入口在某个地区(示例:禁用id=1在"云浮"
UPDATE entry_modules
SET areas = (
SELECT jsonb_agg(CASE WHEN elem->>'area' = '云浮'
THEN jsonb_set(elem, '{enabled}', 'false'::jsonb) ELSE elem END)
FROM jsonb_array_elements(areas) AS elem
), updated_at = NOW()
WHERE id = 1;
-- 启用某个入口在某个地区(示例:启用id=1在"云浮"
UPDATE entry_modules
SET areas = (
SELECT jsonb_agg(CASE WHEN elem->>'area' = '云浮'
THEN jsonb_set(elem, '{enabled}', 'true'::jsonb) ELSE elem END)
FROM jsonb_array_elements(areas) AS elem
), updated_at = NOW()
WHERE id = 1;
-- 添加新地区(示例:为id=1添加"揭阳"
UPDATE entry_modules
SET areas = areas || jsonb_build_array(
jsonb_build_object('area', '揭阳', 'enabled', true, 'sort_order',
(SELECT COALESCE(MAX((elem->>'sort_order')::int), 0) + 1
FROM jsonb_array_elements(areas) AS elem))
), updated_at = NOW()
WHERE id = 1 AND NOT EXISTS (
SELECT 1 FROM jsonb_array_elements(areas) AS elem WHERE elem->>'area' = '揭阳'
);
-- 删除某个地区配置(示例:从id=1删除"云浮"
UPDATE entry_modules
SET areas = (SELECT jsonb_agg(elem) FROM jsonb_array_elements(areas) AS elem
WHERE elem->>'area' != '云浮'
), updated_at = NOW()
WHERE id = 1;
-- 查看某个地区已启用的模块(示例:查看"梅州")
SELECT id, name FROM entry_modules
WHERE areas @> '[{"area": "梅州", "enabled": true}]'::jsonb;
-- 统计每个地区的已启用模块数量
SELECT elem->>'area' AS area, COUNT(*) AS count
FROM entry_modules, jsonb_array_elements(areas) AS elem
WHERE (elem->>'enabled')::boolean = true
GROUP BY elem->>'area';