# zqyy_app — Web 管理后台表结构文档

## 变更说明

在 `zqyy_app` 数据库中新增 4 张表，支撑 Web 管理后台的用户认证、任务队列、执行日志和定时调度功能。

| 操作 | 表名 | 说明 |
|------|------|------|
| 新增 | admin_users | 管理后台操作员账户，绑定门店 site_id |
| 新增 | task_queue | ETL 任务执行队列，支持排序和状态流转 |
| 新增 | task_execution_log | 任务执行历史记录，含日志和摘要 |
| 新增 | scheduled_tasks | 定时调度任务，支持 5 种调度类型 |

### 新增字段概览

#### admin_users
| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| id | SERIAL | PK | 自增主键 |
| username | VARCHAR(64) | UNIQUE NOT NULL | 登录用户名 |
| password_hash | VARCHAR(256) | NOT NULL | bcrypt 密码哈希 |
| display_name | VARCHAR(128) | | 显示名称 |
| site_id | BIGINT | NOT NULL | 绑定的门店 ID |
| is_active | BOOLEAN | DEFAULT TRUE | 是否启用 |
| created_at | TIMESTAMPTZ | DEFAULT NOW() | 创建时间 |
| updated_at | TIMESTAMPTZ | DEFAULT NOW() | 更新时间 |

#### task_queue
| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| id | UUID | PK, DEFAULT gen_random_uuid() | 队列任务 ID |
| site_id | BIGINT | NOT NULL | 门店隔离 |
| config | JSONB | NOT NULL | 序列化的 TaskConfig |
| status | VARCHAR(20) | NOT NULL, DEFAULT 'pending' | pending/running/success/failed/cancelled |
| position | INTEGER | NOT NULL, DEFAULT 0 | 队列排序位置 |
| created_at | TIMESTAMPTZ | DEFAULT NOW() | 创建时间 |
| started_at | TIMESTAMPTZ | | 开始执行时间 |
| finished_at | TIMESTAMPTZ | | 执行完成时间 |
| exit_code | INTEGER | | 子进程退出码 |
| error_message | TEXT | | 错误信息 |

#### task_execution_log
| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| id | UUID | PK, DEFAULT gen_random_uuid() | 日志 ID |
| queue_id | UUID | FK → task_queue(id) | 关联的队列任务（可空） |
| site_id | BIGINT | NOT NULL | 门店隔离 |
| task_codes | TEXT[] | NOT NULL | 执行的任务编码列表 |
| status | VARCHAR(20) | NOT NULL | 执行状态 |
| started_at | TIMESTAMPTZ | NOT NULL | 开始时间 |
| finished_at | TIMESTAMPTZ | | 结束时间 |
| exit_code | INTEGER | | 退出码 |
| duration_ms | INTEGER | | 执行时长（毫秒） |
| command | TEXT | | 实际执行的 CLI 命令 |
| output_log | TEXT | | stdout 完整日志 |
| error_log | TEXT | | stderr 日志 |
| summary | JSONB | | 执行摘要 |
| created_at | TIMESTAMPTZ | DEFAULT NOW() | 记录创建时间 |

#### scheduled_tasks
| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| id | UUID | PK, DEFAULT gen_random_uuid() | 调度任务 ID |
| site_id | BIGINT | NOT NULL | 门店隔离 |
| name | VARCHAR(256) | NOT NULL | 调度任务名称 |
| task_codes | TEXT[] | NOT NULL | 任务编码列表 |
| task_config | JSONB | NOT NULL | 序列化的 TaskConfig |
| schedule_config | JSONB | NOT NULL | 序列化的 ScheduleConfig |
| enabled | BOOLEAN | DEFAULT TRUE | 是否启用 |
| last_run_at | TIMESTAMPTZ | | 上次执行时间 |
| next_run_at | TIMESTAMPTZ | | 下次执行时间 |
| run_count | INTEGER | DEFAULT 0 | 累计执行次数 |
| last_status | VARCHAR(20) | | 上次执行状态 |
| created_at | TIMESTAMPTZ | DEFAULT NOW() | 创建时间 |
| updated_at | TIMESTAMPTZ | DEFAULT NOW() | 更新时间 |

### 索引

| 索引名 | 表 | 列 | 类型 | 说明 |
|--------|------|------|------|------|
| idx_admin_users_site | admin_users | site_id | B-tree | 按门店查询用户 |
| idx_task_queue_status | task_queue | status | B-tree | 按状态查询队列 |
| idx_task_queue_site_position | task_queue | site_id, position | 部分索引 (WHERE status='pending') | 取出待执行任务 |
| idx_execution_log_site_started | task_execution_log | site_id, started_at DESC | B-tree | 执行历史列表 |
| idx_scheduled_tasks_site | scheduled_tasks | site_id | B-tree | 按门店查询调度 |
| idx_scheduled_tasks_next_run | scheduled_tasks | next_run_at | 部分索引 (WHERE enabled=TRUE) | 查询到期调度 |

### 种子数据

- 默认管理员：`admin` / `admin123`，site_id=1
- 种子脚本：`db/zqyy_app/seeds/admin_web_seed.sql`

## 兼容性

- **ETL Connector**：无影响。新表位于 `zqyy_app` 库，ETL 数据仍在 `etl_feiqiu` 库
- **后端 API**：新增的 FastAPI 路由将读写这 4 张表，需配置 `zqyy_app` 数据库连接
- **小程序**：无影响。小程序通过 FDW 访问 ETL 数据，不涉及管理后台表
- **现有 zqyy_app 表**：无影响。新表与现有 users/roles/tasks 等表无外键关联

## 回滚策略

```sql
-- 按依赖顺序删除（task_execution_log 引用 task_queue）
BEGIN;
DROP TABLE IF EXISTS task_execution_log CASCADE;
DROP TABLE IF EXISTS task_queue CASCADE;
DROP TABLE IF EXISTS scheduled_tasks CASCADE;
DROP TABLE IF EXISTS admin_users CASCADE;
COMMIT;
```

## 验证 SQL

```sql
-- 1. 验证 4 张表均已创建
SELECT table_name
FROM information_schema.tables
WHERE table_schema = 'public'
  AND table_name IN ('admin_users', 'task_queue', 'task_execution_log', 'scheduled_tasks')
ORDER BY table_name;

-- 2. 验证索引已创建（应返回 6 条）
SELECT indexname, tablename
FROM pg_indexes
WHERE tablename IN ('admin_users', 'task_queue', 'task_execution_log', 'scheduled_tasks')
  AND indexname LIKE 'idx_%'
ORDER BY tablename, indexname;

-- 3. 验证默认管理员已插入
SELECT id, username, display_name, site_id, is_active
FROM admin_users
WHERE username = 'admin';

-- 4. 验证 task_queue 的部分索引条件
SELECT indexname, indexdef
FROM pg_indexes
WHERE indexname = 'idx_task_queue_site_position';

-- 5. 验证 task_execution_log 的外键约束
SELECT conname, conrelid::regclass, confrelid::regclass
FROM pg_constraint
WHERE conrelid = 'task_execution_log'::regclass
  AND contype = 'f';
```
